Files
android_kernel_samsung_sm8750/drivers/android/binder/transaction.rs
2025-08-11 13:49:01 +02:00

527 lines
18 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2024 Google LLC.
use core::sync::atomic::{AtomicBool, Ordering};
use kernel::{
list::ListArcSafe,
prelude::*,
seq_file::SeqFile,
seq_print,
sync::{Arc, SpinLock},
task::Kuid,
time::{ktime_ms_delta, Ktime},
types::ScopeGuard,
uaccess::UserSliceWriter,
};
use crate::{
allocation::{Allocation, TranslatedFds},
defs::*,
error::{BinderError, BinderResult},
node::{Node, NodeRef},
prio::{self, BinderPriority, PriorityState},
process::{Process, ProcessInner},
ptr_align,
thread::{PushWorkRes, Thread},
DArc, DLArc, DTRWrap, DeliverToRead,
};
#[pin_data(PinnedDrop)]
pub(crate) struct Transaction {
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: AtomicBool,
set_priority_called: AtomicBool,
priority: BinderPriority,
#[pin]
saved_priority: SpinLock<BinderPriority>,
code: u32,
pub(crate) flags: u32,
data_size: usize,
offsets_size: usize,
data_address: usize,
sender_euid: Kuid,
txn_security_ctx_off: Option<usize>,
pub(crate) oneway_spam_detected: bool,
start_time: Ktime,
}
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>,
tr: &BinderTransactionDataSg,
) -> BinderResult<DLArc<Self>> {
let trd = &tr.transaction_data;
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(),
tr,
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);
}
};
let oneway_spam_detected = alloc.oneway_spam_detected;
if trd.flags & TF_ONE_WAY != 0 {
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 trd.flags & TF_CLEAR_BUF != 0 {
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;
let priority =
if (trd.flags & TF_ONE_WAY == 0) && prio::is_supported_policy(from.task.policy()) {
BinderPriority {
sched_policy: from.task.policy(),
prio: from.task.normal_prio(),
}
} else {
from.process.default_priority
};
Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
debug_id: super::next_debug_id(),
target_node: Some(target_node),
from_parent,
sender_euid: from.process.cred.euid(),
from: from.clone(),
to,
code: trd.code,
flags: trd.flags,
data_size: trd.data_size as _,
offsets_size: trd.offsets_size as _,
data_address,
allocation <- kernel::new_spinlock!(Some(alloc), "Transaction::new"),
is_outstanding: AtomicBool::new(false),
priority,
saved_priority <- kernel::new_spinlock!(BinderPriority::default(), "Transaction::saved_priority"),
set_priority_called: AtomicBool::new(false),
txn_security_ctx_off,
oneway_spam_detected,
start_time: Ktime::ktime_get(),
}))?)
}
pub(crate) fn new_reply(
from: &Arc<Thread>,
to: Arc<Process>,
tr: &BinderTransactionDataSg,
allow_fds: bool,
) -> BinderResult<DLArc<Self>> {
let trd = &tr.transaction_data;
let mut alloc = match from.copy_transaction_data(to.clone(), tr, allow_fds, None) {
Ok(alloc) => alloc,
Err(err) => {
pr_warn!("Failure in copy_transaction_data: {:?}", err);
return Err(err);
}
};
let oneway_spam_detected = alloc.oneway_spam_detected;
if trd.flags & TF_CLEAR_BUF != 0 {
alloc.set_info_clear_on_drop();
}
Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
debug_id: super::next_debug_id(),
target_node: None,
from_parent: None,
sender_euid: from.process.task.euid(),
from: from.clone(),
to,
code: trd.code,
flags: trd.flags,
data_size: trd.data_size as _,
offsets_size: trd.offsets_size as _,
data_address: alloc.ptr,
allocation <- kernel::new_spinlock!(Some(alloc), "Transaction::new"),
is_outstanding: AtomicBool::new(false),
priority: BinderPriority::default(),
saved_priority <- kernel::new_spinlock!(BinderPriority::default(), "Transaction::saved_priority"),
set_priority_called: AtomicBool::new(false),
txn_security_ctx_off: None,
oneway_spam_detected,
start_time: Ktime::ktime_get(),
}))?)
}
#[inline(never)]
pub(crate) fn debug_print_inner(&self, m: &mut SeqFile, prefix: &str) {
seq_print!(
m,
"{}{}: from {}:{} to {} code {:x} flags {:x} pri {}:{} elapsed {}ms",
prefix,
self.debug_id,
self.from.process.task.pid(),
self.from.id,
self.to.task.pid(),
self.code,
self.flags,
self.priority.sched_policy,
self.priority.prio,
ktime_ms_delta(Ktime::ktime_get(), self.start_time),
);
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);
}
pub(crate) fn saved_priority(&self) -> BinderPriority {
*self.saved_priority.lock()
}
/// 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.clone());
}
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(Ordering::Relaxed) {
self.is_outstanding.store(true, Ordering::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(Ordering::Relaxed) {
self.is_outstanding.store(false, Ordering::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>) -> BinderResult {
// Defined before `process_inner` so that the destructor runs after releasing the lock.
let mut _t_outdated = None;
let oneway = self.flags & TF_ONE_WAY != 0;
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() {
if process_inner.is_frozen {
process_inner.async_recv = true;
if self.flags & TF_UPDATE_TXN != 0 {
_t_outdated =
target_node.take_outdated_transaction(&self, &mut process_inner);
}
}
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 {
return Err(BinderError::new_frozen_oneway());
} else {
return Ok(());
}
} else {
pr_err!("Failed to submit oneway transaction to node.");
}
}
if process_inner.is_frozen {
process_inner.sync_recv = true;
return Err(BinderError::new_frozen());
}
let res = if let Some(thread) = self.find_target_thread() {
match thread.push_work(self) {
PushWorkRes::Ok => Ok(()),
PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)),
}
} else {
process_inner.push_work(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;
}
if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) {
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 UserSliceWriter) -> Result<bool> {
let send_failed_reply = ScopeGuard::new(|| {
if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
let reply = Err(BR_FAILED_REPLY);
self.from.deliver_reply(reply, &self);
}
self.drop_outstanding_txn();
});
// Update thread priority. This only has an effect if the transaction is delivered via the
// process work list, since the priority has otherwise already been updated.
self.on_thread_selected(thread);
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.
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 _;
tr.cookie = cookie as _;
};
tr.code = self.code;
tr.flags = self.flags;
tr.data_size = self.data_size as _;
tr.data.ptr.buffer = self.data_address as _;
tr.offsets_size = self.offsets_size as _;
if tr.offsets_size > 0 {
tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size)) as _;
}
tr.sender_euid = self.sender_euid.into_uid_in_current_ns();
tr.sender_pid = 0;
if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
// Not a reply and not one-way.
tr.sender_pid = self.from.process.task.pid_in_current_ns();
}
let code = if self.target_node.is_none() {
BR_REPLY
} else if self.txn_security_ctx_off.is_some() {
BR_TRANSACTION_SEC_CTX
} else {
BR_TRANSACTION
};
// Write the transaction code and data to the user buffer.
writer.write(&code)?;
if let Some(off) = self.txn_security_ctx_off {
tr_sec.secctx = (self.data_address + off) as u64;
writer.write(&tr_sec)?;
} else {
writer.write(&*tr)?;
}
let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?;
// Dismiss the completion of transaction with a failure. No failure paths are allowed from
// here on out.
send_failed_reply.dismiss();
// Commit files, and set FDs in FDA to be closed on buffer free.
let close_on_free = files.commit();
alloc.set_info_close_on_free(close_on_free);
// It is now the user's responsibility to clear the allocation.
alloc.keep_alive();
self.drop_outstanding_txn();
// When this is not a reply and not a oneway transaction, update `current_transaction`. If
// it's a reply, `current_transaction` has already been updated appropriately.
if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 {
thread.set_current_transaction(self);
}
Ok(false)
}
fn cancel(self: DArc<Self>) {
drop(self.allocation.lock().take());
// If this is not a reply or oneway transaction, then send a dead reply.
if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
let reply = Err(BR_DEAD_REPLY);
self.from.deliver_reply(reply, &self);
}
self.drop_outstanding_txn();
}
fn on_thread_selected(&self, to_thread: &Thread) {
// Return immediately if reply.
let target_node = match self.target_node.as_ref() {
Some(target_node) => target_node,
None => return,
};
// We only need to do this once.
if self.set_priority_called.swap(true, Ordering::Relaxed) {
return;
}
let node_prio = target_node.node_prio();
let mut desired = self.priority;
if !target_node.inherit_rt() && prio::is_rt_policy(desired.sched_policy) {
desired.prio = prio::DEFAULT_PRIO;
desired.sched_policy = prio::SCHED_NORMAL;
}
if node_prio.prio < self.priority.prio
|| (node_prio.prio == self.priority.prio && node_prio.sched_policy == prio::SCHED_FIFO)
{
// In case the minimum priority on the node is
// higher (lower value), use that priority. If
// the priority is the same, but the node uses
// SCHED_FIFO, prefer SCHED_FIFO, since it can
// run unbounded, unlike SCHED_RR.
desired = node_prio;
}
let mut prio_state = to_thread.prio_lock.lock();
if prio_state.state == PriorityState::Pending {
// Task is in the process of changing priorities
// saving its current values would be incorrect.
// Instead, save the pending priority and signal
// the task to abort the priority restore.
prio_state.state = PriorityState::Abort;
*self.saved_priority.lock() = prio_state.next;
} else {
let task = &*self.to.task;
let mut saved_priority = self.saved_priority.lock();
saved_priority.sched_policy = task.policy();
saved_priority.prio = task.normal_prio();
}
drop(prio_state);
to_thread.set_priority(&desired);
}
fn should_sync_wakeup(&self) -> bool {
self.flags & TF_ONE_WAY == 0
}
fn debug_print(&self, m: &mut SeqFile, _prefix: &str, tprefix: &str) -> Result<()> {
self.debug_print_inner(m, tprefix);
Ok(())
}
}
#[pinned_drop]
impl PinnedDrop for Transaction {
fn drop(self: Pin<&mut Self>) {
self.drop_outstanding_txn();
}
}