| // 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); | |
| } | |
| } | |
| } |