use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fs::File;
use std::os::raw::c_void;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{self, Arc, Mutex, OnceLock, RwLock};
use std::thread;
use std::time::Duration;
use crate::death::{DeathCmd, DeathRegistry};
use crate::freeze::{ClearDone, FreezeCmd, FreezeRegistry};
use crate::{binder::*, error::*, proxy::*, sys::binder, thread_state};
pub(crate) struct HandlePin {
handle: u32,
generation: u64,
counted: OnceLock<CountedPin>,
}
struct CountedPin {
uid: u32,
by_uid: bool,
}
impl HandlePin {
fn new(handle: u32, generation: u64) -> Arc<Self> {
Arc::new(Self {
handle,
generation,
counted: OnceLock::new(),
})
}
fn count_once(&self) {
self.counted.get_or_init(|| {
let uid = thread_state::get_calling_uid();
CountedPin {
uid,
by_uid: crate::proxy_count::on_proxy_create(uid),
}
});
}
#[cfg(test)]
pub(crate) fn synthetic(handle: u32, generation: u64) -> Arc<Self> {
Self::new(handle, generation)
}
pub(crate) fn handle(&self) -> u32 {
self.handle
}
pub(crate) fn generation(&self) -> u64 {
self.generation
}
}
impl Drop for HandlePin {
fn drop(&mut self) {
if let Some(counted) = self.counted.get() {
crate::proxy_count::on_proxy_drop(counted.uid, counted.by_uid);
}
let Some(this) = ProcessState::instance().get() else {
return;
};
{
let mut handle_to_proxy = this
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
expunge_locked(&mut handle_to_proxy, self.handle, self);
}
let count = {
let mut ledger = this.pin_ledger_lock();
ledger.hold(self.handle);
ledger.take_releasable(self.handle)
};
release_pins(self.handle, count);
}
}
enum SlowPathPlan {
CaseA { pin: Arc<HandlePin> },
CaseB { pin: Arc<HandlePin> },
}
enum SlowPathDecision {
Cached(SIBinder),
NeedIpc(SlowPathPlan),
}
enum SlowPathReady {
CaseA {
pin: Arc<HandlePin>,
descriptor: String,
},
CaseB { _pin: Arc<HandlePin> },
}
struct RestoreCallRestriction(CallRestriction);
impl Drop for RestoreCallRestriction {
fn drop(&mut self) {
thread_state::set_call_restriction(self.0);
}
}
#[cfg(test)]
type SlowPathP2TestHook = Box<dyn FnMut(u32)>;
#[cfg(test)]
thread_local! {
static SLOW_PATH_P2_TEST_HOOK: std::cell::RefCell<Option<SlowPathP2TestHook>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(test)]
fn set_slow_path_p2_test_hook(hook: Option<SlowPathP2TestHook>) {
SLOW_PATH_P2_TEST_HOOK.with(|h| *h.borrow_mut() = hook);
}
#[cfg(test)]
fn slow_path_p2_test_hook(handle: u32) {
let hook = SLOW_PATH_P2_TEST_HOOK.with(|h| h.borrow_mut().take());
if let Some(mut hook) = hook {
hook(handle);
SLOW_PATH_P2_TEST_HOOK.with(|h| *h.borrow_mut() = Some(hook));
}
}
fn commit_new_acquired(
handle_to_proxy: &mut HashMap<u32, CacheEntry>,
pin: &Arc<HandlePin>,
descriptor: String,
stability: Stability,
) -> Result<SIBinder> {
let arc = ProxyHandle::new_acquired(pin, descriptor.clone(), stability)?;
pin.count_once();
handle_to_proxy.insert(
pin.handle(),
CacheEntry {
weak: Arc::downgrade(&arc),
pin: Arc::downgrade(pin),
descriptor,
},
);
Ok(SIBinder::from_arc(arc as Arc<dyn IBinder>))
}
pub(crate) struct CacheEntry {
pub(crate) weak: sync::Weak<ProxyHandle>,
pub(crate) pin: sync::Weak<HandlePin>,
pub(crate) descriptor: String,
}
fn expunge_locked(
handle_to_proxy: &mut HashMap<u32, CacheEntry>,
handle: u32,
pin: *const HandlePin,
) -> bool {
let ours = handle_to_proxy
.get(&handle)
.is_some_and(|entry| std::ptr::eq(entry.pin.as_ptr(), pin));
if ours {
handle_to_proxy.remove(&handle);
}
ours
}
#[derive(Default)]
struct PinLedger(HashMap<u32, LedgerSlot>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ClearKind {
Death,
Freeze,
}
#[derive(Default)]
struct LedgerSlot {
clears_in_flight: u32,
freeze_clears_in_flight: u32,
held_pins: u32,
}
impl LedgerSlot {
fn in_flight(&mut self, kind: ClearKind) -> &mut u32 {
match kind {
ClearKind::Death => &mut self.clears_in_flight,
ClearKind::Freeze => &mut self.freeze_clears_in_flight,
}
}
}
impl PinLedger {
fn note_clear(&mut self, handle: u32, kind: ClearKind) {
let count = self.0.entry(handle).or_default().in_flight(kind);
*count = count.saturating_add(1);
}
fn hold(&mut self, handle: u32) {
let slot = self.0.entry(handle).or_default();
slot.held_pins = slot.held_pins.saturating_add(1);
}
fn take_releasable(&mut self, handle: u32) -> u32 {
match self.0.get(&handle) {
Some(slot) if slot.clears_in_flight == 0 && slot.freeze_clears_in_flight == 0 => {
self.0.remove(&handle).map_or(0, |slot| slot.held_pins)
}
_ => 0,
}
}
fn clear_done(&mut self, handle: u32, kind: ClearKind) -> u32 {
match self.0.get_mut(&handle).map(|slot| slot.in_flight(kind)) {
Some(count) if *count > 0 => *count -= 1,
_ => log::warn!("{kind:?} clear done for handle {handle} with no clear in flight"),
}
self.take_releasable(handle)
}
}
fn release_pins(handle: u32, count: u32) {
for _ in 0..count {
if let Err(err) = thread_state::dec_weak_handle(handle) {
log::error!("BC_DECREFS for handle {handle} failed: {err:?}; its binder_ref leaks");
}
}
}
fn features_dir(driver_name: &Path) -> Option<PathBuf> {
let driver = std::fs::canonicalize(driver_name).ok()?;
driver.parent().map(|dir| dir.join("features"))
}
fn driver_feature_enabled(features_dir: Option<&Path>, name: &str) -> bool {
let own = features_dir.map(|dir| dir.join(name));
let aosp = Path::new("/dev/binderfs/features").join(name);
own.into_iter()
.chain(std::iter::once(aosp))
.find_map(|path| std::fs::read_to_string(path).ok())
.is_some_and(|value| value.trim() == "1")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct ProcessFreezeInfo {
pub sync_received: bool,
pub transactions_pending: bool,
pub async_received: bool,
}
pub(crate) struct PublishedNative {
pub(crate) binder_pin: SIBinder,
pub(crate) publish_count: u32,
pub(crate) kernel_refs: u32,
pub(crate) pending_reservations: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CallRestriction {
None,
ErrorIfNotOneway,
FatalIfNotOneway,
}
pub const DEFAULT_MAX_BINDER_THREADS: u32 = 15;
const DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION: u32 = 1;
pub const MAX_BINDER_MMAP_SIZE: usize = 4 * 1024 * 1024;
struct MemoryMap {
ptr: *mut c_void,
size: usize,
}
unsafe impl Sync for MemoryMap {}
unsafe impl Send for MemoryMap {}
pub struct ProcessState {
max_threads: u32,
driver_name: PathBuf,
driver: Arc<File>,
mmap: RwLock<MemoryMap>,
context_manager: RwLock<Option<SIBinder>>,
handle_to_proxy: RwLock<HashMap<u32, CacheEntry>>,
next_generation: AtomicU64,
pin_ledger: Mutex<PinLedger>,
death: Mutex<DeathRegistry>,
freeze: Mutex<FreezeRegistry<sync::Weak<ProxyHandle>>>,
features_dir: Option<PathBuf>,
freeze_notification: OnceLock<bool>,
published_natives: RwLock<HashMap<u64, PublishedNative>>,
next_native_id: AtomicU64,
disable_background_scheduling: AtomicBool,
call_restriction: RwLock<CallRestriction>,
thread_pool_started: AtomicBool,
thread_pool_seq: AtomicUsize,
kernel_started_threads: AtomicUsize,
main_thread_spawned: AtomicUsize,
pub(crate) current_threads: AtomicUsize,
}
impl ProcessState {
fn instance() -> &'static OnceLock<ProcessState> {
static INSTANCE: OnceLock<ProcessState> = OnceLock::new();
&INSTANCE
}
pub fn as_self() -> &'static ProcessState {
Self::instance()
.get()
.expect("ProcessState is not initialized!")
}
pub fn is_initialized() -> bool {
Self::instance().get().is_some()
}
pub fn set_call_restriction(&self, call_restriction: CallRestriction) {
{
let mut self_call_restriction = self
.call_restriction
.write()
.expect("Call restriction lock poisoned");
*self_call_restriction = call_restriction;
}
thread_state::set_call_restriction(call_restriction);
}
pub(crate) fn call_restriction(&self) -> CallRestriction {
*self
.call_restriction
.read()
.expect("Call restriction lock poisoned")
}
fn log_max_threads(max_threads: u32) {
match max_threads {
0 => log::info!(
"binder max threads = 0: the kernel will never ask this process to spawn a \
binder thread, so only threads that call `join_thread_pool` serve incoming \
transactions (AOSP `setThreadPoolMaxThreadCount(0)`, what a service manager \
wants). Pass `DEFAULT_MAX_BINDER_THREADS` for the usual pool."
),
n if n > DEFAULT_MAX_BINDER_THREADS => log::info!(
"binder max threads = {n}, above the default {DEFAULT_MAX_BINDER_THREADS}; \
the pool may grow to {n} kernel-started threads"
),
n => log::info!("binder max threads = {n}"),
}
}
pub(crate) fn normalized_mmap_size(mmap_size: usize) -> Result<usize> {
let page = rustix::param::page_size();
if mmap_size < page || mmap_size > MAX_BINDER_MMAP_SIZE {
log::error!(
"binder mmap size {mmap_size} is outside [{page}, {MAX_BINDER_MMAP_SIZE}]; \
the driver clamps to 4 MB without saying so, which is why a larger \
request is refused here rather than silently shrunk"
);
return Err(StatusCode::BadValue);
}
Ok(mmap_size.next_multiple_of(page))
}
fn inner_init(
driver_name: &str,
max_threads: u32,
mmap_size: usize,
) -> std::result::Result<ProcessState, Box<dyn std::error::Error>> {
Self::log_max_threads(max_threads);
let vm_size = Self::normalized_mmap_size(mmap_size)?;
let driver_name = PathBuf::from(driver_name);
let driver = open_driver(&driver_name, max_threads)?;
let features_dir = features_dir(&driver_name);
let mmap = unsafe {
let vm_start = rustix::mm::mmap(
std::ptr::null_mut(),
vm_size,
rustix::mm::ProtFlags::READ,
rustix::mm::MapFlags::PRIVATE | rustix::mm::MapFlags::NORESERVE,
&driver,
0,
)?;
(vm_start, vm_size)
};
Ok(ProcessState {
max_threads,
driver_name,
driver: driver.into(),
mmap: RwLock::new(MemoryMap {
ptr: mmap.0,
size: mmap.1,
}),
context_manager: RwLock::new(None),
handle_to_proxy: RwLock::new(HashMap::new()),
next_generation: AtomicU64::new(1),
pin_ledger: Mutex::new(PinLedger::default()),
death: Mutex::new(DeathRegistry::default()),
freeze: Mutex::new(FreezeRegistry::default()),
features_dir,
freeze_notification: OnceLock::new(),
published_natives: RwLock::new(HashMap::new()),
next_native_id: AtomicU64::new(1),
disable_background_scheduling: AtomicBool::new(false),
call_restriction: RwLock::new(CallRestriction::None),
thread_pool_started: AtomicBool::new(false),
thread_pool_seq: AtomicUsize::new(1),
kernel_started_threads: AtomicUsize::new(0),
main_thread_spawned: AtomicUsize::new(0),
current_threads: AtomicUsize::new(0),
})
}
pub fn init(
driver_name: &str,
max_threads: u32,
) -> std::result::Result<&'static ProcessState, Box<dyn std::error::Error>> {
Self::init_with_mmap_size(driver_name, max_threads, Self::default_mmap_size())
}
pub fn init_with_mmap_size(
driver_name: &str,
max_threads: u32,
mmap_size: usize,
) -> std::result::Result<&'static ProcessState, Box<dyn std::error::Error>> {
let cell = Self::instance();
if let Some(existing) = cell.get() {
return Ok(existing);
}
let instance = Self::inner_init(driver_name, max_threads, mmap_size)?;
Ok(cell.get_or_init(|| instance))
}
pub fn default_mmap_size() -> usize {
(1024usize * 1024).saturating_sub(rustix::param::page_size() * 2)
}
pub fn init_default() -> std::result::Result<&'static ProcessState, Box<dyn std::error::Error>>
{
Self::init(Self::default_driver_path(), DEFAULT_MAX_BINDER_THREADS)
}
pub(crate) fn default_driver_path() -> &'static str {
if Path::new(crate::DEFAULT_BINDER_PATH).exists() {
crate::DEFAULT_BINDER_PATH
} else {
crate::LEGACY_BINDER_PATH
}
}
pub fn become_context_manager(
&self,
binder: SIBinder,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let mut context_manager = self
.context_manager
.write()
.expect("Context manager lock poisoned");
if context_manager.is_some() {
log::warn!(
"become_context_manager called again; keeping the first registration (no-op)"
);
return Ok(());
}
let mut flags = binder::FLAT_BINDER_FLAG_ACCEPTS_FDS;
if selinux_available() {
flags |= binder::FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
}
let obj = crate::binder_object::FlatBinderObject::new_binder_with_flags(flags).to_uapi();
if binder::set_context_mgr_ext(&self.driver, obj).is_err() {
if let Err(e) = binder::set_context_mgr(&self.driver, 0) {
return Err(format!("Binder ioctl to become context manager failed: {e}").into());
}
}
*context_manager = Some(binder);
Ok(())
}
pub(crate) fn context_manager(&self) -> Option<SIBinder> {
self.context_manager
.read()
.expect("Context manager lock poisoned")
.clone()
}
pub fn context_object(&self) -> Result<SIBinder> {
self.strong_proxy_for_handle(0)
}
pub fn strong_proxy_for_handle(&self, handle: u32) -> Result<SIBinder> {
self.strong_proxy_for_handle_stability(handle, Default::default())
}
pub(crate) fn strong_proxy_for_handle_stability(
&self,
handle: u32,
stability: Stability,
) -> Result<SIBinder> {
if let Some(arc) = self
.handle_to_proxy
.read()
.expect("Handle to proxy lock poisoned")
.get(&handle)
.and_then(|e| e.weak.upgrade())
{
return Ok(SIBinder::from_arc(arc));
}
loop {
let plan = match self.slow_path_p1(handle)? {
SlowPathDecision::Cached(arc) => return Ok(arc),
SlowPathDecision::NeedIpc(plan) => plan,
};
let ready = self.slow_path_p2(handle, plan)?;
if let Some(binder) = self.slow_path_p3(handle, stability, ready)? {
return Ok(binder);
}
}
}
fn slow_path_p1(&self, handle: u32) -> Result<SlowPathDecision> {
let handle_to_proxy = self
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
if let Some(entry) = handle_to_proxy.get(&handle) {
if let Some(arc) = entry.weak.upgrade() {
return Ok(SlowPathDecision::Cached(SIBinder::from_arc(arc)));
}
if let Some(pin) = entry.pin.upgrade() {
return Ok(SlowPathDecision::NeedIpc(SlowPathPlan::CaseB { pin }));
}
}
thread_state::inc_weak_handle(handle)?;
if let Err(err) = thread_state::flush_commands() {
log::warn!(
"BC_INCREFS for handle {handle} failed at flush: {err:?}; \
handle is no longer valid in the kernel"
);
return Err(StatusCode::DeadObject);
}
let generation = self.next_generation.fetch_add(1, Ordering::Relaxed);
Ok(SlowPathDecision::NeedIpc(SlowPathPlan::CaseA {
pin: HandlePin::new(handle, generation),
}))
}
fn slow_path_p2(&self, handle: u32, plan: SlowPathPlan) -> Result<SlowPathReady> {
#[cfg(test)]
slow_path_p2_test_hook(handle);
let _restore = RestoreCallRestriction(thread_state::call_restriction());
thread_state::set_call_restriction(CallRestriction::None);
if handle == 0 && crate::sdk_at_least(30) {
thread_state::ping_binder(handle)?;
}
match plan {
SlowPathPlan::CaseA { pin } => Ok(SlowPathReady::CaseA {
descriptor: thread_state::query_interface(handle)?,
pin,
}),
SlowPathPlan::CaseB { pin } => Ok(SlowPathReady::CaseB { _pin: pin }),
}
}
fn slow_path_p3(
&self,
handle: u32,
stability: Stability,
ready: SlowPathReady,
) -> Result<Option<SIBinder>> {
let _proxy_count_defer = crate::proxy_count::CallbackDeferGuard::new();
let entry_pin: Option<Arc<HandlePin>>;
let mut handle_to_proxy = self
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
let entry = handle_to_proxy.get(&handle);
if let Some(arc) = entry.and_then(|e| e.weak.upgrade()) {
return Ok(Some(SIBinder::from_arc(arc)));
}
let entry_descriptor = entry.map(|e| e.descriptor.clone());
entry_pin = entry.and_then(|e| e.pin.upgrade());
let (pin, descriptor) = match (&ready, &entry_pin, entry_descriptor) {
(_, Some(pin), Some(descriptor)) => (pin, descriptor),
(SlowPathReady::CaseA { pin, descriptor }, _, _) => (pin, descriptor.clone()),
(SlowPathReady::CaseB { .. }, _, _) => return Ok(None),
};
commit_new_acquired(&mut handle_to_proxy, pin, descriptor, stability).map(Some)
}
pub(crate) fn live_proxy_for_pin(pin: &Arc<HandlePin>) -> Option<Arc<ProxyHandle>> {
Self::instance()
.get()?
.handle_to_proxy
.read()
.expect("Handle to proxy lock poisoned")
.get(&pin.handle())
.filter(|entry| std::ptr::eq(entry.pin.as_ptr(), Arc::as_ptr(pin)))
.and_then(|entry| entry.weak.upgrade())
}
#[cfg(test)]
pub(crate) fn cache_generation_for(&self, handle: u32) -> Option<u64> {
let pin = self
.handle_to_proxy
.read()
.expect("Handle to proxy lock poisoned")
.get(&handle)
.and_then(|e| e.pin.upgrade());
pin.map(|pin| pin.generation())
}
pub(crate) fn send_obituary_for_handle(&self, handle: u32) -> Result<()> {
let removed = {
let mut handle_to_proxy = self
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
handle_to_proxy
.remove(&handle)
.map(|entry| entry.weak.upgrade())
};
let existed = removed.is_some();
let arc = removed.flatten();
let who = arc.as_ref().map(|arc| {
let sibinder = SIBinder::from_arc(arc.clone() as Arc<dyn IBinder>);
SIBinder::downgrade(&sibinder)
});
match (arc, who) {
(Some(arc), Some(who)) => arc.send_obituary(&who)?,
_ if existed => {
log::trace!("Object for handle {handle} already destroyed at obituary time");
}
_ => log::trace!("Handle {handle} was not in cache during obituary"),
}
Ok(())
}
pub(crate) fn finish_obituary(&self) -> Result<()> {
thread_state::flush_commands()
}
fn pin_ledger_lock(&self) -> sync::MutexGuard<'_, PinLedger> {
self.pin_ledger.lock().expect("Pin ledger lock poisoned")
}
pub(crate) fn note_death_clear(handle: u32) {
if let Some(this) = Self::instance().get() {
this.pin_ledger_lock().note_clear(handle, ClearKind::Death);
}
}
pub(crate) fn death_clear_done(&self, handle: u32) {
{
let mut registry = self.death_lock();
if registry.clear_done(handle) == ClearDone::Rerequest {
if let Err(err) = thread_state::send_death_request(handle) {
registry.abort_request(handle);
log::error!(
"BC_REQUEST_DEATH_NOTIFICATION for handle {handle} failed: {err:?}; \
its recipients will not hear of its death"
);
}
}
}
let count = self.pin_ledger_lock().clear_done(handle, ClearKind::Death);
release_pins(handle, count);
}
fn death_lock(&self) -> sync::MutexGuard<'_, DeathRegistry> {
self.death.lock().expect("Death registry lock poisoned")
}
pub(crate) fn link_death(&self, handle: u32, owner: usize) -> Result<()> {
let mut registry = self.death_lock();
if registry.link(handle, owner) == Some(DeathCmd::Request) {
thread_state::send_death_request(handle)
.inspect_err(|_| registry.abort_request(handle))?;
}
Ok(())
}
pub(crate) fn unlink_death(&self, handle: u32, owner: usize) -> Result<()> {
let mut registry = self.death_lock();
match registry.unlink(handle, owner) {
Some(DeathCmd::Clear) => thread_state::clear_death_notification(handle),
_ => Ok(()),
}
}
pub(crate) fn note_freeze_clear(handle: u32) {
if let Some(this) = Self::instance().get() {
this.pin_ledger_lock().note_clear(handle, ClearKind::Freeze);
}
}
fn freeze_lock(&self) -> sync::MutexGuard<'_, FreezeRegistry<sync::Weak<ProxyHandle>>> {
self.freeze.lock().expect("Freeze registry lock poisoned")
}
pub(crate) fn freeze_notification_supported(&self) -> bool {
*self.freeze_notification.get_or_init(|| {
driver_feature_enabled(self.features_dir.as_deref(), "freeze_notification")
})
}
pub(crate) fn add_frozen_callback(
&self,
handle: u32,
owner: usize,
proxy: sync::Weak<ProxyHandle>,
callback: sync::Weak<dyn FrozenStateChangeCallback>,
) -> Result<()> {
{
let mut registry = self.freeze_lock();
if registry.add(handle, owner, proxy, callback) == Some(FreezeCmd::Request) {
self.request_freeze_locked(&mut registry, handle)?;
}
}
self.deliver_frozen(handle);
Ok(())
}
fn request_freeze_locked(
&self,
registry: &mut FreezeRegistry<sync::Weak<ProxyHandle>>,
handle: u32,
) -> Result<()> {
thread_state::send_freeze_request(handle).inspect_err(|_| registry.abort_request(handle))
}
pub(crate) fn remove_frozen_callback(
&self,
handle: u32,
owner: usize,
callback: &sync::Weak<dyn FrozenStateChangeCallback>,
) -> Result<()> {
let mut registry = self.freeze_lock();
if registry.remove(handle, owner, callback)? == Some(FreezeCmd::Clear) {
Self::write_freeze_clear(handle);
if let Err(err) = thread_state::flush_commands_if_alive() {
log::error!(
"BC_CLEAR_FREEZE_NOTIFICATION for handle {handle} not flushed: {err:?}"
);
}
}
Ok(())
}
pub(crate) fn drop_frozen_owner(&self, handle: u32, owner: usize) {
if self.freeze_lock().drop_owner(handle, owner) == Some(FreezeCmd::Clear) {
Self::write_freeze_clear(handle);
}
}
fn write_freeze_clear(handle: u32) {
if let Err(err) = thread_state::clear_freeze_notification(handle) {
log::error!("BC_CLEAR_FREEZE_NOTIFICATION for handle {handle} failed: {err:?}");
}
}
pub(crate) fn frozen_state_changed(&self, handle: u32, is_frozen: bool) {
if self.freeze_lock().state_changed(handle, is_frozen) {
self.deliver_frozen(handle);
}
}
pub(crate) fn freeze_clear_done(&self, handle: u32) {
let outcome = {
let mut registry = self.freeze_lock();
let outcome = registry.clear_done(handle);
if outcome == ClearDone::Rerequest {
if let Err(err) = self.request_freeze_locked(&mut registry, handle) {
log::error!(
"BC_REQUEST_FREEZE_NOTIFICATION for handle {handle} failed: {err:?}; \
its callbacks are dropped"
);
}
}
outcome
};
if outcome == ClearDone::Unexpected {
log::warn!("BR_CLEAR_FREEZE_NOTIFICATION_DONE for handle {handle} with no clear");
}
let count = self.pin_ledger_lock().clear_done(handle, ClearKind::Freeze);
release_pins(handle, count);
}
fn deliver_frozen(&self, handle: u32) {
loop {
let owners = self.freeze_lock().pending_owners(handle);
let mut delivered = false;
for proxy in owners.iter().filter_map(sync::Weak::upgrade) {
let owner = Arc::as_ptr(&proxy) as usize;
let batch = self.freeze_lock().begin_batch(handle, owner);
let Some((token, state, callbacks)) = batch else {
continue;
};
delivered = true;
let binder = SIBinder::from_arc(proxy as Arc<dyn IBinder>);
let who = SIBinder::downgrade(&binder);
for callback in &callbacks {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
callback.on_state_changed(&who, state);
}));
if let Err(payload) = result {
let msg = payload
.downcast_ref::<&'static str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic payload>");
log::error!(
"FrozenStateChangeCallback panicked for handle {handle:X}: {msg}"
);
}
}
self.freeze_lock().end_batch(handle, token);
}
if !delivered {
return;
}
}
}
pub fn freeze_process(&self, pid: i32, enable: bool, timeout: Duration) -> Result<()> {
let pid = u32::try_from(pid)
.ok()
.filter(|pid| *pid > 0)
.ok_or(StatusCode::BadValue)?;
let info = binder::binder_freeze_info {
pid,
enable: enable.into(),
timeout_ms: u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX),
};
binder::freeze(&*self.driver, info).map_err(StatusCode::from)
}
pub fn process_freeze_info(&self, pid: i32) -> Result<ProcessFreezeInfo> {
let pid = u32::try_from(pid)
.ok()
.filter(|pid| *pid > 0)
.ok_or(StatusCode::BadValue)?;
let mut info = binder::binder_frozen_status_info {
pid,
sync_recv: 0,
async_recv: 0,
};
binder::get_frozen_info(&*self.driver, &mut info).map_err(StatusCode::from)?;
Ok(ProcessFreezeInfo {
sync_received: info.sync_recv & 1 != 0,
transactions_pending: info.sync_recv & 2 != 0,
async_received: info.async_recv != 0,
})
}
pub(crate) fn publish_native(&self, arc: Arc<dyn IBinder>) -> u64 {
let mut map = self
.published_natives
.write()
.expect("Published natives lock poisoned");
for (existing_id, entry) in map.iter_mut() {
if Arc::ptr_eq(entry.binder_pin.as_arc(), &arc) {
entry.pending_reservations = entry.pending_reservations.saturating_add(1);
return *existing_id;
}
}
let binder_pin = SIBinder::from_arc(Arc::clone(&arc));
let dummy_wi = SIBinder::downgrade(&binder_pin);
arc.inc_weak(&dummy_wi)
.expect("inc_weak on Arc<dyn IBinder> must not fail");
let id = self.next_native_id.fetch_add(1, Ordering::Relaxed);
map.insert(
id,
PublishedNative {
binder_pin,
publish_count: 0,
kernel_refs: 0,
pending_reservations: 0,
},
);
id
}
pub(crate) fn incref_publish(&self, id: u64) -> bool {
let mut map = self
.published_natives
.write()
.expect("Published natives lock poisoned");
match map.get_mut(&id) {
Some(entry) => {
entry.publish_count += 1;
entry.pending_reservations = entry.pending_reservations.saturating_sub(1);
true
}
None => false,
}
}
pub(crate) fn decref_publish(&self, id: u64) -> bool {
let trigger_remove = {
let mut map = self
.published_natives
.write()
.expect("Published natives lock poisoned");
match map.get_mut(&id) {
Some(entry) => {
debug_assert!(
entry.publish_count > 0,
"decref_publish on id {id} with publish_count == 0 \
(unpaired release; check From<&SIBinder> ↔ \
FlatBinderObject::release pairing)"
);
entry.publish_count = entry.publish_count.saturating_sub(1);
entry.publish_count == 0
&& entry.kernel_refs == 0
&& entry.pending_reservations == 0
}
None => return false,
}
};
if trigger_remove {
self.remove_entry_if_zero(id);
}
true
}
pub(crate) fn ref_native_kernel(&self, id: u64) -> Option<Arc<dyn IBinder>> {
let mut map = self
.published_natives
.write()
.expect("Published natives lock poisoned");
let entry = map.get_mut(&id)?;
entry.kernel_refs += 1;
Some(Arc::clone(entry.binder_pin.as_arc()))
}
pub(crate) fn deref_native_kernel(&self, id: u64) -> Option<Arc<dyn IBinder>> {
let (arc, trigger_remove) = {
let mut map = self
.published_natives
.write()
.expect("Published natives lock poisoned");
let entry = map.get_mut(&id)?;
entry.kernel_refs = entry.kernel_refs.saturating_sub(1);
let arc = Arc::clone(entry.binder_pin.as_arc());
let trigger = entry.publish_count == 0
&& entry.kernel_refs == 0
&& entry.pending_reservations == 0;
(arc, trigger)
};
if trigger_remove {
self.remove_entry_if_zero(id);
}
Some(arc)
}
pub(crate) fn lookup_native(&self, id: u64) -> Option<Arc<dyn IBinder>> {
let map = self
.published_natives
.read()
.expect("Published natives lock poisoned");
map.get(&id).map(|e| Arc::clone(e.binder_pin.as_arc()))
}
fn remove_entry_if_zero(&self, id: u64) {
let entry = {
let mut map = self
.published_natives
.write()
.expect("Published natives lock poisoned");
let Entry::Occupied(slot) = map.entry(id) else {
return;
};
let e = slot.get();
if e.publish_count != 0 || e.kernel_refs != 0 || e.pending_reservations != 0 {
return;
}
slot.remove()
};
let arc_for_weak = Arc::clone(entry.binder_pin.as_arc());
if let Err(e) = arc_for_weak.dec_weak() {
log::error!("unpublish_native: dec_weak failed for id {id}: {e:?}");
}
drop(entry.binder_pin);
}
pub fn disable_background_scheduling(&self, disable: bool) {
self.disable_background_scheduling
.store(disable, Ordering::Relaxed);
}
pub fn background_scheduling_disabled(&self) -> bool {
self.disable_background_scheduling.load(Ordering::Relaxed)
}
pub fn driver(&self) -> Arc<File> {
self.driver.clone()
}
pub(crate) fn driver_name(&self) -> &std::path::Path {
&self.driver_name
}
pub(crate) fn max_threads(&self) -> u32 {
self.max_threads
}
pub(crate) fn thread_pool_started(&self) -> bool {
self.thread_pool_started.load(Ordering::Acquire)
}
pub fn mmap_size(&self) -> usize {
self.mmap.read().unwrap_or_else(|e| e.into_inner()).size
}
pub(crate) fn mapped_range(&self) -> std::ops::Range<usize> {
let mmap = self.mmap.read().unwrap_or_else(|e| e.into_inner());
let start = mmap.ptr as usize;
start..start + mmap.size
}
pub fn start_thread_pool() {
let this = Self::as_self();
if this
.thread_pool_started
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
if this.max_threads == 0 {
log::warn!(
"start_thread_pool() with max_threads = 0: one worker is spawned, but the \
kernel will never ask for another, so the pool cannot grow under load. \
Pass DEFAULT_MAX_BINDER_THREADS (or any non-zero ceiling) to \
ProcessState::init if this process serves incoming transactions."
);
}
this.spawn_pooled_thread(true);
}
}
fn make_binder_thread_name(&self) -> String {
let seq = self.thread_pool_seq.fetch_add(1, Ordering::SeqCst);
let pid = std::process::id();
let driver_name = self
.driver_name
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.to_owned())
.unwrap_or("BINDER".to_owned());
format!("{driver_name}:{pid}_{seq:X}")
}
pub(crate) fn spawn_pooled_thread(&self, is_main: bool) {
if self.thread_pool_started.load(Ordering::Relaxed) {
let name = self.make_binder_thread_name();
log::info!("Spawning new pooled thread, name={name}");
match thread::Builder::new().name(name).spawn(move || {
if let Err(e) = thread_state::join_thread_pool(is_main) {
log::error!("pooled binder thread exited with {e}");
}
}) {
Ok(_) => {
if is_main {
self.main_thread_spawned.fetch_add(1, Ordering::SeqCst);
} else {
self.kernel_started_threads.fetch_add(1, Ordering::SeqCst);
}
}
Err(e) => {
log::error!("failed to spawn pooled binder thread: {e}");
}
}
}
}
pub fn strong_ref_count_for_node(&self, node: &ProxyHandle) -> Result<usize> {
let mut info = binder::binder_node_info_for_ref {
handle: node.handle(),
strong_count: 0,
weak_count: 0,
reserved1: 0,
reserved2: 0,
reserved3: 0,
};
binder::get_node_info_for_ref(&self.driver, &mut info).inspect_err(|&e| {
log::error!("Binder ioctl(BINDER_GET_NODE_INFO_FOR_REF) failed: {e:?}");
})?;
Ok(info.strong_count as usize)
}
pub fn join_thread_pool() -> Result<()> {
thread_state::join_thread_pool(true)
}
}
fn open_driver(
driver: &Path,
max_threads: u32,
) -> std::result::Result<File, Box<dyn std::error::Error>> {
let fd = File::options()
.read(true)
.write(true)
.open(driver)
.map_err(|e| format!("Opening '{}' failed: {}\n", driver.to_string_lossy(), e))?;
let mut vers = binder::binder_version {
protocol_version: 0,
};
binder::version(&fd, &mut vers)
.map_err(|e| format!("Binder ioctl to obtain version failed: {e}"))?;
log::info!("Binder driver protocol version: {}", vers.protocol_version);
if vers.protocol_version != binder::BINDER_CURRENT_PROTOCOL_VERSION as i32 {
return Err(format!(
"Binder driver protocol({}) does not match user space protocol({})!",
vers.protocol_version,
binder::BINDER_CURRENT_PROTOCOL_VERSION
)
.into());
}
binder::set_max_threads(&fd, max_threads)
.map_err(|e| format!("Binder ioctl to set max threads failed: {e}"))?;
log::info!("Binder driver max threads set to {max_threads}");
let enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;
if let Err(e) = binder::enable_oneway_spam_detection(&fd, enable) {
log::warn!("Binder ioctl to enable oneway spam detection failed: {e}")
}
Ok(fd)
}
pub(crate) fn selinux_available() -> bool {
static AVAILABLE: OnceLock<bool> = OnceLock::new();
*AVAILABLE.get_or_init(|| {
cfg!(target_os = "android") || std::path::Path::new("/sys/fs/selinux/enforce").exists()
})
}
impl Drop for ProcessState {
fn drop(self: &mut ProcessState) {
let mmap = self.mmap.read().unwrap_or_else(|e| e.into_inner());
unsafe {
if let Err(e) = rustix::mm::munmap(mmap.ptr, mmap.size) {
log::error!("ProcessState::drop: munmap failed: {e}");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_thread_ceiling_matches_aosp() {
assert_eq!(DEFAULT_MAX_BINDER_THREADS, 15);
}
#[test]
fn pin_ledger_releases_after_the_last_clear_completes() {
let mut ledger = PinLedger::default();
ledger.hold(7);
assert_eq!(ledger.take_releasable(7), 1);
assert!(ledger.0.is_empty(), "a released handle leaves no slot");
ledger.note_clear(7, ClearKind::Death);
ledger.hold(7);
assert_eq!(ledger.take_releasable(7), 0);
ledger.note_clear(7, ClearKind::Death);
assert_eq!(ledger.clear_done(7, ClearKind::Death), 0);
assert_eq!(ledger.clear_done(7, ClearKind::Death), 1);
assert!(ledger.0.is_empty());
ledger.note_clear(1, ClearKind::Death);
ledger.hold(1);
ledger.hold(2);
assert_eq!(ledger.take_releasable(2), 1);
assert_eq!(ledger.clear_done(3, ClearKind::Death), 0);
assert_eq!(ledger.clear_done(1, ClearKind::Death), 1);
assert!(ledger.0.is_empty());
}
#[test]
fn driver_feature_flag_is_read_next_to_the_driver_node() {
static NEXT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let root = std::env::temp_dir().join(format!(
"rsbinder-features-{}-{}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
));
let features = root.join("features");
std::fs::create_dir_all(&features).expect("temp dir");
let driver = root.join("binder");
std::fs::write(&driver, b"").expect("driver stand-in");
std::fs::write(features.join("on"), b"1\n").expect("flag");
std::fs::write(features.join("off"), b"0\n").expect("flag");
let dir = features_dir(&driver);
assert_eq!(dir, Some(features.canonicalize().expect("canonical")));
assert!(driver_feature_enabled(dir.as_deref(), "on"));
assert!(!driver_feature_enabled(dir.as_deref(), "off"));
let aosp = std::fs::read_to_string("/dev/binderfs/features/freeze_notification")
.is_ok_and(|value| value.trim() == "1");
assert_eq!(
driver_feature_enabled(dir.as_deref(), "freeze_notification"),
aosp
);
assert_eq!(driver_feature_enabled(None, "freeze_notification"), aosp);
std::fs::remove_dir_all(&root).expect("cleanup");
}
#[test]
fn pin_ledger_holds_pins_for_freeze_clears_too() {
let mut ledger = PinLedger::default();
ledger.note_clear(4, ClearKind::Freeze);
ledger.note_clear(4, ClearKind::Death);
ledger.hold(4);
assert_eq!(ledger.take_releasable(4), 0);
assert_eq!(
ledger.clear_done(4, ClearKind::Death),
0,
"freeze still in flight"
);
assert_eq!(ledger.clear_done(4, ClearKind::Death), 0);
assert_eq!(ledger.clear_done(4, ClearKind::Freeze), 1);
assert!(ledger.0.is_empty());
}
#[test]
fn expunge_removes_only_the_dropping_pins_entry() {
let pin: sync::Weak<HandlePin> = sync::Weak::new();
let ours = pin.as_ptr();
let entry = || CacheEntry {
weak: sync::Weak::new(),
pin: pin.clone(),
descriptor: String::new(),
};
let other = std::ptr::NonNull::<HandlePin>::dangling()
.as_ptr()
.cast_const();
assert!(!std::ptr::eq(ours, other));
let mut map = HashMap::new();
assert!(
!expunge_locked(&mut map, 5, ours),
"absent: nothing to remove"
);
map.insert(5, entry());
assert!(
!expunge_locked(&mut map, 5, other),
"a newer pin's entry: keep it"
);
assert!(map.contains_key(&5));
assert!(expunge_locked(&mut map, 5, ours));
assert!(!map.contains_key(&5));
}
#[test]
fn a_receive_mapping_size_is_range_checked_then_page_rounded() {
let page = rustix::param::page_size();
let default = ProcessState::default_mmap_size();
assert_eq!(default, (1024 * 1024) - page * 2);
assert_eq!(ProcessState::normalized_mmap_size(default), Ok(default));
assert_eq!(
ProcessState::normalized_mmap_size(0),
Err(StatusCode::BadValue)
);
assert_eq!(
ProcessState::normalized_mmap_size(page - 1),
Err(StatusCode::BadValue)
);
assert_eq!(ProcessState::normalized_mmap_size(page), Ok(page));
assert_eq!(
ProcessState::normalized_mmap_size(MAX_BINDER_MMAP_SIZE),
Ok(MAX_BINDER_MMAP_SIZE)
);
assert_eq!(
ProcessState::normalized_mmap_size(MAX_BINDER_MMAP_SIZE + 1),
Err(StatusCode::BadValue)
);
assert_eq!(ProcessState::normalized_mmap_size(page + 1), Ok(page * 2));
assert_eq!(
ProcessState::normalized_mmap_size(MAX_BINDER_MMAP_SIZE - 1),
Ok(MAX_BINDER_MMAP_SIZE)
);
}
fn assert_process_state_initialized() {
let process = ProcessState::init_default().expect("init_default");
assert_eq!(process.max_threads, DEFAULT_MAX_BINDER_THREADS);
assert_eq!(
process.driver_name,
PathBuf::from(ProcessState::default_driver_path())
);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_process_state() {
assert_process_state_initialized();
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_process_state_context_object() {
let process = ProcessState::init_default().expect("init_default");
assert!(process.context_object().is_ok());
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_process_state_strong_proxy_for_handle() {
let process = ProcessState::init_default().expect("init_default");
assert!(process.strong_proxy_for_handle(0).is_ok());
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_concurrent_strong_proxy_same_handle_returns_same_arc() {
let _ = ProcessState::init_default();
let handles: Vec<_> = (0..8)
.map(|_| std::thread::spawn(|| ProcessState::as_self().strong_proxy_for_handle(0)))
.collect();
let arcs: Vec<SIBinder> = handles
.into_iter()
.map(|h| {
h.join()
.expect("thread panic")
.expect("strong_proxy failed")
})
.collect();
let first = &arcs[0];
for a in &arcs[1..] {
assert_eq!(
first, a,
"concurrent slow-path winners must share a single Arc"
);
}
let map = ProcessState::as_self()
.handle_to_proxy
.read()
.expect("Handle to proxy lock poisoned");
assert!(
map.contains_key(&0),
"case (a) winner must have installed an entry for handle 0"
);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_concurrent_strong_proxy_case_b_resurrection() {
let _ = ProcessState::init_default();
let lookup_from_8_threads = || -> SIBinder {
let handles: Vec<_> = (0..8)
.map(|_| std::thread::spawn(|| ProcessState::as_self().strong_proxy_for_handle(0)))
.collect();
let arcs: Vec<SIBinder> = handles
.into_iter()
.map(|h| {
h.join()
.expect("thread panic")
.expect("strong_proxy failed")
})
.collect();
for a in &arcs[1..] {
assert_eq!(&arcs[0], a, "concurrent lookups must produce a single Arc");
}
arcs[0].clone()
};
let uid = thread_state::get_calling_uid();
let was_by_uid = crate::proxy_count::is_count_by_uid_enabled();
crate::proxy_count::enable_count_by_uid(true);
let count = || {
(
crate::proxy_count::get_binder_proxy_count(),
crate::proxy_count::get_binder_proxy_count_for_uid(uid),
)
};
let (base, base_uid) = count();
let initial = ProcessState::as_self()
.strong_proxy_for_handle(0)
.expect("initial strong_proxy failed");
let initial_gen = ProcessState::as_self()
.cache_generation_for(0)
.expect("entry must exist for handle 0");
assert_eq!(
count(),
(base + 1, base_uid + 1),
"a committed pin counts once"
);
let weak = SIBinder::downgrade(&initial);
drop(initial);
assert!(
weak.upgrade().is_err(),
"WIBinder::upgrade never resurrects"
);
assert_eq!(
count(),
(base + 1, base_uid + 1),
"a pin only a WIBinder keeps stays counted, as a BpBinder a wp keeps"
);
assert_eq!(
ProcessState::as_self().cache_generation_for(0),
Some(initial_gen),
"a live WIBinder keeps the entry, as a wp<BpBinder> keeps its cache slot"
);
let resurrected = lookup_from_8_threads();
assert_eq!(
ProcessState::as_self().cache_generation_for(0),
Some(initial_gen),
"case (b) resurrection must preserve the entry's generation"
);
assert!(
weak == resurrected,
"the WIBinder names the resurrected proxy"
);
assert_eq!(
weak.upgrade().as_ref(),
Ok(&resurrected),
"the WIBinder upgrades to the proxy revived on its pin, as AOSP promote() after force_set"
);
assert_eq!(
count(),
(base + 1, base_uid + 1),
"a case (b) revival is not counted again"
);
drop(resurrected);
drop(weak);
assert_eq!(
ProcessState::as_self().cache_generation_for(0),
None,
"the last proxy and WIBinder gone, the pin's drop removes the entry"
);
assert_eq!(
count(),
(base, base_uid),
"the pin's drop posts the decrement"
);
let rebuilt = lookup_from_8_threads();
let rebuilt_gen = ProcessState::as_self()
.cache_generation_for(0)
.expect("the lookups must have installed an entry for handle 0");
assert_ne!(
rebuilt_gen, initial_gen,
"a fresh case (a) takes a new generation"
);
assert_eq!(
count(),
(base + 1, base_uid + 1),
"8 racing lookups count one pin, uncommitted spares none"
);
drop(rebuilt);
assert_eq!(count(), (base, base_uid));
crate::proxy_count::enable_count_by_uid(was_by_uid);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_strong_proxy_under_same_thread_dead_binder_no_deadlock() {
let process = ProcessState::init_default().expect("init_default");
let seed = process
.strong_proxy_for_handle(0)
.expect("seed strong_proxy_for_handle(0) must succeed");
drop(seed);
let fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let fired_w = std::sync::Arc::clone(&fired);
let (tx, rx) = std::sync::mpsc::channel();
let join = std::thread::spawn(move || {
super::set_slow_path_p2_test_hook(Some(Box::new(move |handle| {
fired_w.store(true, std::sync::atomic::Ordering::SeqCst);
ProcessState::as_self()
.send_obituary_for_handle(handle)
.expect("send_obituary_for_handle from P2 hook must not fail");
})));
let r = ProcessState::as_self().strong_proxy_for_handle(0);
super::set_slow_path_p2_test_hook(None);
tx.send(r).expect("result channel must not drop");
});
let result = rx
.recv_timeout(std::time::Duration::from_secs(5))
.expect("strong_proxy_for_handle must complete within 5s — deadlock regression");
join.join().expect("worker thread must not panic");
assert!(
fired.load(std::sync::atomic::Ordering::SeqCst),
"P2 hook never fired — P1 short-circuited at case (c), most likely \
because a parallel test held an Arc for handle 0 and kept the \
cache Weak upgradeable. Test passed vacuously."
);
match result {
Ok(_arc) => {}
Err(StatusCode::DeadObject) => {}
Err(other) => panic!("unexpected slow-path result: {other:?}"),
}
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_process_state_disable_background_scheduling() {
let process = ProcessState::init_default().expect("init_default");
process.disable_background_scheduling(true);
assert!(process.background_scheduling_disabled());
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn set_call_restriction_reaches_a_thread_that_already_used_binder() {
let process = ProcessState::init_default().expect("init_default");
let prev = process.call_restriction();
assert_eq!(thread_state::call_restriction(), prev);
process.set_call_restriction(CallRestriction::ErrorIfNotOneway);
assert_eq!(
process.call_restriction(),
CallRestriction::ErrorIfNotOneway
);
assert_eq!(
thread_state::call_restriction(),
CallRestriction::ErrorIfNotOneway
);
process.set_call_restriction(prev);
assert_eq!(thread_state::call_restriction(), prev);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_process_state_start_thread_pool() {
assert_process_state_initialized();
let process = ProcessState::as_self();
let was_started = process.thread_pool_started.load(Ordering::SeqCst);
let before = process.main_thread_spawned.load(Ordering::SeqCst);
ProcessState::start_thread_pool();
assert!(process.thread_pool_started.load(Ordering::SeqCst));
let after = process.main_thread_spawned.load(Ordering::SeqCst);
if was_started {
assert_eq!(after, before);
} else {
assert_eq!(after, before + 1);
}
}
struct MockNative;
impl IBinder for MockNative {
fn link_to_death(&self, _: sync::Weak<dyn DeathRecipient>) -> Result<()> {
Err(StatusCode::InvalidOperation)
}
fn unlink_to_death(&self, _: sync::Weak<dyn DeathRecipient>) -> Result<()> {
Err(StatusCode::InvalidOperation)
}
fn ping_binder(&self) -> Result<()> {
Ok(())
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_transactable(&self) -> Option<&dyn crate::Transactable> {
None
}
fn descriptor(&self) -> &str {
"rsbinder.test.MockNative"
}
fn is_remote(&self) -> bool {
false
}
fn inc_strong(&self, _: &SIBinder) -> Result<()> {
Ok(())
}
fn attempt_inc_strong(&self) -> bool {
true
}
fn dec_strong(&self, _: Option<std::mem::ManuallyDrop<SIBinder>>) -> Result<()> {
Ok(())
}
fn inc_weak(&self, _: &WIBinder) -> Result<()> {
Ok(())
}
fn dec_weak(&self) -> Result<()> {
Ok(())
}
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_native_uaf_window_closed() {
let process = ProcessState::init_default().expect("init_default");
let arc: Arc<dyn IBinder> = Arc::new(MockNative);
let id = process.publish_native(Arc::clone(&arc));
assert!(
process.incref_publish(id),
"incref on freshly published id must succeed"
);
drop(arc);
assert!(process.ref_native_kernel(id).is_some());
assert!(process.ref_native_kernel(id).is_some());
assert!(process.deref_native_kernel(id).is_some());
assert!(
process.lookup_native(id).is_some(),
"entry must remain while publish_count > 0"
);
assert!(process.decref_publish(id));
assert!(
process.lookup_native(id).is_some(),
"entry must remain while kernel_refs > 0"
);
assert!(process.deref_native_kernel(id).is_some());
assert!(
process.lookup_native(id).is_none(),
"entry must be removed after both counts hit zero"
);
assert!(!process.incref_publish(id));
assert!(!process.decref_publish(id));
assert!(process.ref_native_kernel(id).is_none());
assert!(process.deref_native_kernel(id).is_none());
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_native_dedup_same_arc() {
let process = ProcessState::init_default().expect("init_default");
let arc: Arc<dyn IBinder> = Arc::new(MockNative);
let id1 = process.publish_native(Arc::clone(&arc));
let id2 = process.publish_native(Arc::clone(&arc));
assert_eq!(id1, id2, "publishing the same Arc twice must dedup");
assert!(process.incref_publish(id1));
assert!(process.incref_publish(id1));
assert!(process.decref_publish(id1));
assert!(
process.lookup_native(id1).is_some(),
"entry must remain while one parcel slot still holds a ref"
);
assert!(process.decref_publish(id1));
assert!(
process.lookup_native(id1).is_none(),
"entry must be removed after the last release fires"
);
drop(arc);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_native_dedup_reserves_against_concurrent_removal() {
let process = ProcessState::init_default().expect("init_default");
let arc: Arc<dyn IBinder> = Arc::new(MockNative);
let id = process.publish_native(Arc::clone(&arc));
assert!(process.incref_publish(id));
let id_b = process.publish_native(Arc::clone(&arc));
assert_eq!(id, id_b);
assert!(process.decref_publish(id));
assert!(
process.lookup_native(id).is_some(),
"dedup reservation must keep the entry alive across the acquire gap"
);
assert!(process.incref_publish(id));
assert!(process.decref_publish(id));
assert!(process.lookup_native(id).is_none());
drop(arc);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_native_distinct_arcs_get_distinct_ids() {
let process = ProcessState::init_default().expect("init_default");
let arc_a: Arc<dyn IBinder> = Arc::new(MockNative);
let arc_b: Arc<dyn IBinder> = Arc::new(MockNative);
assert!(!Arc::ptr_eq(&arc_a, &arc_b));
let id_a = process.publish_native(Arc::clone(&arc_a));
let id_b = process.publish_native(Arc::clone(&arc_b));
assert_ne!(id_a, id_b);
for id in [id_a, id_b] {
assert!(process.incref_publish(id));
assert!(process.decref_publish(id));
assert!(process.lookup_native(id).is_none());
}
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_native_lookup_does_not_change_counts() {
let process = ProcessState::init_default().expect("init_default");
let arc: Arc<dyn IBinder> = Arc::new(MockNative);
let id = process.publish_native(Arc::clone(&arc));
assert!(process.incref_publish(id)); assert!(process.ref_native_kernel(id).is_some());
for _ in 0..5 {
assert!(process.lookup_native(id).is_some());
}
assert!(process.decref_publish(id));
assert!(
process.lookup_native(id).is_some(),
"lookup must not have decremented kernel_refs"
);
assert!(process.deref_native_kernel(id).is_some());
assert!(process.lookup_native(id).is_none());
drop(arc);
}
}