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