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