xref: /linux/drivers/android/binder/transaction.rs (revision 4bb1f7e19c4a1d6eeb52b80acff5ac63edd1b91d)
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 {
new( node_ref: NodeRef, from_parent: Option<DArc<Transaction>>, from: &Arc<Thread>, tr: &BinderTransactionDataSg, ) -> BinderResult<DLArc<Self>>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 
new_reply( from: &Arc<Thread>, to: Arc<Process>, tr: &BinderTransactionDataSg, allow_fds: bool, ) -> BinderResult<DLArc<Self>>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)]
debug_print_inner(&self, m: &SeqFile, prefix: &str)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.
is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool176     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.
clone_next(&self) -> Option<DArc<Self>>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.
find_target_thread(&self) -> Option<Arc<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.
find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>>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 
set_outstanding(&self, to_process: &mut ProcessInner)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.
drop_outstanding_txn(&self)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.
submit(self: DLArc<Self>) -> BinderResult240     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.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.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.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.
can_replace(&self, old: &Transaction) -> bool309     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 
prepare_file_list(&self) -> Result<TranslatedFds>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 {
do_work( self: DArc<Self>, thread: &Thread, writer: &mut BinderReturnWriter<'_>, ) -> Result<bool>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 
cancel(self: DArc<Self>)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 
should_sync_wakeup(&self) -> bool441     fn should_sync_wakeup(&self) -> bool {
442         self.flags & TF_ONE_WAY == 0
443     }
444 
debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()>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 {
drop(self: Pin<&mut Self>)453     fn drop(self: Pin<&mut Self>) {
454         self.drop_outstanding_txn();
455     }
456 }
457