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, OnceLock, RwLock};
use std::thread;
use crate::{binder::*, error::*, proxy::*, sys::binder, thread_state};
fn undo_case_a_pin(handle: u32) {
if let Err(err) = thread_state::dec_weak_handle(handle) {
log::warn!(
"Best-effort BC_DECREFS for handle {handle} failed during \
case (a) cleanup: {err:?}; kernel binder_ref pin may leak \
until obituary"
);
return;
}
if let Err(err) = thread_state::flush_commands() {
log::warn!(
"Best-effort flush after BC_DECREFS for handle {handle} \
failed during case (a) cleanup: {err:?}; kernel binder_ref \
pin may leak until obituary"
);
}
}
enum SlowPathPlan {
CaseA,
CaseB { descriptor: String, generation: u64 },
}
enum SlowPathDecision {
Cached(SIBinder),
NeedIpc(SlowPathPlan),
}
enum SlowPathReady {
CaseA { descriptor: String },
CaseB { descriptor: String, generation: u64 },
}
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>,
handle: u32,
descriptor: String,
generation: u64,
stability: Stability,
owns_case_a_pin: bool,
) -> Result<SIBinder> {
let arc = match ProxyHandle::new_acquired(handle, descriptor.clone(), stability) {
Ok(arc) => arc,
Err(err) => {
if owns_case_a_pin {
undo_case_a_pin(handle);
}
return Err(err);
}
};
handle_to_proxy.insert(
handle,
CacheEntry {
weak: Arc::downgrade(&arc),
descriptor,
generation,
},
);
Ok(SIBinder::from_arc(arc as Arc<dyn IBinder>))
}
pub(crate) struct CacheEntry {
pub(crate) weak: sync::Weak<ProxyHandle>,
pub(crate) descriptor: String,
pub(crate) generation: u64,
}
pub(crate) struct PublishedNative {
pub(crate) binder_pin: SIBinder,
pub(crate) publish_count: u32,
pub(crate) kernel_refs: u32,
}
#[derive(Debug, Clone, Copy)]
pub enum CallRestriction {
None,
ErrorIfNotOneway,
FatalIfNotOneway,
}
const DEFAULT_MAX_BINDER_THREADS: u32 = 15;
const DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION: u32 = 1;
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,
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,
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 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;
}
pub(crate) fn call_restriction(&self) -> CallRestriction {
*self
.call_restriction
.read()
.expect("Call restriction lock poisoned")
}
fn inner_init(
driver_name: &str,
max_threads: u32,
) -> std::result::Result<ProcessState, Box<dyn std::error::Error>> {
let max_threads = if max_threads != 0 && max_threads < DEFAULT_MAX_BINDER_THREADS {
max_threads
} else {
DEFAULT_MAX_BINDER_THREADS
};
let driver_name = PathBuf::from(driver_name);
let driver = open_driver(&driver_name, max_threads)?;
let vm_size = (1024 * 1024) - rustix::param::page_size() * 2;
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),
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),
current_threads: AtomicUsize::new(0),
})
}
pub fn init(
driver_name: &str,
max_threads: u32,
) -> 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)?;
Ok(cell.get_or_init(|| instance))
}
pub fn init_default() -> std::result::Result<&'static ProcessState, Box<dyn std::error::Error>>
{
let path = if Path::new(crate::DEFAULT_BINDER_PATH).exists() {
crate::DEFAULT_BINDER_PATH
} else {
crate::LEGACY_BINDER_PATH
};
Self::init(path, 0)
}
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_none() {
let obj = binder::flat_binder_object::new_binder_with_flags(
binder::FLAT_BINDER_FLAG_ACCEPTS_FDS,
);
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));
}
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)?;
self.slow_path_p3(handle, stability, ready)
}
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(arc) = handle_to_proxy.get(&handle).and_then(|e| e.weak.upgrade()) {
return Ok(SlowPathDecision::Cached(SIBinder::from_arc(arc)));
}
if let Some(entry) = handle_to_proxy.get(&handle) {
return Ok(SlowPathDecision::NeedIpc(SlowPathPlan::CaseB {
descriptor: entry.descriptor.clone(),
generation: entry.generation,
}));
}
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);
}
Ok(SlowPathDecision::NeedIpc(SlowPathPlan::CaseA))
}
fn slow_path_p2(&self, handle: u32, plan: SlowPathPlan) -> Result<SlowPathReady> {
#[cfg(test)]
slow_path_p2_test_hook(handle);
if handle == 0 && crate::sdk_at_least(30) {
let _restore = RestoreCallRestriction(thread_state::call_restriction());
thread_state::set_call_restriction(CallRestriction::None);
if let Err(err) = thread_state::ping_binder(handle) {
if matches!(plan, SlowPathPlan::CaseA) {
undo_case_a_pin(handle);
}
return Err(err);
}
}
match plan {
SlowPathPlan::CaseA => match thread_state::query_interface(handle) {
Ok(descriptor) => Ok(SlowPathReady::CaseA { descriptor }),
Err(err) => {
undo_case_a_pin(handle);
Err(err)
}
},
SlowPathPlan::CaseB {
descriptor,
generation,
} => Ok(SlowPathReady::CaseB {
descriptor,
generation,
}),
}
}
fn slow_path_p3(
&self,
handle: u32,
stability: Stability,
ready: SlowPathReady,
) -> Result<SIBinder> {
let mut handle_to_proxy = self
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
if let Some(arc) = handle_to_proxy.get(&handle).and_then(|e| e.weak.upgrade()) {
if matches!(ready, SlowPathReady::CaseA { .. }) {
undo_case_a_pin(handle);
}
return Ok(SIBinder::from_arc(arc));
}
let cached = handle_to_proxy
.get(&handle)
.map(|e| (e.descriptor.clone(), e.generation));
match (ready, cached) {
(SlowPathReady::CaseA { descriptor }, None) => {
let generation = self.next_generation.fetch_add(1, Ordering::Relaxed);
commit_new_acquired(
&mut handle_to_proxy,
handle,
descriptor,
generation,
stability,
true,
)
}
(SlowPathReady::CaseA { .. }, Some((cached_desc, cached_gen))) => {
undo_case_a_pin(handle);
commit_new_acquired(
&mut handle_to_proxy,
handle,
cached_desc,
cached_gen,
stability,
false,
)
}
(
SlowPathReady::CaseB {
descriptor,
generation,
},
Some((_, cached_gen)),
) if cached_gen == generation => {
commit_new_acquired(
&mut handle_to_proxy,
handle,
descriptor,
generation,
stability,
false,
)
}
(SlowPathReady::CaseB { .. }, Some((cached_desc, cached_gen))) => {
commit_new_acquired(
&mut handle_to_proxy,
handle,
cached_desc,
cached_gen,
stability,
false,
)
}
(SlowPathReady::CaseB { .. }, None) => {
Err(StatusCode::DeadObject)
}
}
}
pub(crate) fn cache_generation_for(&self, handle: u32) -> Option<u64> {
self.handle_to_proxy
.read()
.expect("Handle to proxy lock poisoned")
.get(&handle)
.map(|e| e.generation)
}
pub(crate) fn resurrect_proxy_for_handle_stability(
&self,
handle: u32,
stability: Stability,
expected_generation: u64,
) -> Result<SIBinder> {
if let Some(arc) = self
.handle_to_proxy
.read()
.expect("Handle to proxy lock poisoned")
.get(&handle)
.filter(|e| e.generation == expected_generation)
.and_then(|e| e.weak.upgrade())
{
return Ok(SIBinder::from_arc(arc as Arc<dyn IBinder>));
}
let mut handle_to_proxy = self
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
let (descriptor, generation) = match handle_to_proxy.get(&handle) {
None => return Err(StatusCode::DeadObject),
Some(entry) if entry.generation != expected_generation => {
return Err(StatusCode::DeadObject);
}
Some(entry) => {
if let Some(arc) = entry.weak.upgrade() {
return Ok(SIBinder::from_arc(arc as Arc<dyn IBinder>));
}
(entry.descriptor.clone(), entry.generation)
}
};
let arc = ProxyHandle::new_acquired(handle, descriptor.clone(), stability)?;
handle_to_proxy.insert(
handle,
CacheEntry {
weak: Arc::downgrade(&arc),
descriptor,
generation,
},
);
Ok(SIBinder::from_arc(arc as Arc<dyn IBinder>))
}
pub(crate) fn send_obituary_for_handle(&self, handle: u32) -> Result<()> {
let entry = {
let mut handle_to_proxy = self
.handle_to_proxy
.write()
.expect("Handle to proxy lock poisoned");
handle_to_proxy.remove(&handle)
};
if let Some(entry) = entry {
if let Some(arc) = entry.weak.upgrade() {
let sibinder = SIBinder::from_arc(arc.clone() as Arc<dyn IBinder>);
let who = SIBinder::downgrade(&sibinder);
arc.send_obituary(&who)?;
} else {
log::trace!("Object for handle {handle} already destroyed at obituary time");
}
} else {
log::trace!("Handle {handle} was not in cache during obituary");
}
Ok(())
}
pub(crate) fn release_obituary_pin(&self, handle: u32) -> Result<()> {
thread_state::flush_commands()?;
thread_state::dec_weak_handle(handle)?;
thread_state::flush_commands()?;
Ok(())
}
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() {
if Arc::ptr_eq(entry.binder_pin.as_arc(), &arc) {
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,
},
);
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;
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> ↔ \
flat_binder_object::release pairing)"
);
entry.publish_count = entry.publish_count.saturating_sub(1);
entry.publish_count == 0 && entry.kernel_refs == 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;
(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 needs_remove = map
.get(&id)
.map(|e| e.publish_count == 0 && e.kernel_refs == 0)
.unwrap_or(false);
if !needs_remove {
return;
}
map.remove(&id).expect("just observed Some")
};
let arc_for_weak = Arc::clone(entry.binder_pin.as_arc());
let _ = arc_for_weak.dec_weak();
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 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!("Extra binder thread started, but 0 threads requested.\nDo not use *start_thread_pool when zero threads are requested.");
}
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}");
let _ = thread::Builder::new()
.name(name)
.spawn(move || thread_state::join_thread_pool(is_main));
self.kernel_started_threads.fetch_add(1, Ordering::SeqCst);
}
}
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)
}
impl Drop for ProcessState {
fn drop(self: &mut ProcessState) {
let mmap = self.mmap.read().expect("Mmap lock poisoned");
unsafe {
rustix::mm::munmap(mmap.ptr, mmap.size).expect("Failed to unmap memory");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
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(crate::DEFAULT_BINDER_PATH)
);
}
#[test]
#[serial_test::serial(binder)]
fn test_process_state() {
assert_process_state_initialized();
}
#[test]
#[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]
#[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]
#[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]
#[serial_test::serial(binder)]
fn test_concurrent_strong_proxy_case_b_resurrection() {
let _ = ProcessState::init_default();
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");
drop(initial);
std::thread::yield_now();
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 case (b) resurrection must produce a single Arc"
);
}
assert_eq!(
ProcessState::as_self().cache_generation_for(0),
Some(initial_gen),
"case (b) resurrection must preserve the entry's generation"
);
}
#[test]
#[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]
#[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]
#[serial_test::serial(binder)]
fn test_process_state_start_thread_pool() {
assert_process_state_initialized();
ProcessState::start_thread_pool();
assert_eq!(
ProcessState::as_self()
.kernel_started_threads
.load(Ordering::SeqCst),
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]
#[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]
#[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]
#[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]
#[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);
}
}