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