590 lines · 20.1 KB · Rust
RawDownload
// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. use kernel::{    net::netlink::GENLMSG_DEFAULT_SIZE,    prelude::*,    seq_file::SeqFile,    seq_print,    sync::atomic::{ordering::Relaxed, Atomic},    sync::{Arc, SpinLock},    task::{Kuid, Pid},    time::{Instant, Monotonic},    types::ScopeGuard,    uapi,}; use crate::{    allocation::{Allocation, TranslatedFds},    defs::*,    error::{BinderError, BinderResult},    netlink::Report,    node::{Node, NodeRef},    process::{Process, ProcessInner},    ptr_align,    thread::{PushWorkRes, Thread},    BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead,}; kernel::impl_flags!(    /// Represents multiple transaction flags.    #[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Zeroable)]    pub struct TransactionFlags(u32);     /// Represents a single transaction flag.    #[derive(Debug, Clone, Copy, PartialEq, Eq)]    pub enum TransactionFlag {        OneWay = TF_ONE_WAY,        AcceptFds = TF_ACCEPT_FDS,        ClearBuf = TF_CLEAR_BUF,        UpdateTxn = TF_UPDATE_TXN,    }); impl TransactionFlags {    /// Creates a `TransactionFlags` from a raw `u32` value.    pub(crate) fn from_bits(bits: u32) -> Self {        Self(bits)    }     /// Checks if the Oneway flag is set.    pub(crate) fn is_oneway(self) -> bool {        self.contains(TransactionFlag::OneWay)    }} #[derive(Zeroable)]pub(crate) struct TransactionInfo {    pub(crate) from_pid: Pid,    pub(crate) from_tid: Pid,    pub(crate) to_pid: Pid,    pub(crate) to_tid: Pid,    pub(crate) code: u32,    pub(crate) flags: TransactionFlags,    pub(crate) data_ptr: UserPtr,    pub(crate) data_size: usize,    pub(crate) offsets_ptr: UserPtr,    pub(crate) offsets_size: usize,    pub(crate) buffers_size: usize,    pub(crate) target_handle: u32,    pub(crate) errno: i32,    pub(crate) reply: u32,    pub(crate) oneway_spam_suspect: bool,    pub(crate) is_reply: bool,    pub(crate) debug_id: usize,} impl TransactionInfo {    #[inline]    pub(crate) fn is_oneway(&self) -> bool {        self.flags.is_oneway()    }     pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) {        if let Err(err) = self.report_netlink_inner(reply, ctx) {            pr_warn!(                "{}:{} netlink report failed: {err:?}\n",                self.from_pid,                self.from_tid            );        }    }     fn report_netlink_inner(&self, reply: u32, ctx: &crate::Context) -> kernel::error::Result {        if !Report::has_listeners() {            return Ok(());        }        let mut report = Report::new(GENLMSG_DEFAULT_SIZE, 0, 0, GFP_KERNEL)?;         report.error(reply)?;        report.context(&ctx.name)?;        report.from_pid(self.from_pid as u32)?;        report.from_tid(self.from_tid as u32)?;        if self.to_pid != 0 {            report.to_pid(self.to_pid as u32)?;        }        if self.to_tid != 0 {            report.to_tid(self.to_tid as u32)?;        }         if self.is_reply {            report.is_reply()?;        }        report.flags(u32::from(self.flags))?;        report.code(self.code)?;        report.data_size(self.data_size as u32)?;         report.multicast(0, GFP_KERNEL)?;        Ok(())    }} use core::mem::offset_of;use kernel::bindings::rb_transaction_layout;pub(crate) const TRANSACTION_LAYOUT: rb_transaction_layout = rb_transaction_layout {    debug_id: offset_of!(Transaction, debug_id),    code: offset_of!(Transaction, code),    flags: offset_of!(Transaction, flags),    from_thread: offset_of!(Transaction, from),    to_proc: offset_of!(Transaction, to),    target_node: offset_of!(Transaction, target_node),}; #[pin_data(PinnedDrop)]pub(crate) struct Transaction {    pub(crate) debug_id: usize,    target_node: Option<DArc<Node>>,    pub(crate) from_parent: Option<DArc<Transaction>>,    pub(crate) from: Arc<Thread>,    pub(crate) to: Arc<Process>,    #[pin]    allocation: SpinLock<Option<Allocation>>,    is_outstanding: Atomic<bool>,    code: u32,    pub(crate) flags: TransactionFlags,    data_size: usize,    offsets_size: usize,    data_address: usize,    sender_euid: Kuid,    txn_security_ctx_off: Option<usize>,    start_time: Instant<Monotonic>,} kernel::list::impl_list_arc_safe! {    impl ListArcSafe<0> for Transaction { untracked; }} impl Transaction {    pub(crate) fn new(        node_ref: NodeRef,        from_parent: Option<DArc<Transaction>>,        from: &Arc<Thread>,        info: &mut TransactionInfo,    ) -> BinderResult<DLArc<Self>> {        let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0;        let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0;        let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None };        let to = node_ref.node.owner.clone();        let mut alloc = match from.copy_transaction_data(            to.clone(),            info,            info.debug_id,            allow_fds,            txn_security_ctx_off.as_mut(),        ) {            Ok(alloc) => alloc,            Err(err) => {                if !err.is_dead() {                    pr_warn!("Failure in copy_transaction_data: {:?}", err);                }                return Err(err);            }        };        if info.is_oneway() {            if from_parent.is_some() {                pr_warn!("Oneway transaction should not be in a transaction stack.");                return Err(EINVAL.into());            }            alloc.set_info_oneway_node(node_ref.node.clone());        }        if info.flags.contains(TransactionFlag::ClearBuf) {            alloc.set_info_clear_on_drop();        }        let target_node = node_ref.node.clone();        alloc.set_info_target_node(node_ref);        let data_address = alloc.ptr;         Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {            debug_id: info.debug_id,            target_node: Some(target_node),            from_parent,            sender_euid: Kuid::current_euid(),            from: from.clone(),            to,            code: info.code,            flags: info.flags,            data_size: info.data_size,            offsets_size: info.offsets_size,            data_address,            allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),            is_outstanding: Atomic::new(false),            txn_security_ctx_off,            start_time: Instant::now(),        }))?)    }     pub(crate) fn new_reply(        from: &Arc<Thread>,        to: Arc<Process>,        info: &mut TransactionInfo,        allow_fds: bool,    ) -> BinderResult<DLArc<Self>> {        let mut alloc =            match from.copy_transaction_data(to.clone(), info, info.debug_id, allow_fds, None) {                Ok(alloc) => alloc,                Err(err) => {                    pr_warn!("Failure in copy_transaction_data: {:?}", err);                    return Err(err);                }            };        if info.flags.contains(TransactionFlag::ClearBuf) {            alloc.set_info_clear_on_drop();        }        Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {            debug_id: info.debug_id,            target_node: None,            from_parent: None,            sender_euid: Kuid::current_euid(),            from: from.clone(),            to,            code: info.code,            flags: info.flags,            data_size: info.data_size,            offsets_size: info.offsets_size,            data_address: alloc.ptr,            allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),            is_outstanding: Atomic::new(false),            txn_security_ctx_off: None,            start_time: Instant::now(),        }))?)    }     #[inline(never)]    pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) {        seq_print!(            m,            "{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms",            prefix,            self.debug_id,            self.from.process.task.pid(),            self.from.id,            self.to.task.pid(),            self.code,            u32::from(self.flags),            self.start_time.elapsed().as_millis(),        );        if let Some(target_node) = &self.target_node {            seq_print!(m, " node {}", target_node.debug_id);        }        seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size);    }     /// Determines if the transaction is stacked on top of the given transaction.    pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool {        match (&self.from_parent, onext) {            (None, None) => true,            (Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next),            _ => false,        }    }     /// Returns a pointer to the next transaction on the transaction stack, if there is one.    pub(crate) fn clone_next(&self) -> Option<DArc<Self>> {        Some(self.from_parent.as_ref()?.clone())    }     /// Searches in the transaction stack for a thread that belongs to the target process. This is    /// useful when finding a target for a new transaction: if the node belongs to a process that    /// is already part of the transaction stack, we reuse the thread.    fn find_target_thread(&self) -> Option<Arc<Thread>> {        let mut it = &self.from_parent;        while let Some(transaction) = it {            if Arc::ptr_eq(&transaction.from.process, &self.to) {                return Some(transaction.from.clone());            }            it = &transaction.from_parent;        }        None    }     /// Searches in the transaction stack for a transaction originating at the given thread.    pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> {        let mut it = &self.from_parent;        while let Some(transaction) = it {            if core::ptr::eq(thread, transaction.from.as_ref()) {                return Some(transaction);            }             it = &transaction.from_parent;        }        None    }     pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) {        // No race because this method is only called once.        if !self.is_outstanding.load(Relaxed) {            self.is_outstanding.store(true, Relaxed);            to_process.add_outstanding_txn();        }    }     /// Decrement `outstanding_txns` in `to` if it hasn't already been decremented.    fn drop_outstanding_txn(&self) {        // No race because this is called at most twice, and one of the calls are in the        // destructor, which is guaranteed to not race with any other operations on the        // transaction. It also cannot race with `set_outstanding`, since submission happens        // before delivery.        if self.is_outstanding.load(Relaxed) {            self.is_outstanding.store(false, Relaxed);            self.to.drop_outstanding_txn();        }    }     /// Submits the transaction to a work queue. Uses a thread if there is one in the transaction    /// stack, otherwise uses the destination process.    ///    /// Not used for replies.    pub(crate) fn submit(self: DLArc<Self>, info: &mut TransactionInfo) -> BinderResult {        // Defined before `process_inner` so that the destructor runs after releasing the lock.        let _t_outdated;        let _oneway_node;         let oneway = self.flags.is_oneway();        let process = self.to.clone();        let mut process_inner = process.inner.lock();         self.set_outstanding(&mut process_inner);         if oneway {            if let Some(target_node) = self.target_node.clone() {                crate::trace::trace_transaction(false, &self, None);                if process_inner.is_frozen.is_frozen() {                    process_inner.async_recv = true;                    if self.flags.contains(TransactionFlag::UpdateTxn) {                        if let Some(t_outdated) =                            target_node.take_outdated_transaction(&self, &mut process_inner)                        {                            let mut alloc_guard = t_outdated.allocation.lock();                            if let Some(alloc) = (*alloc_guard).as_mut() {                                // Take the oneway node to prevent `Allocation::drop` from calling                                // `pending_oneway_finished()`, which would be incorrect as this                                // transaction is not being submitted.                                _oneway_node = alloc.take_oneway_node();                            }                            drop(alloc_guard);                            // Save the transaction to be dropped after locks are released.                            _t_outdated = t_outdated;                        }                    }                }                match target_node.submit_oneway(self, &mut process_inner) {                    Ok(()) => {}                    Err((err, work)) => {                        drop(process_inner);                        // Drop work after releasing process lock.                        drop(work);                        return Err(err);                    }                }                 if process_inner.is_frozen.is_frozen() {                    return Err(BinderError::new_frozen_oneway());                } else {                    return Ok(());                }            } else {                pr_err!("Failed to submit oneway transaction to node.");            }        }         if process_inner.is_frozen.is_frozen() {            process_inner.sync_recv = true;            return Err(BinderError::new_frozen());        }         let res = if let Some(thread) = self.find_target_thread() {            info.to_tid = thread.id;            crate::trace::trace_transaction(false, &self, Some(&thread.task));            match thread.push_work(self) {                PushWorkRes::Ok => Ok(()),                PushWorkRes::OkNotifyPoll => {                    process.notify_poll(true);                    Ok(())                }                PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)),            }        } else {            crate::trace::trace_transaction(false, &self, None);            process_inner.push_work(&process, self)        };        drop(process_inner);         match res {            Ok(()) => Ok(()),            Err((err, work)) => {                // Drop work after releasing process lock.                drop(work);                Err(err)            }        }    }     /// Check whether one oneway transaction can supersede another.    pub(crate) fn can_replace(&self, old: &Transaction) -> bool {        if self.from.process.task.pid() != old.from.process.task.pid() {            return false;        }         let required = TransactionFlag::OneWay | TransactionFlag::UpdateTxn;        if !(self.flags.contains_all(required) && old.flags.contains_all(required)) {            return false;        }         let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) {            (None, None) => true,            (Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2),            _ => false,        };         self.code == old.code && self.flags == old.flags && target_node_match    }     fn prepare_file_list(&self) -> Result<TranslatedFds> {        let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;         match alloc.translate_fds() {            Ok(translated) => {                *self.allocation.lock() = Some(alloc);                Ok(translated)            }            Err(err) => {                // Free the allocation eagerly.                drop(alloc);                Err(err)            }        }    }} impl DeliverToRead for Transaction {    fn do_work(        self: DArc<Self>,        thread: &Thread,        writer: &mut BinderReturnWriter<'_>,    ) -> Result<bool> {        let send_failed_reply = ScopeGuard::new(|| {            if self.target_node.is_some() && !self.flags.is_oneway() {                let reply = Err(BR_FAILED_REPLY);                self.from.deliver_reply(reply, &self, None);            }            self.drop_outstanding_txn();        });         let files = if let Ok(list) = self.prepare_file_list() {            list        } else {            // On failure to process the list, we send a reply back to the sender and ignore the            // transaction on the recipient.            binder_debug!(                FailedTransaction,                "transaction {} to {} failed, fd fixups failed, size {}-{}",                self.debug_id,                self.to.task.pid(),                self.data_size,                self.offsets_size            );            return Ok(true);        };         let mut tr_sec = BinderTransactionDataSecctx::default();        let tr = tr_sec.tr_data();        if let Some(target_node) = &self.target_node {            let (ptr, cookie) = target_node.get_id();            tr.target.ptr = ptr as uapi::binder_uintptr_t;            tr.cookie = cookie as uapi::binder_uintptr_t;        };        tr.code = self.code;        tr.flags = u32::from(self.flags);        tr.data_size = self.data_size as uapi::binder_size_t;        tr.data.ptr.buffer = self.data_address as uapi::binder_uintptr_t;

Lines 1–500 of 590.