Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/android/binder/transaction.rs
29519 views
1
// SPDX-License-Identifier: GPL-2.0
2
3
// Copyright (C) 2025 Google LLC.
4
5
use core::sync::atomic::{AtomicBool, Ordering};
6
use kernel::{
7
prelude::*,
8
seq_file::SeqFile,
9
seq_print,
10
sync::{Arc, SpinLock},
11
task::Kuid,
12
time::{Instant, Monotonic},
13
types::ScopeGuard,
14
};
15
16
use crate::{
17
allocation::{Allocation, TranslatedFds},
18
defs::*,
19
error::{BinderError, BinderResult},
20
node::{Node, NodeRef},
21
process::{Process, ProcessInner},
22
ptr_align,
23
thread::{PushWorkRes, Thread},
24
BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead,
25
};
26
27
#[pin_data(PinnedDrop)]
28
pub(crate) struct Transaction {
29
pub(crate) debug_id: usize,
30
target_node: Option<DArc<Node>>,
31
pub(crate) from_parent: Option<DArc<Transaction>>,
32
pub(crate) from: Arc<Thread>,
33
pub(crate) to: Arc<Process>,
34
#[pin]
35
allocation: SpinLock<Option<Allocation>>,
36
is_outstanding: AtomicBool,
37
code: u32,
38
pub(crate) flags: u32,
39
data_size: usize,
40
offsets_size: usize,
41
data_address: usize,
42
sender_euid: Kuid,
43
txn_security_ctx_off: Option<usize>,
44
pub(crate) oneway_spam_detected: bool,
45
start_time: Instant<Monotonic>,
46
}
47
48
kernel::list::impl_list_arc_safe! {
49
impl ListArcSafe<0> for Transaction { untracked; }
50
}
51
52
impl Transaction {
53
pub(crate) fn new(
54
node_ref: NodeRef,
55
from_parent: Option<DArc<Transaction>>,
56
from: &Arc<Thread>,
57
tr: &BinderTransactionDataSg,
58
) -> BinderResult<DLArc<Self>> {
59
let debug_id = super::next_debug_id();
60
let trd = &tr.transaction_data;
61
let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0;
62
let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0;
63
let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None };
64
let to = node_ref.node.owner.clone();
65
let mut alloc = match from.copy_transaction_data(
66
to.clone(),
67
tr,
68
debug_id,
69
allow_fds,
70
txn_security_ctx_off.as_mut(),
71
) {
72
Ok(alloc) => alloc,
73
Err(err) => {
74
if !err.is_dead() {
75
pr_warn!("Failure in copy_transaction_data: {:?}", err);
76
}
77
return Err(err);
78
}
79
};
80
let oneway_spam_detected = alloc.oneway_spam_detected;
81
if trd.flags & TF_ONE_WAY != 0 {
82
if from_parent.is_some() {
83
pr_warn!("Oneway transaction should not be in a transaction stack.");
84
return Err(EINVAL.into());
85
}
86
alloc.set_info_oneway_node(node_ref.node.clone());
87
}
88
if trd.flags & TF_CLEAR_BUF != 0 {
89
alloc.set_info_clear_on_drop();
90
}
91
let target_node = node_ref.node.clone();
92
alloc.set_info_target_node(node_ref);
93
let data_address = alloc.ptr;
94
95
Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
96
debug_id,
97
target_node: Some(target_node),
98
from_parent,
99
sender_euid: from.process.task.euid(),
100
from: from.clone(),
101
to,
102
code: trd.code,
103
flags: trd.flags,
104
data_size: trd.data_size as _,
105
offsets_size: trd.offsets_size as _,
106
data_address,
107
allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),
108
is_outstanding: AtomicBool::new(false),
109
txn_security_ctx_off,
110
oneway_spam_detected,
111
start_time: Instant::now(),
112
}))?)
113
}
114
115
pub(crate) fn new_reply(
116
from: &Arc<Thread>,
117
to: Arc<Process>,
118
tr: &BinderTransactionDataSg,
119
allow_fds: bool,
120
) -> BinderResult<DLArc<Self>> {
121
let debug_id = super::next_debug_id();
122
let trd = &tr.transaction_data;
123
let mut alloc = match from.copy_transaction_data(to.clone(), tr, debug_id, allow_fds, None)
124
{
125
Ok(alloc) => alloc,
126
Err(err) => {
127
pr_warn!("Failure in copy_transaction_data: {:?}", err);
128
return Err(err);
129
}
130
};
131
let oneway_spam_detected = alloc.oneway_spam_detected;
132
if trd.flags & TF_CLEAR_BUF != 0 {
133
alloc.set_info_clear_on_drop();
134
}
135
Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
136
debug_id,
137
target_node: None,
138
from_parent: None,
139
sender_euid: from.process.task.euid(),
140
from: from.clone(),
141
to,
142
code: trd.code,
143
flags: trd.flags,
144
data_size: trd.data_size as _,
145
offsets_size: trd.offsets_size as _,
146
data_address: alloc.ptr,
147
allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),
148
is_outstanding: AtomicBool::new(false),
149
txn_security_ctx_off: None,
150
oneway_spam_detected,
151
start_time: Instant::now(),
152
}))?)
153
}
154
155
#[inline(never)]
156
pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) {
157
seq_print!(
158
m,
159
"{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms",
160
prefix,
161
self.debug_id,
162
self.from.process.task.pid(),
163
self.from.id,
164
self.to.task.pid(),
165
self.code,
166
self.flags,
167
self.start_time.elapsed().as_millis(),
168
);
169
if let Some(target_node) = &self.target_node {
170
seq_print!(m, " node {}", target_node.debug_id);
171
}
172
seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size);
173
}
174
175
/// Determines if the transaction is stacked on top of the given transaction.
176
pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool {
177
match (&self.from_parent, onext) {
178
(None, None) => true,
179
(Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next),
180
_ => false,
181
}
182
}
183
184
/// Returns a pointer to the next transaction on the transaction stack, if there is one.
185
pub(crate) fn clone_next(&self) -> Option<DArc<Self>> {
186
Some(self.from_parent.as_ref()?.clone())
187
}
188
189
/// Searches in the transaction stack for a thread that belongs to the target process. This is
190
/// useful when finding a target for a new transaction: if the node belongs to a process that
191
/// is already part of the transaction stack, we reuse the thread.
192
fn find_target_thread(&self) -> Option<Arc<Thread>> {
193
let mut it = &self.from_parent;
194
while let Some(transaction) = it {
195
if Arc::ptr_eq(&transaction.from.process, &self.to) {
196
return Some(transaction.from.clone());
197
}
198
it = &transaction.from_parent;
199
}
200
None
201
}
202
203
/// Searches in the transaction stack for a transaction originating at the given thread.
204
pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> {
205
let mut it = &self.from_parent;
206
while let Some(transaction) = it {
207
if core::ptr::eq(thread, transaction.from.as_ref()) {
208
return Some(transaction);
209
}
210
211
it = &transaction.from_parent;
212
}
213
None
214
}
215
216
pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) {
217
// No race because this method is only called once.
218
if !self.is_outstanding.load(Ordering::Relaxed) {
219
self.is_outstanding.store(true, Ordering::Relaxed);
220
to_process.add_outstanding_txn();
221
}
222
}
223
224
/// Decrement `outstanding_txns` in `to` if it hasn't already been decremented.
225
fn drop_outstanding_txn(&self) {
226
// No race because this is called at most twice, and one of the calls are in the
227
// destructor, which is guaranteed to not race with any other operations on the
228
// transaction. It also cannot race with `set_outstanding`, since submission happens
229
// before delivery.
230
if self.is_outstanding.load(Ordering::Relaxed) {
231
self.is_outstanding.store(false, Ordering::Relaxed);
232
self.to.drop_outstanding_txn();
233
}
234
}
235
236
/// Submits the transaction to a work queue. Uses a thread if there is one in the transaction
237
/// stack, otherwise uses the destination process.
238
///
239
/// Not used for replies.
240
pub(crate) fn submit(self: DLArc<Self>) -> BinderResult {
241
// Defined before `process_inner` so that the destructor runs after releasing the lock.
242
let mut _t_outdated;
243
244
let oneway = self.flags & TF_ONE_WAY != 0;
245
let process = self.to.clone();
246
let mut process_inner = process.inner.lock();
247
248
self.set_outstanding(&mut process_inner);
249
250
if oneway {
251
if let Some(target_node) = self.target_node.clone() {
252
if process_inner.is_frozen {
253
process_inner.async_recv = true;
254
if self.flags & TF_UPDATE_TXN != 0 {
255
if let Some(t_outdated) =
256
target_node.take_outdated_transaction(&self, &mut process_inner)
257
{
258
// Save the transaction to be dropped after locks are released.
259
_t_outdated = t_outdated;
260
}
261
}
262
}
263
match target_node.submit_oneway(self, &mut process_inner) {
264
Ok(()) => {}
265
Err((err, work)) => {
266
drop(process_inner);
267
// Drop work after releasing process lock.
268
drop(work);
269
return Err(err);
270
}
271
}
272
273
if process_inner.is_frozen {
274
return Err(BinderError::new_frozen_oneway());
275
} else {
276
return Ok(());
277
}
278
} else {
279
pr_err!("Failed to submit oneway transaction to node.");
280
}
281
}
282
283
if process_inner.is_frozen {
284
process_inner.sync_recv = true;
285
return Err(BinderError::new_frozen());
286
}
287
288
let res = if let Some(thread) = self.find_target_thread() {
289
match thread.push_work(self) {
290
PushWorkRes::Ok => Ok(()),
291
PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)),
292
}
293
} else {
294
process_inner.push_work(self)
295
};
296
drop(process_inner);
297
298
match res {
299
Ok(()) => Ok(()),
300
Err((err, work)) => {
301
// Drop work after releasing process lock.
302
drop(work);
303
Err(err)
304
}
305
}
306
}
307
308
/// Check whether one oneway transaction can supersede another.
309
pub(crate) fn can_replace(&self, old: &Transaction) -> bool {
310
if self.from.process.task.pid() != old.from.process.task.pid() {
311
return false;
312
}
313
314
if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) {
315
return false;
316
}
317
318
let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) {
319
(None, None) => true,
320
(Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2),
321
_ => false,
322
};
323
324
self.code == old.code && self.flags == old.flags && target_node_match
325
}
326
327
fn prepare_file_list(&self) -> Result<TranslatedFds> {
328
let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;
329
330
match alloc.translate_fds() {
331
Ok(translated) => {
332
*self.allocation.lock() = Some(alloc);
333
Ok(translated)
334
}
335
Err(err) => {
336
// Free the allocation eagerly.
337
drop(alloc);
338
Err(err)
339
}
340
}
341
}
342
}
343
344
impl DeliverToRead for Transaction {
345
fn do_work(
346
self: DArc<Self>,
347
thread: &Thread,
348
writer: &mut BinderReturnWriter<'_>,
349
) -> Result<bool> {
350
let send_failed_reply = ScopeGuard::new(|| {
351
if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
352
let reply = Err(BR_FAILED_REPLY);
353
self.from.deliver_reply(reply, &self);
354
}
355
self.drop_outstanding_txn();
356
});
357
358
let files = if let Ok(list) = self.prepare_file_list() {
359
list
360
} else {
361
// On failure to process the list, we send a reply back to the sender and ignore the
362
// transaction on the recipient.
363
return Ok(true);
364
};
365
366
let mut tr_sec = BinderTransactionDataSecctx::default();
367
let tr = tr_sec.tr_data();
368
if let Some(target_node) = &self.target_node {
369
let (ptr, cookie) = target_node.get_id();
370
tr.target.ptr = ptr as _;
371
tr.cookie = cookie as _;
372
};
373
tr.code = self.code;
374
tr.flags = self.flags;
375
tr.data_size = self.data_size as _;
376
tr.data.ptr.buffer = self.data_address as _;
377
tr.offsets_size = self.offsets_size as _;
378
if tr.offsets_size > 0 {
379
tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _;
380
}
381
tr.sender_euid = self.sender_euid.into_uid_in_current_ns();
382
tr.sender_pid = 0;
383
if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
384
// Not a reply and not one-way.
385
tr.sender_pid = self.from.process.pid_in_current_ns();
386
}
387
let code = if self.target_node.is_none() {
388
BR_REPLY
389
} else if self.txn_security_ctx_off.is_some() {
390
BR_TRANSACTION_SEC_CTX
391
} else {
392
BR_TRANSACTION
393
};
394
395
// Write the transaction code and data to the user buffer.
396
writer.write_code(code)?;
397
if let Some(off) = self.txn_security_ctx_off {
398
tr_sec.secctx = (self.data_address + off) as u64;
399
writer.write_payload(&tr_sec)?;
400
} else {
401
writer.write_payload(&*tr)?;
402
}
403
404
let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;
405
406
// Dismiss the completion of transaction with a failure. No failure paths are allowed from
407
// here on out.
408
send_failed_reply.dismiss();
409
410
// Commit files, and set FDs in FDA to be closed on buffer free.
411
let close_on_free = files.commit();
412
alloc.set_info_close_on_free(close_on_free);
413
414
// It is now the user's responsibility to clear the allocation.
415
alloc.keep_alive();
416
417
self.drop_outstanding_txn();
418
419
// When this is not a reply and not a oneway transaction, update `current_transaction`. If
420
// it's a reply, `current_transaction` has already been updated appropriately.
421
if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 {
422
thread.set_current_transaction(self);
423
}
424
425
Ok(false)
426
}
427
428
fn cancel(self: DArc<Self>) {
429
let allocation = self.allocation.lock().take();
430
drop(allocation);
431
432
// If this is not a reply or oneway transaction, then send a dead reply.
433
if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
434
let reply = Err(BR_DEAD_REPLY);
435
self.from.deliver_reply(reply, &self);
436
}
437
438
self.drop_outstanding_txn();
439
}
440
441
fn should_sync_wakeup(&self) -> bool {
442
self.flags & TF_ONE_WAY == 0
443
}
444
445
fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> {
446
self.debug_print_inner(m, tprefix);
447
Ok(())
448
}
449
}
450
451
#[pinned_drop]
452
impl PinnedDrop for Transaction {
453
fn drop(self: Pin<&mut Self>) {
454
self.drop_outstanding_txn();
455
}
456
}
457
458