File Index
Files and directories
Sign in
FILE

transaction.rs

File preview with syntax highlighting.

linux-6.19.14/drivers/android/binder/transaction.rs RUST
  1// SPDX-License-Identifier: GPL-2.0
  2
  3// Copyright (C) 2025 Google LLC.
  4
  5use core::sync::atomic::{AtomicBool, Ordering};
  6use 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
 16use 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)]
 28pub(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
 48kernel::list::impl_list_arc_safe! {
 49    impl ListArcSafe<0> for Transaction { untracked; }
 50}
 51
 52impl Transaction {
 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
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)]
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.
176    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.
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.
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.
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
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.
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.
240    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.
309    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
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
344impl DeliverToRead for Transaction {
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
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
441    fn should_sync_wakeup(&self) -> bool {
442        self.flags & TF_ONE_WAY == 0
443    }
444
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]
452impl PinnedDrop for Transaction {
453    fn drop(self: Pin<&mut Self>) {
454        self.drop_outstanding_txn();
455    }
456}
~/ Read-only file preview.