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