@@ -69,6 +69,18 @@ pub(crate) struct ContextList {
list: List<Context>,
}
+pub(crate) fn get_all_contexts() -> Result<Vec<Arc<Context>>> {
+ let lock = CONTEXTS.lock();
+
+ let count = lock.list.iter().count();
+
+ let mut ctxs = Vec::try_with_capacity(count)?;
+ for ctx in &lock.list {
+ ctxs.try_push(Arc::from(ctx))?;
+ }
+ Ok(ctxs)
+}
+
/// This struct keeps track of the processes using this context, and which process is the context
/// manager.
struct Manager {
@@ -183,4 +195,31 @@ pub(crate) fn get_manager_node(&self, strong: bool) -> Result<NodeRef, BinderErr
.clone(strong)
.map_err(BinderError::from)
}
+
+ pub(crate) fn for_each_proc<F>(&self, mut func: F)
+ where
+ F: FnMut(&Process),
+ {
+ let lock = self.manager.lock();
+ for proc in &lock.all_procs {
+ func(&proc);
+ }
+ }
+
+ pub(crate) fn get_all_procs(&self) -> Result<Vec<Arc<Process>>> {
+ let lock = self.manager.lock();
+ let count = lock.all_procs.iter().count();
+
+ let mut procs = Vec::try_with_capacity(count)?;
+ for proc in &lock.all_procs {
+ procs.try_push(Arc::from(proc))?;
+ }
+ Ok(procs)
+ }
+
+ pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result<Vec<Arc<Process>>> {
+ let mut procs = self.get_all_procs()?;
+ procs.retain(|proc| proc.task.pid() == pid);
+ Ok(procs)
+ }
}
@@ -20,6 +20,7 @@ macro_rules! pub_no_prefix {
BR_REPLY,
BR_DEAD_REPLY,
BR_FAILED_REPLY,
+ BR_FROZEN_REPLY,
BR_NOOP,
BR_SPAWN_LOOPER,
BR_TRANSACTION_COMPLETE,
@@ -120,6 +121,8 @@ fn default() -> Self {
);
decl_wrapper!(BinderWriteRead, bindings::binder_write_read);
decl_wrapper!(BinderVersion, bindings::binder_version);
+decl_wrapper!(BinderFrozenStatusInfo, bindings::binder_frozen_status_info);
+decl_wrapper!(BinderFreezeInfo, bindings::binder_freeze_info);
decl_wrapper!(ExtendedError, bindings::binder_extended_error);
impl BinderVersion {
@@ -21,6 +21,13 @@ pub(crate) fn new_dead() -> Self {
}
}
+ pub(crate) fn new_frozen() -> Self {
+ Self {
+ reply: BR_FROZEN_REPLY,
+ source: None,
+ }
+ }
+
pub(crate) fn is_dead(&self) -> bool {
self.reply == BR_DEAD_REPLY
}
@@ -76,6 +83,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
None => f.pad("BR_FAILED_REPLY"),
},
BR_DEAD_REPLY => f.pad("BR_DEAD_REPLY"),
+ BR_FROZEN_REPLY => f.pad("BR_FROZEN_REPLY"),
BR_TRANSACTION_COMPLETE => f.pad("BR_TRANSACTION_COMPLETE"),
_ => f
.debug_struct("BinderError")
@@ -20,7 +20,9 @@
pages::Pages,
prelude::*,
rbtree::RBTree,
- sync::{lock::Guard, Arc, ArcBorrow, Mutex, SpinLock, UniqueArc},
+ sync::{
+ lock::Guard, Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc,
+ },
task::Task,
types::{ARef, Either},
user_ptr::{UserSlicePtr, UserSlicePtrReader},
@@ -80,6 +82,16 @@ pub(crate) struct ProcessInner {
/// Bitmap of deferred work to do.
defer_work: u8,
+
+ /// Number of transactions to be transmitted before processes in freeze_wait
+ /// are woken up.
+ outstanding_txns: u32,
+ /// Process is frozen and unable to service binder transactions.
+ pub(crate) is_frozen: bool,
+ /// Process received sync transactions since last frozen.
+ pub(crate) sync_recv: bool,
+ /// Process received async transactions since last frozen.
+ pub(crate) async_recv: bool,
}
impl ProcessInner {
@@ -97,6 +109,10 @@ fn new() -> Self {
max_threads: 0,
started_thread_count: 0,
defer_work: 0,
+ outstanding_txns: 0,
+ is_frozen: false,
+ sync_recv: false,
+ async_recv: false,
}
}
@@ -248,6 +264,22 @@ pub(crate) fn death_delivered(&mut self, death: DArc<NodeDeath>) {
pr_warn!("Notification added to `delivered_deaths` twice.");
}
}
+
+ pub(crate) fn add_outstanding_txn(&mut self) {
+ self.outstanding_txns += 1;
+ }
+
+ fn txns_pending_locked(&self) -> bool {
+ if self.outstanding_txns > 0 {
+ return true;
+ }
+ for thread in self.threads.values() {
+ if thread.has_current_transaction() {
+ return true;
+ }
+ }
+ false
+ }
}
struct NodeRefInfo {
@@ -296,6 +328,11 @@ pub(crate) struct Process {
#[pin]
pub(crate) inner: SpinLock<ProcessInner>,
+ // Waitqueue of processes waiting for all outstanding transactions to be
+ // processed.
+ #[pin]
+ freeze_wait: CondVar,
+
// Node references are in a different lock to avoid recursive acquisition when
// incrementing/decrementing a node in another process.
#[pin]
@@ -353,6 +390,7 @@ fn new(ctx: Arc<Context>, cred: ARef<Credential>) -> Result<Arc<Self>> {
cred,
inner <- kernel::new_spinlock!(ProcessInner::new(), "Process::inner"),
node_refs <- kernel::new_mutex!(ProcessNodeRefs::new(), "Process::node_refs"),
+ freeze_wait <- kernel::new_condvar!("Process::freeze_wait"),
task: kernel::current!().group_leader().into(),
defer_work <- kernel::new_work!("Process::defer_work"),
links <- ListLinks::new(),
@@ -878,6 +916,9 @@ fn deferred_release(self: Arc<Self>) {
let is_manager = {
let mut inner = self.inner.lock();
inner.is_dead = true;
+ inner.is_frozen = false;
+ inner.sync_recv = false;
+ inner.async_recv = false;
inner.is_manager
};
@@ -975,6 +1016,116 @@ pub(crate) fn flush(this: ArcBorrow<'_, Process>) -> Result {
}
Ok(())
}
+
+ pub(crate) fn drop_outstanding_txn(&self) {
+ let wake = {
+ let mut inner = self.inner.lock();
+ if inner.outstanding_txns == 0 {
+ pr_err!("outstanding_txns underflow");
+ return;
+ }
+ inner.outstanding_txns -= 1;
+ inner.is_frozen && inner.outstanding_txns == 0
+ };
+
+ if wake {
+ self.freeze_wait.notify_all();
+ }
+ }
+
+ pub(crate) fn ioctl_freeze(&self, info: &BinderFreezeInfo) -> Result {
+ if info.enable != 0 {
+ let mut inner = self.inner.lock();
+ inner.sync_recv = false;
+ inner.async_recv = false;
+ inner.is_frozen = false;
+ return Ok(());
+ }
+
+ let mut inner = self.inner.lock();
+ inner.sync_recv = false;
+ inner.async_recv = false;
+ inner.is_frozen = true;
+
+ if info.timeout_ms > 0 {
+ // Safety: Just an FFI call.
+ let mut jiffies = unsafe { bindings::__msecs_to_jiffies(info.timeout_ms) };
+ while jiffies > 0 {
+ if inner.outstanding_txns == 0 {
+ break;
+ }
+
+ match self.freeze_wait.wait_timeout(&mut inner, jiffies) {
+ CondVarTimeoutResult::Signal { .. } => {
+ inner.is_frozen = false;
+ return Err(ERESTARTSYS);
+ }
+ CondVarTimeoutResult::Woken { jiffies: remaining } => {
+ jiffies = remaining;
+ }
+ CondVarTimeoutResult::Timeout => {
+ jiffies = 0;
+ }
+ }
+ }
+ }
+
+ if inner.txns_pending_locked() {
+ inner.is_frozen = false;
+ Err(EAGAIN)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+fn get_frozen_status(data: UserSlicePtr) -> Result {
+ let (mut reader, mut writer) = data.reader_writer();
+
+ let mut info = reader.read::<BinderFrozenStatusInfo>()?;
+ info.sync_recv = 0;
+ info.async_recv = 0;
+ let mut found = false;
+
+ for ctx in crate::context::get_all_contexts()? {
+ ctx.for_each_proc(|proc| {
+ if proc.task.pid() == info.pid as _ {
+ found = true;
+ let inner = proc.inner.lock();
+ let txns_pending = inner.txns_pending_locked();
+ info.async_recv |= inner.async_recv as u32;
+ info.sync_recv |= inner.sync_recv as u32;
+ info.sync_recv |= (txns_pending as u32) << 1;
+ }
+ });
+ }
+
+ if found {
+ writer.write(&info)?;
+ Ok(())
+ } else {
+ Err(EINVAL)
+ }
+}
+
+fn ioctl_freeze(reader: &mut UserSlicePtrReader) -> Result {
+ let info = reader.read::<BinderFreezeInfo>()?;
+
+ // Very unlikely for there to be more than 3, since a process normally uses at most binder and
+ // hwbinder.
+ let mut procs = Vec::try_with_capacity(3)?;
+
+ let ctxs = crate::context::get_all_contexts()?;
+ for ctx in ctxs {
+ for proc in ctx.get_procs_with_pid(info.pid as i32)? {
+ procs.try_push(proc)?;
+ }
+ }
+
+ for proc in procs {
+ proc.ioctl_freeze(&info)?;
+ }
+ Ok(())
}
/// The ioctl handler.
@@ -993,6 +1144,7 @@ fn write(
bindings::BINDER_SET_CONTEXT_MGR_EXT => {
this.set_as_manager(Some(reader.read()?), &thread)?
}
+ bindings::BINDER_FREEZE => ioctl_freeze(reader)?,
_ => return Err(EINVAL),
}
Ok(0)
@@ -1011,6 +1163,7 @@ fn read_write(
bindings::BINDER_GET_NODE_DEBUG_INFO => this.get_node_debug_info(data)?,
bindings::BINDER_GET_NODE_INFO_FOR_REF => this.get_node_info_from_ref(data)?,
bindings::BINDER_VERSION => this.version(data)?,
+ bindings::BINDER_GET_FROZEN_INFO => get_frozen_status(data)?,
bindings::BINDER_GET_EXTENDED_ERROR => thread.get_extended_error(data)?,
_ => return Err(EINVAL),
}
@@ -458,6 +458,10 @@ pub(crate) fn set_current_transaction(&self, transaction: DArc<Transaction>) {
self.inner.lock().current_transaction = Some(transaction);
}
+ pub(crate) fn has_current_transaction(&self) -> bool {
+ self.inner.lock().current_transaction.is_some()
+ }
+
/// Attempts to fetch a work item from the thread-local queue. The behaviour if the queue is
/// empty depends on `wait`: if it is true, the function waits for some work to be queued (or a
/// signal); otherwise it returns indicating that none is available.
@@ -482,7 +486,7 @@ fn get_work_local(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn Deliv
}
inner.looper_flags |= LOOPER_WAITING;
- let signal_pending = self.work_condvar.wait(&mut inner);
+ let signal_pending = self.work_condvar.wait_freezable(&mut inner);
inner.looper_flags &= !LOOPER_WAITING;
if signal_pending {
@@ -533,7 +537,7 @@ fn get_work(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRe
}
inner.looper_flags |= LOOPER_WAITING | LOOPER_WAITING_PROC;
- let signal_pending = self.work_condvar.wait(&mut inner);
+ let signal_pending = self.work_condvar.wait_freezable(&mut inner);
inner.looper_flags &= !(LOOPER_WAITING | LOOPER_WAITING_PROC);
if signal_pending || inner.looper_need_return {
@@ -1043,6 +1047,10 @@ fn deliver_single_reply(
reply: Either<DLArc<Transaction>, u32>,
transaction: &DArc<Transaction>,
) -> bool {
+ if let Either::Left(transaction) = &reply {
+ transaction.set_outstanding(&mut self.process.inner.lock());
+ }
+
{
let mut inner = self.inner.lock();
if !inner.pop_transaction_replied(transaction) {
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
+use core::sync::atomic::{AtomicBool, Ordering};
use kernel::{
io_buffer::IoBufferWriter,
list::ListArcSafe,
@@ -15,13 +16,13 @@
defs::*,
error::{BinderError, BinderResult},
node::{Node, NodeRef},
- process::Process,
+ process::{Process, ProcessInner},
ptr_align,
thread::{PushWorkRes, Thread},
DArc, DLArc, DTRWrap, DeliverToRead,
};
-#[pin_data]
+#[pin_data(PinnedDrop)]
pub(crate) struct Transaction {
target_node: Option<DArc<Node>>,
stack_next: Option<DArc<Transaction>>,
@@ -29,6 +30,7 @@ pub(crate) struct Transaction {
to: Arc<Process>,
#[pin]
allocation: SpinLock<Option<Allocation>>,
+ is_outstanding: AtomicBool,
code: u32,
pub(crate) flags: u32,
data_size: usize,
@@ -94,6 +96,7 @@ pub(crate) fn new(
offsets_size: trd.offsets_size as _,
data_address,
allocation <- kernel::new_spinlock!(Some(alloc), "Transaction::new"),
+ is_outstanding: AtomicBool::new(false),
txn_security_ctx_off,
}))?)
}
@@ -127,6 +130,7 @@ pub(crate) fn new_reply(
offsets_size: trd.offsets_size as _,
data_address: alloc.ptr,
allocation <- kernel::new_spinlock!(Some(alloc), "Transaction::new"),
+ is_outstanding: AtomicBool::new(false),
txn_security_ctx_off: None,
}))?)
}
@@ -172,6 +176,26 @@ pub(crate) fn find_from(&self, thread: &Thread) -> Option<DArc<Transaction>> {
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.
///
@@ -181,8 +205,13 @@ pub(crate) fn submit(self: DLArc<Self>) -> BinderResult {
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;
+ }
match target_node.submit_oneway(self, &mut process_inner) {
Ok(()) => return Ok(()),
Err((err, work)) => {
@@ -197,6 +226,11 @@ pub(crate) fn submit(self: DLArc<Self>) -> BinderResult {
}
}
+ 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(()),
@@ -241,6 +275,7 @@ fn do_work(self: DArc<Self>, thread: &Thread, writer: &mut UserSlicePtrWriter) -
let reply = Either::Right(BR_FAILED_REPLY);
self.from.deliver_reply(reply, &self);
}
+ self.drop_outstanding_txn();
});
let files = if let Ok(list) = self.prepare_file_list() {
list
@@ -301,6 +336,8 @@ fn do_work(self: DArc<Self>, thread: &Thread, writer: &mut UserSlicePtrWriter) -
// 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 {
@@ -318,9 +355,18 @@ fn cancel(self: DArc<Self>) {
let reply = Either::Right(BR_DEAD_REPLY);
self.from.deliver_reply(reply, &self);
}
+
+ self.drop_outstanding_txn();
}
fn should_sync_wakeup(&self) -> bool {
self.flags & TF_ONE_WAY == 0
}
}
+
+#[pinned_drop]
+impl PinnedDrop for Transaction {
+ fn drop(self: Pin<&mut Self>) {
+ self.drop_outstanding_txn();
+ }
+}
@@ -191,6 +191,16 @@ pub fn wait<T: ?Sized, B: Backend>(&self, guard: &mut Guard<'_, T, B>) -> bool {
crate::current!().signal_pending()
}
+ /// Releases the lock and waits for a notification in interruptible and freezable mode.
+ #[must_use = "wait returns if a signal is pending, so the caller must check the return value"]
+ pub fn wait_freezable<T: ?Sized, B: Backend>(&self, guard: &mut Guard<'_, T, B>) -> bool {
+ self.wait_internal(
+ bindings::TASK_INTERRUPTIBLE | bindings::TASK_FREEZABLE,
+ guard,
+ );
+ crate::current!().signal_pending()
+ }
+
/// Releases the lock and waits for a notification in uninterruptible mode.
///
/// Similar to [`CondVar::wait`], except that the wait is not interruptible. That is, the