mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
This adds dynamic debug logs for: - Releasing active transactions during thread stack unwinding. - Discarded transaction error codes when a thread exits. - Undelivered transaction acknowledgments (TRANSACTION_COMPLETE) upon thread exit. - Undelivered process death and freeze notifications when processes exit or die. - Undelivered transactions canceled due to target process death. We now store the process PID in `ThreadError`, `DeliverCode`, and `FreezeMessage` to ensure the correct PID is logged on cancellation. This is necessary because `cancel()` runs from background `kworkers`, which would otherwise print the wrong PID. Reviewed-by: Alice Ryhl <aliceryhl@google.com> Reviewed-by: Carlos Llamas <cmllamas@google.com> Signed-off-by: Jahnavi MN <jahnavimn@google.com> Link: https://patch.msgid.link/20260716-rust_binder_debug_mask-v4-7-3d7436c2d2f2@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
457 lines
16 KiB
Rust
457 lines
16 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
// Copyright (C) 2025 Google LLC.
|
|
|
|
use kernel::{
|
|
alloc::AllocError,
|
|
list::ListArc,
|
|
prelude::*,
|
|
rbtree::{self, RBTreeNodeReservation},
|
|
seq_file::SeqFile,
|
|
seq_print,
|
|
sync::{Arc, UniqueArc},
|
|
uaccess::UserSliceReader,
|
|
};
|
|
|
|
use crate::{
|
|
defs::*, node::Node, process::Process, thread::Thread, BinderReturnWriter, DArc, DLArc,
|
|
DTRWrap, DeliverToRead,
|
|
};
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub(crate) struct FreezeCookie(u64);
|
|
|
|
/// Represents a listener for changes to the frozen state of a process.
|
|
pub(crate) struct FreezeListener {
|
|
/// The node we are listening for.
|
|
pub(crate) node: DArc<Node>,
|
|
/// The cookie of this freeze listener.
|
|
cookie: FreezeCookie,
|
|
/// What value of `is_frozen` did we most recently tell userspace about?
|
|
last_is_frozen: Option<bool>,
|
|
/// We sent a `BR_FROZEN_BINDER` and we are waiting for `BC_FREEZE_NOTIFICATION_DONE` before
|
|
/// sending any other commands.
|
|
is_pending: bool,
|
|
/// Userspace sent `BC_CLEAR_FREEZE_NOTIFICATION` and we need to reply with
|
|
/// `BR_CLEAR_FREEZE_NOTIFICATION_DONE` as soon as possible. If `is_pending` is set, then we
|
|
/// must wait for it to be unset before we can reply.
|
|
is_clearing: bool,
|
|
/// Number of cleared duplicates that can't be deleted until userspace sends
|
|
/// `BC_FREEZE_NOTIFICATION_DONE`.
|
|
num_pending_duplicates: u64,
|
|
/// Number of cleared duplicates that can be deleted.
|
|
num_cleared_duplicates: u64,
|
|
}
|
|
|
|
impl FreezeListener {
|
|
/// Is it okay to create a new listener with the same cookie as this one for the provided node?
|
|
///
|
|
/// Under some scenarios, userspace may delete a freeze listener and immediately recreate it
|
|
/// with the same cookie. This results in duplicate listeners. To avoid issues with ambiguity,
|
|
/// we allow this only if the new listener is for the same node, and we also require that the
|
|
/// old listener has already been cleared.
|
|
fn allow_duplicate(&self, node: &DArc<Node>) -> bool {
|
|
Arc::ptr_eq(&self.node, node) && self.is_clearing
|
|
}
|
|
}
|
|
|
|
type UninitFM = UniqueArc<core::mem::MaybeUninit<DTRWrap<FreezeMessage>>>;
|
|
|
|
/// Represents a notification that the freeze state has changed.
|
|
pub(crate) struct FreezeMessage {
|
|
cookie: FreezeCookie,
|
|
pid: i32,
|
|
}
|
|
|
|
kernel::list::impl_list_arc_safe! {
|
|
impl ListArcSafe<0> for FreezeMessage {
|
|
untracked;
|
|
}
|
|
}
|
|
|
|
impl FreezeMessage {
|
|
fn new(flags: kernel::alloc::Flags) -> Result<UninitFM, AllocError> {
|
|
UniqueArc::new_uninit(flags)
|
|
}
|
|
|
|
fn init(ua: UninitFM, cookie: FreezeCookie, pid: i32) -> DLArc<FreezeMessage> {
|
|
match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid })) {
|
|
Ok(msg) => ListArc::from(msg),
|
|
Err(err) => match err {},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DeliverToRead for FreezeMessage {
|
|
fn do_work(
|
|
self: DArc<Self>,
|
|
thread: &Thread,
|
|
writer: &mut BinderReturnWriter<'_>,
|
|
) -> Result<bool> {
|
|
let _removed_listener;
|
|
let mut node_refs = thread.process.node_refs.lock();
|
|
let Some(mut freeze_entry) = node_refs.freeze_listeners.find_mut(&self.cookie) else {
|
|
return Ok(true);
|
|
};
|
|
let freeze = freeze_entry.get_mut();
|
|
|
|
if freeze.num_cleared_duplicates > 0 {
|
|
freeze.num_cleared_duplicates -= 1;
|
|
drop(node_refs);
|
|
writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?;
|
|
writer.write_payload(&self.cookie.0)?;
|
|
return Ok(true);
|
|
}
|
|
|
|
if freeze.is_pending {
|
|
return Ok(true);
|
|
}
|
|
if freeze.is_clearing {
|
|
kernel::warn_on!(freeze.num_cleared_duplicates != 0);
|
|
if freeze.num_pending_duplicates > 0 {
|
|
// The primary freeze listener was deleted, so convert a pending duplicate back
|
|
// into the primary one.
|
|
freeze.num_pending_duplicates -= 1;
|
|
freeze.is_pending = true;
|
|
freeze.is_clearing = true;
|
|
} else {
|
|
_removed_listener = freeze_entry.remove_node();
|
|
}
|
|
drop(node_refs);
|
|
writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?;
|
|
writer.write_payload(&self.cookie.0)?;
|
|
Ok(true)
|
|
} else {
|
|
let is_frozen = freeze.node.owner.inner.lock().is_frozen.is_fully_frozen();
|
|
if freeze.last_is_frozen == Some(is_frozen) {
|
|
return Ok(true);
|
|
}
|
|
|
|
let mut state_info = BinderFrozenStateInfo::default();
|
|
state_info.is_frozen = u32::from(is_frozen);
|
|
state_info.cookie = freeze.cookie.0;
|
|
freeze.is_pending = true;
|
|
freeze.last_is_frozen = Some(is_frozen);
|
|
drop(node_refs);
|
|
|
|
writer.write_code(BR_FROZEN_BINDER)?;
|
|
writer.write_payload(&state_info)?;
|
|
// BR_FROZEN_BINDER notifications can cause transactions
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
fn cancel(self: DArc<Self>) {
|
|
binder_debug!(
|
|
pid = self.pid,
|
|
DeadTransaction,
|
|
"undelivered freeze notification, {:016x}",
|
|
self.cookie.0
|
|
);
|
|
}
|
|
|
|
fn should_sync_wakeup(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
|
|
seq_print!(m, "{}has frozen binder\n", prefix);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl FreezeListener {
|
|
/// Called when this freeze listener is cleared abnormally.
|
|
///
|
|
/// This occurs either because the process exited or because the process dropped its last
|
|
/// refcount on the node ref without explicitly removing the freeze listener first.
|
|
///
|
|
/// The returned `KVVec` is just a value that should be dropped outside of the lock.
|
|
pub(crate) fn on_process_cleanup(&self, proc: &Process) -> KVVec<Arc<Process>> {
|
|
if !self.is_clearing {
|
|
return self.node.remove_freeze_listener(proc);
|
|
}
|
|
KVVec::new()
|
|
}
|
|
}
|
|
|
|
impl Process {
|
|
pub(crate) fn request_freeze_notif(
|
|
self: &Arc<Self>,
|
|
reader: &mut UserSliceReader,
|
|
) -> Result<()> {
|
|
let hc = reader.read::<BinderHandleCookie>()?;
|
|
let handle = hc.handle;
|
|
let cookie = FreezeCookie(hc.cookie);
|
|
|
|
let msg = FreezeMessage::new(GFP_KERNEL)?;
|
|
let alloc = RBTreeNodeReservation::new(GFP_KERNEL)?;
|
|
|
|
let mut afl_vec_alloc = KVVec::new();
|
|
let mut info;
|
|
let mut freeze_entry;
|
|
let mut node_refs_guard = self.node_refs.lock();
|
|
loop {
|
|
let node_refs = &mut *node_refs_guard;
|
|
info = match node_refs.by_handle.get_mut(&handle) {
|
|
Some(info) => info,
|
|
None => {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_REQUEST_FREEZE_NOTIFICATION invalid ref {handle}"
|
|
);
|
|
return Err(EINVAL);
|
|
}
|
|
};
|
|
if info.freeze().is_some() {
|
|
binder_debug!(UserError, "BC_REQUEST_FREEZE_NOTIFICATION already set");
|
|
return Err(EINVAL);
|
|
}
|
|
let node_ref = info.node_ref();
|
|
freeze_entry = node_refs.freeze_listeners.entry(cookie);
|
|
|
|
if let rbtree::Entry::Occupied(ref dupe) = freeze_entry {
|
|
if !dupe.get().allow_duplicate(&node_ref.node) {
|
|
binder_debug!(UserError, "BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie");
|
|
return Err(EINVAL);
|
|
}
|
|
}
|
|
|
|
// Now we add to the node's freeze listener list, with retry and re-allocate if the
|
|
// vector is full.
|
|
//
|
|
// To ensure that the node is added atomically, this is the first time we modify any
|
|
// state. When this call succeeds, all other modifications must occur without the
|
|
// possibility for any failure paths.
|
|
match node_ref
|
|
.node
|
|
.add_freeze_listener(self, &mut afl_vec_alloc)?
|
|
{
|
|
Ok(()) => break,
|
|
Err(resize_target) => {
|
|
drop(node_refs_guard);
|
|
afl_vec_alloc = KVVec::with_capacity(resize_target, GFP_KERNEL)?;
|
|
node_refs_guard = self.node_refs.lock();
|
|
}
|
|
}
|
|
}
|
|
|
|
match freeze_entry {
|
|
rbtree::Entry::Vacant(entry) => {
|
|
entry.insert(
|
|
FreezeListener {
|
|
cookie,
|
|
node: info.node_ref().node.clone(),
|
|
last_is_frozen: None,
|
|
is_pending: false,
|
|
is_clearing: false,
|
|
num_pending_duplicates: 0,
|
|
num_cleared_duplicates: 0,
|
|
},
|
|
alloc,
|
|
);
|
|
}
|
|
rbtree::Entry::Occupied(mut dupe) => {
|
|
let dupe = dupe.get_mut();
|
|
if dupe.is_pending {
|
|
dupe.num_pending_duplicates += 1;
|
|
} else {
|
|
dupe.num_cleared_duplicates += 1;
|
|
}
|
|
dupe.last_is_frozen = None;
|
|
dupe.is_pending = false;
|
|
dupe.is_clearing = false;
|
|
}
|
|
}
|
|
|
|
*info.freeze() = Some(cookie);
|
|
let msg = FreezeMessage::init(msg, cookie, self.task.pid());
|
|
drop(node_refs_guard);
|
|
let _ = self.push_work(msg);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> {
|
|
let cookie = FreezeCookie(reader.read()?);
|
|
let alloc = FreezeMessage::new(GFP_KERNEL)?;
|
|
let mut node_refs_guard = self.node_refs.lock();
|
|
let node_refs = &mut *node_refs_guard;
|
|
let Some(freeze) = node_refs.freeze_listeners.get_mut(&cookie) else {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_FREEZE_NOTIFICATION_DONE {:016x} not found",
|
|
cookie.0
|
|
);
|
|
return Err(EINVAL);
|
|
};
|
|
let mut clear_msg = None;
|
|
if freeze.num_pending_duplicates > 0 {
|
|
clear_msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid()));
|
|
freeze.num_pending_duplicates -= 1;
|
|
freeze.num_cleared_duplicates += 1;
|
|
} else {
|
|
if !freeze.is_pending {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_FREEZE_NOTIFICATION_DONE {:016x} not pending",
|
|
cookie.0
|
|
);
|
|
return Err(EINVAL);
|
|
}
|
|
let is_frozen = freeze.node.owner.inner.lock().is_frozen.is_fully_frozen();
|
|
if freeze.is_clearing || freeze.last_is_frozen != Some(is_frozen) {
|
|
// Immediately send another FreezeMessage.
|
|
clear_msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid()));
|
|
}
|
|
freeze.is_pending = false;
|
|
}
|
|
drop(node_refs_guard);
|
|
if let Some(clear_msg) = clear_msg {
|
|
let _ = self.push_work(clear_msg);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> {
|
|
let hc = reader.read::<BinderHandleCookie>()?;
|
|
let handle = hc.handle;
|
|
let cookie = FreezeCookie(hc.cookie);
|
|
|
|
let _to_free_fl;
|
|
let alloc = FreezeMessage::new(GFP_KERNEL)?;
|
|
let mut node_refs_guard = self.node_refs.lock();
|
|
let node_refs = &mut *node_refs_guard;
|
|
let Some(info) = node_refs.by_handle.get_mut(&handle) else {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_CLEAR_FREEZE_NOTIFICATION invalid ref {handle}"
|
|
);
|
|
return Err(EINVAL);
|
|
};
|
|
let Some(info_cookie) = info.freeze() else {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active"
|
|
);
|
|
return Err(EINVAL);
|
|
};
|
|
if *info_cookie != cookie {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch"
|
|
);
|
|
return Err(EINVAL);
|
|
}
|
|
let Some(listener) = node_refs.freeze_listeners.get_mut(&cookie) else {
|
|
binder_debug!(
|
|
UserError,
|
|
"BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {handle}"
|
|
);
|
|
return Err(EINVAL);
|
|
};
|
|
listener.is_clearing = true;
|
|
_to_free_fl = listener.node.remove_freeze_listener(self);
|
|
*info.freeze() = None;
|
|
let mut msg = None;
|
|
if !listener.is_pending {
|
|
msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid()));
|
|
}
|
|
drop(node_refs_guard);
|
|
|
|
if let Some(msg) = msg {
|
|
let _ = self.push_work(msg);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_freeze_cookie(&self, node: &DArc<Node>) -> Option<FreezeCookie> {
|
|
let node_refs = &mut *self.node_refs.lock();
|
|
let handle = node_refs.by_node.get(&node.global_id())?;
|
|
let node_ref = node_refs.by_handle.get_mut(handle)?;
|
|
*node_ref.freeze()
|
|
}
|
|
|
|
/// Creates a vector of every freeze listener on this process.
|
|
///
|
|
/// Returns pairs of the remote process listening for notifications and the local node it is
|
|
/// listening on.
|
|
#[expect(clippy::type_complexity)]
|
|
fn find_freeze_recipients(&self) -> Result<KVVec<(DArc<Node>, Arc<Process>)>, AllocError> {
|
|
// Defined before `inner` to drop after releasing spinlock if `push_within_capacity` fails.
|
|
let mut node_proc_pair;
|
|
|
|
// We pre-allocate space for up to 8 recipients before we take the spinlock. However, if
|
|
// the allocation fails, use a vector with a capacity of zero instead of failing. After
|
|
// all, there might not be any freeze listeners, in which case this operation could still
|
|
// succeed.
|
|
let mut recipients =
|
|
KVVec::with_capacity(8, GFP_KERNEL).unwrap_or_else(|_err| KVVec::new());
|
|
|
|
let mut inner = self.lock_with_nodes();
|
|
let mut curr = inner.nodes.cursor_front_mut();
|
|
while let Some(cursor) = curr {
|
|
let (key, node) = cursor.current();
|
|
let key = *key;
|
|
let list = node.freeze_list(&inner.inner);
|
|
let len = list.len();
|
|
|
|
if recipients.spare_capacity_mut().len() < len {
|
|
drop(inner);
|
|
recipients.reserve(len, GFP_KERNEL)?;
|
|
inner = self.lock_with_nodes();
|
|
// Find the node we were looking at and try again. If the set of nodes was changed,
|
|
// then just proceed to the next node. This is ok because we don't guarantee the
|
|
// inclusion of nodes that are added or removed in parallel with this operation.
|
|
curr = inner.nodes.cursor_lower_bound_mut(&key);
|
|
continue;
|
|
}
|
|
|
|
for proc in list {
|
|
node_proc_pair = (node.clone(), proc.clone());
|
|
recipients
|
|
.push_within_capacity(node_proc_pair)
|
|
.map_err(|_| {
|
|
pr_err!(
|
|
"push_within_capacity failed even though we checked the capacity\n"
|
|
);
|
|
AllocError
|
|
})?;
|
|
}
|
|
|
|
curr = cursor.move_next();
|
|
}
|
|
Ok(recipients)
|
|
}
|
|
|
|
/// Prepare allocations for sending freeze messages.
|
|
pub(crate) fn prepare_freeze_messages(&self) -> Result<FreezeMessages, AllocError> {
|
|
let recipients = self.find_freeze_recipients()?;
|
|
let mut batch = KVVec::with_capacity(recipients.len(), GFP_KERNEL)?;
|
|
for (node, proc) in recipients {
|
|
let Some(cookie) = proc.get_freeze_cookie(&node) else {
|
|
// If the freeze listener was removed in the meantime, just discard the
|
|
// notification.
|
|
continue;
|
|
};
|
|
let msg_alloc = FreezeMessage::new(GFP_KERNEL)?;
|
|
let msg = FreezeMessage::init(msg_alloc, cookie, proc.task.pid());
|
|
batch.push((proc, msg), GFP_KERNEL)?;
|
|
}
|
|
|
|
Ok(FreezeMessages { batch })
|
|
}
|
|
}
|
|
|
|
pub(crate) struct FreezeMessages {
|
|
batch: KVVec<(Arc<Process>, DLArc<FreezeMessage>)>,
|
|
}
|
|
|
|
impl FreezeMessages {
|
|
pub(crate) fn send_messages(self) {
|
|
for (proc, msg) in self.batch {
|
|
let _ = proc.push_work(msg);
|
|
}
|
|
}
|
|
}
|