Path: blob/master/drivers/android/binder/transaction.rs
29519 views
// SPDX-License-Identifier: GPL-2.012// Copyright (C) 2025 Google LLC.34use core::sync::atomic::{AtomicBool, Ordering};5use kernel::{6prelude::*,7seq_file::SeqFile,8seq_print,9sync::{Arc, SpinLock},10task::Kuid,11time::{Instant, Monotonic},12types::ScopeGuard,13};1415use crate::{16allocation::{Allocation, TranslatedFds},17defs::*,18error::{BinderError, BinderResult},19node::{Node, NodeRef},20process::{Process, ProcessInner},21ptr_align,22thread::{PushWorkRes, Thread},23BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead,24};2526#[pin_data(PinnedDrop)]27pub(crate) struct Transaction {28pub(crate) debug_id: usize,29target_node: Option<DArc<Node>>,30pub(crate) from_parent: Option<DArc<Transaction>>,31pub(crate) from: Arc<Thread>,32pub(crate) to: Arc<Process>,33#[pin]34allocation: SpinLock<Option<Allocation>>,35is_outstanding: AtomicBool,36code: u32,37pub(crate) flags: u32,38data_size: usize,39offsets_size: usize,40data_address: usize,41sender_euid: Kuid,42txn_security_ctx_off: Option<usize>,43pub(crate) oneway_spam_detected: bool,44start_time: Instant<Monotonic>,45}4647kernel::list::impl_list_arc_safe! {48impl ListArcSafe<0> for Transaction { untracked; }49}5051impl Transaction {52pub(crate) fn new(53node_ref: NodeRef,54from_parent: Option<DArc<Transaction>>,55from: &Arc<Thread>,56tr: &BinderTransactionDataSg,57) -> BinderResult<DLArc<Self>> {58let debug_id = super::next_debug_id();59let trd = &tr.transaction_data;60let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0;61let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0;62let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None };63let to = node_ref.node.owner.clone();64let mut alloc = match from.copy_transaction_data(65to.clone(),66tr,67debug_id,68allow_fds,69txn_security_ctx_off.as_mut(),70) {71Ok(alloc) => alloc,72Err(err) => {73if !err.is_dead() {74pr_warn!("Failure in copy_transaction_data: {:?}", err);75}76return Err(err);77}78};79let oneway_spam_detected = alloc.oneway_spam_detected;80if trd.flags & TF_ONE_WAY != 0 {81if from_parent.is_some() {82pr_warn!("Oneway transaction should not be in a transaction stack.");83return Err(EINVAL.into());84}85alloc.set_info_oneway_node(node_ref.node.clone());86}87if trd.flags & TF_CLEAR_BUF != 0 {88alloc.set_info_clear_on_drop();89}90let target_node = node_ref.node.clone();91alloc.set_info_target_node(node_ref);92let data_address = alloc.ptr;9394Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {95debug_id,96target_node: Some(target_node),97from_parent,98sender_euid: from.process.task.euid(),99from: from.clone(),100to,101code: trd.code,102flags: trd.flags,103data_size: trd.data_size as _,104offsets_size: trd.offsets_size as _,105data_address,106allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),107is_outstanding: AtomicBool::new(false),108txn_security_ctx_off,109oneway_spam_detected,110start_time: Instant::now(),111}))?)112}113114pub(crate) fn new_reply(115from: &Arc<Thread>,116to: Arc<Process>,117tr: &BinderTransactionDataSg,118allow_fds: bool,119) -> BinderResult<DLArc<Self>> {120let debug_id = super::next_debug_id();121let trd = &tr.transaction_data;122let mut alloc = match from.copy_transaction_data(to.clone(), tr, debug_id, allow_fds, None)123{124Ok(alloc) => alloc,125Err(err) => {126pr_warn!("Failure in copy_transaction_data: {:?}", err);127return Err(err);128}129};130let oneway_spam_detected = alloc.oneway_spam_detected;131if trd.flags & TF_CLEAR_BUF != 0 {132alloc.set_info_clear_on_drop();133}134Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {135debug_id,136target_node: None,137from_parent: None,138sender_euid: from.process.task.euid(),139from: from.clone(),140to,141code: trd.code,142flags: trd.flags,143data_size: trd.data_size as _,144offsets_size: trd.offsets_size as _,145data_address: alloc.ptr,146allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"),147is_outstanding: AtomicBool::new(false),148txn_security_ctx_off: None,149oneway_spam_detected,150start_time: Instant::now(),151}))?)152}153154#[inline(never)]155pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) {156seq_print!(157m,158"{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms",159prefix,160self.debug_id,161self.from.process.task.pid(),162self.from.id,163self.to.task.pid(),164self.code,165self.flags,166self.start_time.elapsed().as_millis(),167);168if let Some(target_node) = &self.target_node {169seq_print!(m, " node {}", target_node.debug_id);170}171seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size);172}173174/// Determines if the transaction is stacked on top of the given transaction.175pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool {176match (&self.from_parent, onext) {177(None, None) => true,178(Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next),179_ => false,180}181}182183/// Returns a pointer to the next transaction on the transaction stack, if there is one.184pub(crate) fn clone_next(&self) -> Option<DArc<Self>> {185Some(self.from_parent.as_ref()?.clone())186}187188/// Searches in the transaction stack for a thread that belongs to the target process. This is189/// useful when finding a target for a new transaction: if the node belongs to a process that190/// is already part of the transaction stack, we reuse the thread.191fn find_target_thread(&self) -> Option<Arc<Thread>> {192let mut it = &self.from_parent;193while let Some(transaction) = it {194if Arc::ptr_eq(&transaction.from.process, &self.to) {195return Some(transaction.from.clone());196}197it = &transaction.from_parent;198}199None200}201202/// Searches in the transaction stack for a transaction originating at the given thread.203pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> {204let mut it = &self.from_parent;205while let Some(transaction) = it {206if core::ptr::eq(thread, transaction.from.as_ref()) {207return Some(transaction);208}209210it = &transaction.from_parent;211}212None213}214215pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) {216// No race because this method is only called once.217if !self.is_outstanding.load(Ordering::Relaxed) {218self.is_outstanding.store(true, Ordering::Relaxed);219to_process.add_outstanding_txn();220}221}222223/// Decrement `outstanding_txns` in `to` if it hasn't already been decremented.224fn drop_outstanding_txn(&self) {225// No race because this is called at most twice, and one of the calls are in the226// destructor, which is guaranteed to not race with any other operations on the227// transaction. It also cannot race with `set_outstanding`, since submission happens228// before delivery.229if self.is_outstanding.load(Ordering::Relaxed) {230self.is_outstanding.store(false, Ordering::Relaxed);231self.to.drop_outstanding_txn();232}233}234235/// Submits the transaction to a work queue. Uses a thread if there is one in the transaction236/// stack, otherwise uses the destination process.237///238/// Not used for replies.239pub(crate) fn submit(self: DLArc<Self>) -> BinderResult {240// Defined before `process_inner` so that the destructor runs after releasing the lock.241let mut _t_outdated;242243let oneway = self.flags & TF_ONE_WAY != 0;244let process = self.to.clone();245let mut process_inner = process.inner.lock();246247self.set_outstanding(&mut process_inner);248249if oneway {250if let Some(target_node) = self.target_node.clone() {251if process_inner.is_frozen {252process_inner.async_recv = true;253if self.flags & TF_UPDATE_TXN != 0 {254if let Some(t_outdated) =255target_node.take_outdated_transaction(&self, &mut process_inner)256{257// Save the transaction to be dropped after locks are released.258_t_outdated = t_outdated;259}260}261}262match target_node.submit_oneway(self, &mut process_inner) {263Ok(()) => {}264Err((err, work)) => {265drop(process_inner);266// Drop work after releasing process lock.267drop(work);268return Err(err);269}270}271272if process_inner.is_frozen {273return Err(BinderError::new_frozen_oneway());274} else {275return Ok(());276}277} else {278pr_err!("Failed to submit oneway transaction to node.");279}280}281282if process_inner.is_frozen {283process_inner.sync_recv = true;284return Err(BinderError::new_frozen());285}286287let res = if let Some(thread) = self.find_target_thread() {288match thread.push_work(self) {289PushWorkRes::Ok => Ok(()),290PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)),291}292} else {293process_inner.push_work(self)294};295drop(process_inner);296297match res {298Ok(()) => Ok(()),299Err((err, work)) => {300// Drop work after releasing process lock.301drop(work);302Err(err)303}304}305}306307/// Check whether one oneway transaction can supersede another.308pub(crate) fn can_replace(&self, old: &Transaction) -> bool {309if self.from.process.task.pid() != old.from.process.task.pid() {310return false;311}312313if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) {314return false;315}316317let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) {318(None, None) => true,319(Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2),320_ => false,321};322323self.code == old.code && self.flags == old.flags && target_node_match324}325326fn prepare_file_list(&self) -> Result<TranslatedFds> {327let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;328329match alloc.translate_fds() {330Ok(translated) => {331*self.allocation.lock() = Some(alloc);332Ok(translated)333}334Err(err) => {335// Free the allocation eagerly.336drop(alloc);337Err(err)338}339}340}341}342343impl DeliverToRead for Transaction {344fn do_work(345self: DArc<Self>,346thread: &Thread,347writer: &mut BinderReturnWriter<'_>,348) -> Result<bool> {349let send_failed_reply = ScopeGuard::new(|| {350if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {351let reply = Err(BR_FAILED_REPLY);352self.from.deliver_reply(reply, &self);353}354self.drop_outstanding_txn();355});356357let files = if let Ok(list) = self.prepare_file_list() {358list359} else {360// On failure to process the list, we send a reply back to the sender and ignore the361// transaction on the recipient.362return Ok(true);363};364365let mut tr_sec = BinderTransactionDataSecctx::default();366let tr = tr_sec.tr_data();367if let Some(target_node) = &self.target_node {368let (ptr, cookie) = target_node.get_id();369tr.target.ptr = ptr as _;370tr.cookie = cookie as _;371};372tr.code = self.code;373tr.flags = self.flags;374tr.data_size = self.data_size as _;375tr.data.ptr.buffer = self.data_address as _;376tr.offsets_size = self.offsets_size as _;377if tr.offsets_size > 0 {378tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _;379}380tr.sender_euid = self.sender_euid.into_uid_in_current_ns();381tr.sender_pid = 0;382if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {383// Not a reply and not one-way.384tr.sender_pid = self.from.process.pid_in_current_ns();385}386let code = if self.target_node.is_none() {387BR_REPLY388} else if self.txn_security_ctx_off.is_some() {389BR_TRANSACTION_SEC_CTX390} else {391BR_TRANSACTION392};393394// Write the transaction code and data to the user buffer.395writer.write_code(code)?;396if let Some(off) = self.txn_security_ctx_off {397tr_sec.secctx = (self.data_address + off) as u64;398writer.write_payload(&tr_sec)?;399} else {400writer.write_payload(&*tr)?;401}402403let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;404405// Dismiss the completion of transaction with a failure. No failure paths are allowed from406// here on out.407send_failed_reply.dismiss();408409// Commit files, and set FDs in FDA to be closed on buffer free.410let close_on_free = files.commit();411alloc.set_info_close_on_free(close_on_free);412413// It is now the user's responsibility to clear the allocation.414alloc.keep_alive();415416self.drop_outstanding_txn();417418// When this is not a reply and not a oneway transaction, update `current_transaction`. If419// it's a reply, `current_transaction` has already been updated appropriately.420if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 {421thread.set_current_transaction(self);422}423424Ok(false)425}426427fn cancel(self: DArc<Self>) {428let allocation = self.allocation.lock().take();429drop(allocation);430431// If this is not a reply or oneway transaction, then send a dead reply.432if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {433let reply = Err(BR_DEAD_REPLY);434self.from.deliver_reply(reply, &self);435}436437self.drop_outstanding_txn();438}439440fn should_sync_wakeup(&self) -> bool {441self.flags & TF_ONE_WAY == 0442}443444fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> {445self.debug_print_inner(m, tprefix);446Ok(())447}448}449450#[pinned_drop]451impl PinnedDrop for Transaction {452fn drop(self: Pin<&mut Self>) {453self.drop_outstanding_txn();454}455}456457458