use log::error;
use std::backtrace::Backtrace;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::{CStr, CString};
use std::fmt::Debug;
use std::fs::File;
use std::sync::{atomic::Ordering, Arc};
use crate::{
binder::*, command_stream::CommandStream, error::*, parcel::*, process_state::*, sys::*,
};
thread_local! {
static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new());
static BINDER_DEREFS: RefCell<BinderDerefs> = RefCell::new(BinderDerefs::new());
static THREAD_EXIT_GUARD: RefCell<Option<ThreadExitGuard>> = const { RefCell::new(None) };
}
struct ThreadExitGuard {
driver: Arc<File>,
}
impl Drop for ThreadExitGuard {
fn drop(&mut self) {
if THREAD_STATE.try_with(|_| ()).is_ok() {
if let Err(e) = flush_commands() {
log::warn!("flush on binder thread exit failed: {e}");
}
}
if let Err(e) = binder::thread_exit(&*self.driver, 0) {
log::warn!("BINDER_THREAD_EXIT failed: {e}");
}
}
}
fn ensure_thread_exit_guard(driver: &Arc<File>) {
let _ = THREAD_EXIT_GUARD.try_with(|g| {
let mut g = g.borrow_mut();
if g.is_none() {
*g = Some(ThreadExitGuard {
driver: Arc::clone(driver),
});
}
});
}
#[cfg(feature = "rpc")]
thread_local! {
static RPC_CALLING: std::cell::RefCell<Option<std::sync::Arc<crate::rpc::transport::PeerIdentity>>> =
const { std::cell::RefCell::new(None) };
}
#[cfg(feature = "rpc")]
pub(crate) const RPC_UNKNOWN_CALLING_UID: binder::uid_t = binder::uid_t::MAX;
#[cfg(feature = "rpc")]
pub(crate) struct RpcCallingGuard {
previous: Option<std::sync::Arc<crate::rpc::transport::PeerIdentity>>,
}
#[cfg(feature = "rpc")]
impl RpcCallingGuard {
pub(crate) fn install(peer: std::sync::Arc<crate::rpc::transport::PeerIdentity>) -> Self {
let previous = RPC_CALLING.with(|c| c.borrow_mut().replace(peer));
RpcCallingGuard { previous }
}
}
#[cfg(feature = "rpc")]
impl Drop for RpcCallingGuard {
fn drop(&mut self) {
RPC_CALLING.with(|c| *c.borrow_mut() = self.previous.take());
}
}
#[cfg(feature = "rpc")]
fn peer_uid_pid(peer: &crate::rpc::transport::PeerIdentity) -> (binder::uid_t, binder::pid_t) {
match peer {
crate::rpc::transport::PeerIdentity::Local { uid, pid } => (*uid, *pid),
_ => (RPC_UNKNOWN_CALLING_UID, -1),
}
}
#[inline]
fn rpc_calling() -> Option<(binder::uid_t, binder::pid_t)> {
#[cfg(feature = "rpc")]
{
RPC_CALLING.with(|c| c.borrow().as_deref().map(peer_uid_pid))
}
#[cfg(not(feature = "rpc"))]
{
None
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Caller {
Kernel {
uid: binder::uid_t,
pid: binder::pid_t,
sid: Option<CString>,
},
#[cfg(feature = "rpc")]
Rpc(crate::rpc::transport::PeerIdentity),
}
pub fn calling_caller() -> Option<Caller> {
#[cfg(feature = "rpc")]
{
if let Some(peer) = RPC_CALLING.with(|c| c.borrow().as_deref().cloned()) {
return Some(Caller::Rpc(peer));
}
}
if !ProcessState::is_initialized() {
return None;
}
THREAD_STATE.with(|thread_state| {
let thread_state = thread_state.borrow();
thread_state.transaction.as_ref().map(|tr| {
let sid = if tr.calling_sid.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(tr.calling_sid as _).to_owned() })
};
Caller::Kernel {
uid: tr.calling_uid,
pid: tr.calling_pid,
sid,
}
})
})
}
const RETURN_STRINGS: [&str; 23] = [
"BR_ERROR",
"BR_OK",
"BR_TRANSACTION",
"BR_REPLY",
"BR_ACQUIRE_RESULT",
"BR_DEAD_REPLY",
"BR_TRANSACTION_COMPLETE",
"BR_INCREFS",
"BR_ACQUIRE",
"BR_RELEASE",
"BR_DECREFS",
"BR_ATTEMPT_ACQUIRE",
"BR_NOOP",
"BR_SPAWN_LOOPER",
"BR_FINISHED",
"BR_DEAD_BINDER",
"BR_CLEAR_DEATH_NOTIFICATION_DONE",
"BR_FAILED_REPLY",
"BR_FROZEN_REPLY",
"BR_ONEWAY_SPAM_SUSPECT",
"BR_TRANSACTION_PENDING_FROZEN",
"BR_FROZEN_BINDER",
"BR_CLEAR_FREEZE_NOTIFICATION_DONE",
];
fn return_to_str(cmd: std::os::raw::c_uint) -> &'static str {
if cmd == binder::BR_TRANSACTION_SEC_CTX {
"BR_TRANSACTION_SEC_CTX"
} else {
let idx: usize = (cmd & binder::_IOC_NRMASK) as _;
if idx < RETURN_STRINGS.len() {
RETURN_STRINGS[idx]
} else {
"Unknown BR_ return"
}
}
}
const COMMAND_STRINGS: [&str; 22] = [
"BC_TRANSACTION",
"BC_REPLY",
"BC_ACQUIRE_RESULT",
"BC_FREE_BUFFER",
"BC_INCREFS",
"BC_ACQUIRE",
"BC_RELEASE",
"BC_DECREFS",
"BC_INCREFS_DONE",
"BC_ACQUIRE_DONE",
"BC_ATTEMPT_ACQUIRE",
"BC_REGISTER_LOOPER",
"BC_ENTER_LOOPER",
"BC_EXIT_LOOPER",
"BC_REQUEST_DEATH_NOTIFICATION",
"BC_CLEAR_DEATH_NOTIFICATION",
"BC_DEAD_BINDER_DONE",
"BC_TRANSACTION_SG",
"BC_REPLY_SG",
"BC_REQUEST_FREEZE_NOTIFICATION",
"BC_CLEAR_FREEZE_NOTIFICATION",
"BC_FREEZE_NOTIFICATION_DONE",
];
fn command_to_str(cmd: std::os::raw::c_uint) -> &'static str {
let idx: usize = (cmd & 0xFF) as _;
if idx < COMMAND_STRINGS.len() {
COMMAND_STRINGS[idx]
} else {
"Unknown BC_ command"
}
}
const WORK_SOURCE_PROPAGATED_BIT_INDEX: i64 = 32;
pub(crate) const UNSET_WORK_SOURCE: i32 = -1;
#[derive(Debug, Clone, Copy)]
struct TransactionState {
calling_pid: binder::pid_t,
calling_sid: *const u8,
calling_uid: binder::uid_t,
last_transaction_binder_flags: u32,
work_source: binder::uid_t,
propagate_work_source: bool,
has_explicit_identity: bool,
}
impl TransactionState {
fn from_transaction_data(data: &binder::binder_transaction_data_secctx) -> Self {
TransactionState {
calling_pid: data.transaction_data.sender_pid,
calling_sid: data.secctx as _,
calling_uid: data.transaction_data.sender_euid,
last_transaction_binder_flags: data.transaction_data.flags,
work_source: 0,
propagate_work_source: false,
has_explicit_identity: false,
}
}
}
fn pack_calling_identity(has_explicit: bool, uid: binder::uid_t, pid: binder::pid_t) -> i64 {
let pid_low = pid as u32;
let pid_low = if has_explicit {
pid_low | (1 << 30)
} else {
pid_low & !(1 << 30)
};
let token = ((uid as u64) << 32) | (pid_low as u64);
token as i64
}
fn unpack_has_explicit_identity(token: i64) -> bool {
((token as i32) & (1 << 30)) != 0
}
fn unpack_calling_uid(token: i64) -> binder::uid_t {
(token >> 32) as binder::uid_t
}
fn unpack_calling_pid(token: i64) -> binder::pid_t {
let encoded = token as i32;
if (encoded & (1 << 31)) != 0 {
(encoded | (1 << 30)) as binder::pid_t
} else {
(encoded & !(1 << 30)) as binder::pid_t
}
}
struct BinderDerefs {
pending_strong_derefs: VecDeque<u64>,
pending_weak_derefs: VecDeque<u64>,
}
impl BinderDerefs {
fn new() -> Self {
BinderDerefs {
pending_strong_derefs: VecDeque::new(),
pending_weak_derefs: VecDeque::new(),
}
}
}
fn process_pending_derefs() -> Result<()> {
loop {
loop {
let batch: VecDeque<u64> =
BINDER_DEREFS.with(|d| std::mem::take(&mut d.borrow_mut().pending_weak_derefs));
if batch.is_empty() {
break;
}
for id in batch {
if ProcessState::as_self().deref_native_kernel(id).is_none() {
log::trace!("BR_DECREFS for unknown native id {id}");
}
}
}
let id = BINDER_DEREFS.with(|d| d.borrow_mut().pending_strong_derefs.pop_front());
match id {
Some(id) => {
if ProcessState::as_self().deref_native_kernel(id).is_none() {
log::trace!("BR_RELEASE for unknown native id {id}");
}
}
None => return Ok(()),
}
}
}
pub(crate) struct ThreadState {
in_parcel: CommandStream,
out_parcel: CommandStream,
transaction: Option<TransactionState>,
strict_mode_policy: i32,
is_looper: bool,
is_flushing: bool,
out_flush_epoch: u64,
call_restriction: CallRestriction,
driver: Arc<File>,
}
impl ThreadState {
fn new() -> Self {
ThreadState {
in_parcel: CommandStream::new(),
out_parcel: CommandStream::new(),
transaction: None,
strict_mode_policy: 0,
is_looper: false,
is_flushing: false,
out_flush_epoch: 0,
call_restriction: ProcessState::as_self().call_restriction(),
driver: ProcessState::as_self().driver(),
}
}
pub(crate) fn set_strict_mode_policy(&mut self, policy: i32) {
self.strict_mode_policy = policy;
}
fn unflushed_mark(&self) -> (u64, usize) {
(self.out_flush_epoch, self.out_parcel.data_size())
}
pub(crate) fn _strict_mode_policy(&self) -> i32 {
self.strict_mode_policy
}
pub(crate) fn last_transaction_binder_flags(&self) -> u32 {
match self.transaction {
Some(tr) => tr.last_transaction_binder_flags,
None => 0,
}
}
fn is_process_pending_derefs(&mut self) -> bool {
self.in_parcel.data_position() >= self.in_parcel.data_size()
}
fn clear_propagate_work_source(&mut self) {
if let Some(ref mut state) = self.transaction {
state.propagate_work_source = false;
}
}
fn clear_calling_work_source(&mut self) {
self.set_calling_work_source_uid(UNSET_WORK_SOURCE as _);
}
fn set_calling_work_source_uid(&mut self, uid: binder::uid_t) -> i64 {
let token = self.set_calling_work_source_uid_without_propagation(uid);
if let Some(ref mut state) = self.transaction {
state.propagate_work_source = true;
}
token
}
pub(crate) fn set_calling_work_source_uid_without_propagation(
&mut self,
uid: binder::uid_t,
) -> i64 {
match self.transaction {
Some(ref mut state) => {
let propagated_bit =
(state.propagate_work_source as i64) << WORK_SOURCE_PROPAGATED_BIT_INDEX;
let token = propagated_bit | (state.work_source as i64);
state.work_source = uid;
token
}
None => 0,
}
}
fn write_transaction_data(
&mut self,
cmd: u32,
mut flags: u32,
handle: u32,
code: u32,
data: &Parcel,
status: &i32,
) -> Result<()> {
log::trace!(
"write_transaction_data: {} {flags:X} {handle} {code}\n{:?}",
command_to_str(cmd),
data
);
let mut target = binder_transaction_data__bindgen_ty_1 { ptr: 0 };
target.handle = handle;
let tr = if *status == StatusCode::Ok.into() {
binder_transaction_data {
target,
cookie: 0,
code,
flags,
sender_pid: 0,
sender_euid: 0,
data_size: data.ipc_data_size() as _,
offsets_size: (data.objects.len() * std::mem::size_of::<binder_size_t>()) as _,
data: binder_transaction_data__bindgen_ty_2 {
ptr: binder_transaction_data__bindgen_ty_2__bindgen_ty_1 {
buffer: data.as_ptr() as _,
offsets: data.objects.as_ptr() as _,
},
},
}
} else {
flags |= binder::transaction_flags_TF_STATUS_CODE;
binder_transaction_data {
target,
cookie: 0,
code,
flags,
sender_pid: 0,
sender_euid: 0,
data_size: std::mem::size_of::<i32>() as _,
offsets_size: 0,
data: binder_transaction_data__bindgen_ty_2 {
ptr: binder_transaction_data__bindgen_ty_2__bindgen_ty_1 {
buffer: status as *const i32 as _,
offsets: 0,
},
},
}
};
let start = self.out_parcel.data_size();
self.out_parcel.write_cmd::<u32>(&cmd)?;
if let Err(e) = self.out_parcel.write_transaction(&tr) {
let _ = self.out_parcel.set_data_size(start);
return Err(e);
}
Ok(())
}
}
pub(crate) fn set_call_restriction(call_restriction: CallRestriction) {
THREAD_STATE.with(|thread_state| {
thread_state.borrow_mut().call_restriction = call_restriction;
})
}
pub(crate) fn call_restriction() -> CallRestriction {
THREAD_STATE.with(|thread_state| thread_state.borrow().call_restriction)
}
pub fn set_strict_mode_policy(policy: i32) {
if !ProcessState::is_initialized() {
return;
}
THREAD_STATE.with(|thread_state| {
thread_state.borrow_mut().set_strict_mode_policy(policy);
})
}
pub fn get_strict_mode_policy() -> i32 {
if !ProcessState::is_initialized() {
return 0;
}
THREAD_STATE.with(|thread_state| thread_state.borrow().strict_mode_policy)
}
pub(crate) fn should_propagate_work_source() -> bool {
THREAD_STATE.with(|thread_state| {
thread_state
.borrow()
.transaction
.is_some_and(|state| state.propagate_work_source)
})
}
pub(crate) fn calling_work_source_uid() -> binder::uid_t {
THREAD_STATE.with(|thread_state| {
thread_state
.borrow()
.transaction
.map_or(0, |state| state.work_source)
})
}
pub(crate) fn _setup_polling() -> Result<()> {
THREAD_STATE.with(|thread_state| -> Result<()> {
thread_state
.borrow_mut()
.out_parcel
.write_cmd::<u32>(&binder::BC_ENTER_LOOPER)
})?;
flush_commands()?;
Ok(())
}
enum UntilResponse {
Reply,
TransactionComplete,
#[allow(dead_code)]
AcquireResult,
}
fn wait_for_response(until: UntilResponse) -> Result<Option<Parcel>> {
THREAD_STATE.with(|thread_state| -> Result<Option<Parcel>> {
loop {
talk_with_driver(true)?;
if thread_state.borrow().in_parcel.is_empty() {
continue;
}
let cmd: u32 = thread_state.borrow_mut().in_parcel.read_cmd::<i32>()? as _;
log::trace!("{:?}", return_to_str(cmd));
match cmd {
binder::BR_ONEWAY_SPAM_SUSPECT => {
log::error!("Process seems to be sending too many oneway calls.");
log::error!("{}", Backtrace::capture());
if let UntilResponse::TransactionComplete = until {
break;
}
}
binder::BR_TRANSACTION_COMPLETE => {
if let UntilResponse::TransactionComplete = until {
break;
}
}
binder::BR_TRANSACTION_PENDING_FROZEN => {
log::warn!("Sending oneway calls to frozen process.");
break;
}
binder::BR_DEAD_REPLY => {
return Err(StatusCode::DeadObject);
}
binder::BR_FAILED_REPLY => {
log::error!(
"Received FAILED_REPLY transaction reply for pid {}",
thread_state
.borrow()
.transaction
.map_or(0, |state| state.calling_pid)
);
return Err(StatusCode::FailedTransaction);
}
binder::BR_FROZEN_REPLY => {
log::error!(
"Received FROZEN_REPLY transaction reply for pid {}",
thread_state
.borrow()
.transaction
.map_or(0, |state| state.calling_pid)
);
return Err(StatusCode::FailedTransaction);
}
binder::BR_ACQUIRE_RESULT => {
let result = thread_state.borrow_mut().in_parcel.read_cmd::<i32>()?;
if let UntilResponse::AcquireResult = until {
let res = if result != 0 {
Ok(None)
} else {
Err(StatusCode::InvalidOperation)
};
return res;
} else if cfg!(debug_assertions) {
panic!("Unexpected BR_ACQUIRE_RESULT");
}
}
binder::BR_REPLY => {
let tr = thread_state.borrow_mut().in_parcel.read_transaction()?;
let (buffer, offsets) = unsafe { (tr.data.ptr.buffer, tr.data.ptr.offsets) };
if let UntilResponse::Reply = until {
if (tr.flags & transaction_flags_TF_STATUS_CODE) == 0 {
let reply = unsafe {
Parcel::from_ipc_parts(
buffer as _,
tr.data_size as _,
offsets as _,
(tr.offsets_size as usize)
/ std::mem::size_of::<binder::binder_size_t>(),
free_buffer,
)
};
return Ok(Some(reply));
} else {
let status: StatusCode =
if tr.data_size >= std::mem::size_of::<i32>() as u64 {
unsafe { (*(buffer as *const i32)).into() }
} else {
log::error!(
"Buffer too small for status code: {} < {}",
tr.data_size,
std::mem::size_of::<i32>()
);
StatusCode::BadValue
};
log::trace!("binder::BR_REPLY ({status})");
free_buffer(
None,
buffer,
tr.data_size as _,
offsets,
(tr.offsets_size as usize) / std::mem::size_of::<binder_size_t>(),
)?;
if status != StatusCode::Ok {
log::warn!("binder::BR_REPLY ({status})");
return Err(status);
}
return Ok(Some(Parcel::new()));
}
} else {
free_buffer(
None,
buffer,
tr.data_size as _,
offsets,
(tr.offsets_size as usize) / std::mem::size_of::<binder_size_t>(),
)?;
}
}
_ => {
execute_command(cmd as _)?;
}
};
}
Ok(None)
})
}
fn drive_dead_binder_handshake<O, Q, P>(
handle: binder::binder_uintptr_t,
obituary: O,
queue_done: Q,
pin_release: P,
) -> Result<()>
where
O: FnOnce() -> Result<()>,
Q: FnOnce() -> Result<()>,
P: FnOnce() -> Result<()>,
{
let obituary_result = obituary();
if let Err(qe) = queue_done() {
if let Err(oe) = &obituary_result {
error!(
"BR_DEAD_BINDER: queue BC_DEAD_BINDER_DONE failed ({qe:?}) \
swallowing obituary error {oe:?} for handle {handle:X}"
);
}
return Err(qe);
}
let pin_result = pin_release();
if let Err(e) = &pin_result {
error!(
"release_obituary_pin failed for handle {handle:X}: {e:?}; \
obituary_result: {obituary_result:?}"
);
}
obituary_result?;
pin_result?;
Ok(())
}
fn dispatch_transact_caught(
transactable: &dyn Transactable,
code: TransactionCode,
reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
transactable.transact(code, reader, reply)
}));
match result {
Ok(transact_result) => transact_result,
Err(payload) => {
let msg = payload
.downcast_ref::<&'static str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic payload>");
error!("Transactable::transact panicked for code {code}: {msg}");
*reply = Parcel::new();
Err(StatusCode::Unknown)
}
}
}
fn execute_command(cmd: i32) -> Result<()> {
let cmd: std::os::raw::c_uint = cmd as _;
THREAD_STATE.with(|thread_state| -> Result<()> {
match cmd {
binder::BR_ERROR => {
let other: StatusCode = thread_state
.borrow_mut()
.in_parcel
.read_cmd::<i32>()?
.into();
log::error!("binder::BR_ERROR ({other})");
return Err(other);
}
binder::BR_OK => {}
binder::BR_TRANSACTION_SEC_CTX | binder::BR_TRANSACTION => {
let tr_secctx = {
let mut thread_state = thread_state.borrow_mut();
if cmd == binder::BR_TRANSACTION_SEC_CTX {
thread_state.in_parcel.read_transaction_secctx()?
} else {
binder::binder_transaction_data_secctx {
transaction_data: thread_state.in_parcel.read_transaction()?,
secctx: 0,
}
}
};
let mut reader = unsafe {
let tr = &tr_secctx.transaction_data;
Parcel::from_ipc_parts(
tr.data.ptr.buffer as _,
tr.data_size as _,
tr.data.ptr.offsets as _,
(tr.offsets_size as usize) / std::mem::size_of::<binder::binder_size_t>(),
free_buffer,
)
};
let transaction_old = {
let mut thread_state = thread_state.borrow_mut();
let transaction_old = thread_state.transaction;
thread_state.clear_calling_work_source();
thread_state.clear_propagate_work_source();
thread_state.transaction =
Some(TransactionState::from_transaction_data(&tr_secctx));
transaction_old
};
let mut reply = Parcel::new();
let result = {
let target_ptr = unsafe { tr_secctx.transaction_data.target.ptr };
if target_ptr != 0 {
let id = target_ptr;
match ProcessState::as_self().lookup_native(id) {
Some(arc) => {
let strong = SIBinder::from_arc(arc);
if strong.attempt_increase() {
let result = match strong.as_transactable() {
Some(t) => dispatch_transact_caught(
t,
tr_secctx.transaction_data.code,
&mut reader,
&mut reply,
),
None => {
log::error!("native id {id} is not Transactable");
Err(StatusCode::UnknownTransaction)
}
};
if let Err(e) = strong.decrease() {
log::error!("dec_strong failed for native id {id}: {e:?}");
}
result
} else {
log::warn!("Failed strong.attempt_increase for native id {id}");
Err(StatusCode::UnknownTransaction)
}
}
None => {
log::error!("BR_TRANSACTION for unknown native id {id}");
Err(StatusCode::DeadObject)
}
}
} else {
match ProcessState::as_self().context_manager() {
Some(context) => match context.as_transactable() {
Some(t) => dispatch_transact_caught(
t,
tr_secctx.transaction_data.code,
&mut reader,
&mut reply,
),
None => {
log::error!("context manager is not Transactable");
Err(StatusCode::UnknownTransaction)
}
},
None => {
log::error!(
"BR_TRANSACTION to handle 0 but no context manager set"
);
Err(StatusCode::DeadObject)
}
}
}
};
let flags = tr_secctx.transaction_data.flags;
let queued_at = thread_state.borrow().unflushed_mark();
let reply_result =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| -> Result<()> {
if (flags & transaction_flags_TF_ONE_WAY) == 0 {
let flags = flags & transaction_flags_TF_CLEAR_BUF;
let status: i32 = match result {
Ok(_) => StatusCode::Ok.into(),
Err(err) => err.into(),
};
thread_state.borrow_mut().write_transaction_data(
binder::BC_REPLY,
flags,
u32::MAX,
0,
&reply,
&status,
)?;
if let Err(e) = wait_for_response(UntilResponse::TransactionComplete) {
discard_unflushed_commands(thread_state, queued_at, true);
return Err(e);
}
} else if let Err(err) = result {
let mut log = format!(
"oneway function results for code {} on binder at {:X}",
tr_secctx.transaction_data.code,
unsafe { tr_secctx.transaction_data.target.ptr }
);
log += &format!(" will be dropped but finished with status {err}");
if reply.data_size() != 0 {
log += &format!(" and reply parcel size {}", reply.data_size());
}
log::error!("{log}");
}
Ok(())
}));
thread_state.borrow_mut().transaction = transaction_old;
match reply_result {
Ok(inner) => inner?,
Err(_payload) => {
discard_unflushed_commands(thread_state, queued_at, false);
log::error!(
"reply path panicked for code {}; reply dropped",
tr_secctx.transaction_data.code
);
return Err(StatusCode::Unknown);
}
}
}
binder::BR_INCREFS => {
let mut state = thread_state.borrow_mut();
let id = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
let cookie_echo = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
drop(state);
if ProcessState::as_self().ref_native_kernel(id).is_none() {
log::error!("BR_INCREFS for unknown native id {id}");
debug_assert!(false, "BR_INCREFS for unknown native id {id}");
}
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&binder::BC_INCREFS_DONE)?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&id)?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&cookie_echo)?;
}
binder::BR_ACQUIRE => {
let mut state = thread_state.borrow_mut();
let id = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
let cookie_echo = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
drop(state);
if ProcessState::as_self().ref_native_kernel(id).is_none() {
log::error!("BR_ACQUIRE for unknown native id {id}");
debug_assert!(false, "BR_ACQUIRE for unknown native id {id}");
}
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&(binder::BC_ACQUIRE_DONE))?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&id)?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&cookie_echo)?;
}
binder::BR_RELEASE => {
let mut state = thread_state.borrow_mut();
let id = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
let _cookie_echo = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
BINDER_DEREFS.with(|binder_derefs| {
let mut binder_derefs = binder_derefs.borrow_mut();
binder_derefs.pending_strong_derefs.push_back(id);
});
}
binder::BR_DECREFS => {
let mut state = thread_state.borrow_mut();
let id = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
let _cookie_echo = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
BINDER_DEREFS.with(|binder_derefs| {
let mut binder_derefs = binder_derefs.borrow_mut();
binder_derefs.pending_weak_derefs.push_back(id);
});
}
binder::BR_ATTEMPT_ACQUIRE => {
let mut state = thread_state.borrow_mut();
let id = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
let _cookie_echo = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
drop(state);
let success = ProcessState::as_self().ref_native_kernel(id).is_some();
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&binder::BC_ACQUIRE_RESULT)?;
state.out_parcel.write_cmd::<i32>(&(success as _))?;
}
binder::BR_NOOP => {}
binder::BR_SPAWN_LOOPER => {
ProcessState::as_self().spawn_pooled_thread(false);
}
binder::BR_FINISHED => {
return Err(StatusCode::TimedOut);
}
binder::BR_DEAD_BINDER => {
let handle = {
let mut state = thread_state.borrow_mut();
state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?
};
log::trace!("BR_DEAD_BINDER: handle {handle:X}");
drive_dead_binder_handshake(
handle,
|| ProcessState::as_self().send_obituary_for_handle(handle as _),
|| {
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&(binder::BC_DEAD_BINDER_DONE))?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&handle)?;
Ok(())
},
|| ProcessState::as_self().release_obituary_pin(handle as _),
)?;
}
binder::BR_CLEAR_DEATH_NOTIFICATION_DONE => {
let mut state = thread_state.borrow_mut();
state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
}
_ => {
log::error!("*** BAD COMMAND {cmd} received from Binder driver\n");
return Err(StatusCode::Unknown);
}
};
Ok(())
})
}
fn describe_refused_command(out: &mut CommandStream, stopped_at: usize) -> String {
let total = out.data_size();
if stopped_at >= total {
return format!("consumed {stopped_at} of {total}");
}
let saved = out.data_position();
out.set_data_position(stopped_at);
let named = match out.read_cmd::<u32>() {
Ok(cmd) => format!("{} ({cmd:#x})", command_to_str(cmd)),
Err(e) => format!("<unreadable: {e}>"),
};
out.set_data_position(saved);
format!("consumed {stopped_at} of {total}; driver stopped at {named}")
}
fn talk_with_driver(do_receive: bool) -> Result<()> {
THREAD_STATE.with(|thread_state| -> Result<()> {
let mut bwr = {
let mut thread_state = thread_state.borrow_mut();
let need_read = thread_state.in_parcel.is_empty();
let out_avail = if !do_receive || need_read {
thread_state.out_parcel.data_size()
} else {
0
};
let read_size = if do_receive && need_read {
thread_state.in_parcel.capacity()
} else {
0
};
binder::binder_write_read {
write_size: out_avail as _,
write_consumed: 0,
write_buffer: thread_state.out_parcel.as_mut_ptr() as _,
read_size: read_size as _,
read_consumed: 0,
read_buffer: thread_state.in_parcel.as_mut_ptr() as _,
}
};
if bwr.write_size == 0 && bwr.read_size == 0 {
return Ok(());
}
if bwr.write_size != 0 {
log::trace!(
"Sending command to driver:\n{:?}",
thread_state.borrow().out_parcel
);
log::trace!(
"Size of receive buffer: {}, need_read: {}, do_receive: {}",
bwr.read_size,
thread_state.borrow().in_parcel.is_empty(),
do_receive
);
}
ensure_thread_exit_guard(&thread_state.borrow().driver);
loop {
let res = binder::write_read(&thread_state.borrow().driver, &mut bwr);
match res {
Ok(_) => break,
Err(errno) if errno != rustix::io::Errno::INTR => {
let detail = describe_refused_command(
&mut thread_state.borrow_mut().out_parcel,
bwr.write_consumed as _,
);
log::error!("binder::write_read() error : {errno}; {detail}");
return Err(StatusCode::from(errno));
}
_ => {}
}
}
log::trace!(
"write consumed: {} of {}, read consumed: {} of {}",
bwr.write_consumed,
bwr.write_size,
bwr.read_consumed,
bwr.read_size
);
{
let mut thread_state = thread_state.borrow_mut();
if bwr.write_consumed > 0 {
if bwr.write_consumed < thread_state.out_parcel.data_size() as _ {
let detail = describe_refused_command(
&mut thread_state.out_parcel,
bwr.write_consumed as _,
);
eprintln!(
"rsbinder FATAL: driver did not consume the write buffer — {detail}\n\
The remainder was never seen by the kernel, so the reference\n\
counts and buffer ownership this process believes in are no\n\
longer the kernel's. Queued commands:\n{:?}",
thread_state.out_parcel
);
std::process::abort();
}
thread_state.out_parcel.set_data_size(0)?;
thread_state.out_flush_epoch += 1;
}
if bwr.read_consumed > 0 {
unsafe {
thread_state
.in_parcel
.set_data_size_driver_filled(bwr.read_consumed as _)?;
}
thread_state.in_parcel.set_data_position(0);
log::trace!(
"Received commands from driver:\n{:?}",
thread_state.in_parcel
);
}
}
Ok(())
})
}
fn get_and_execute_command() -> Result<()> {
talk_with_driver(true)?;
let cmd = THREAD_STATE.with(|thread_state| -> Result<i32> {
thread_state.borrow_mut().in_parcel.read_cmd::<i32>()
})?;
execute_command(cmd)?;
Ok(())
}
pub(crate) fn flush_commands() -> Result<()> {
talk_with_driver(false)?;
THREAD_STATE.with(|thread_state| -> Result<()> {
if thread_state.borrow().out_parcel.data_size() > 0 {
talk_with_driver(false)?;
}
if thread_state.borrow().out_parcel.data_size() > 0 {
log::warn!("self.out_parcel.len() > 0 after flush_commands()");
}
Ok(())
})
}
pub(crate) fn inc_strong_handle(handle: u32) -> Result<()> {
log::trace!("inc_strong_handle: {handle}");
THREAD_STATE.with(|thread_state| -> Result<()> {
{
let mut state = thread_state.borrow_mut();
state.out_parcel.write_cmd::<u32>(&(binder::BC_ACQUIRE))?;
state.out_parcel.write_cmd::<u32>(&(handle))?;
}
flush_if_needed()?;
Ok(())
})
}
pub(crate) fn dec_strong_handle(handle: u32) -> Result<()> {
log::trace!("dec_strong_handle: {handle}");
THREAD_STATE.with(|thread_state| -> Result<()> {
{
let mut state = thread_state.borrow_mut();
state.out_parcel.write_cmd::<u32>(&(binder::BC_RELEASE))?;
state.out_parcel.write_cmd::<u32>(&(handle))?;
}
flush_if_needed()?;
Ok(())
})
}
pub(crate) fn inc_weak_handle(handle: u32) -> Result<()> {
log::trace!("inc_weak_handle: {handle}");
THREAD_STATE.with(|thread_state| -> Result<()> {
{
let mut state = thread_state.borrow_mut();
state.out_parcel.write_cmd::<u32>(&(binder::BC_INCREFS))?;
state.out_parcel.write_cmd::<u32>(&(handle))?;
}
flush_if_needed()?;
Ok(())
})
}
pub(crate) fn dec_weak_handle(handle: u32) -> Result<()> {
log::trace!("dec_weak_handle: {handle}");
THREAD_STATE.with(|thread_state| -> Result<()> {
{
let mut state = thread_state.borrow_mut();
state.out_parcel.write_cmd::<u32>(&(binder::BC_DECREFS))?;
state.out_parcel.write_cmd::<u32>(&(handle))?;
}
flush_if_needed()?;
Ok(())
})
}
pub(crate) fn flush_if_needed() -> Result<bool> {
THREAD_STATE.with(|thread_state| -> Result<bool> {
{
let thread_state = thread_state.borrow();
if thread_state.is_looper || thread_state.is_flushing {
return Ok(false);
}
}
struct FlushGuard;
impl Drop for FlushGuard {
fn drop(&mut self) {
THREAD_STATE.with(|ts| ts.borrow_mut().is_flushing = false);
}
}
thread_state.borrow_mut().is_flushing = true;
let _guard = FlushGuard;
flush_commands()?;
Ok(true)
})
}
pub(crate) fn _handle_commands() -> Result<()> {
while {
get_and_execute_command()?;
THREAD_STATE.with(|thread_state| -> bool { !thread_state.borrow().in_parcel.is_empty() })
} {
flush_commands()?;
}
Ok(())
}
pub(crate) fn check_interface(reader: &mut Parcel, descriptor: &str) -> Result<bool> {
let mut strict_policy: i32 = reader.read()?;
THREAD_STATE.with(|thread_state| -> Result<()> {
let mut thread_state = thread_state.borrow_mut();
if (thread_state.last_transaction_binder_flags() & FLAG_ONEWAY) != 0 {
strict_policy = 0;
}
thread_state.set_strict_mode_policy(strict_policy);
reader.update_work_source_request_header_pos();
let work_source: i32 = reader.read()?;
thread_state.set_calling_work_source_uid_without_propagation(work_source as _);
Ok(())
})?;
if crate::sdk_at_least(30) {
let header: u32 = reader.read()?;
if header != INTERFACE_HEADER {
log::error!("Expecting header {INTERFACE_HEADER:#x} but found {header:#x}.");
return Ok(false);
}
}
let parcel_interface: String = reader.read()?;
if parcel_interface.eq(descriptor) {
Ok(true)
} else {
log::error!("check_interface() expected '{descriptor}' but read '{parcel_interface}'");
Ok(false)
}
}
pub(crate) fn transact(
handle: u32,
code: u32,
data: &Parcel,
mut flags: u32,
) -> Result<Option<Parcel>> {
flags |= transaction_flags_TF_ACCEPT_FDS;
if (flags & transaction_flags_TF_ONE_WAY) == 0 {
match call_restriction() {
CallRestriction::ErrorIfNotOneway => {
error!("Process making non-oneway call (code: {code}) but is restricted.")
}
CallRestriction::FatalIfNotOneway => {
panic!("Process may not make non-oneway calls (code: {code}).");
}
_ => (),
}
}
let queued_at = THREAD_STATE.with(|thread_state| -> Result<(u64, usize)> {
let mut thread_state = thread_state.borrow_mut();
let queued_at = thread_state.unflushed_mark();
thread_state.write_transaction_data(
binder::BC_TRANSACTION,
flags,
handle,
code,
data,
&0,
)?;
Ok(queued_at)
})?;
let waited = if (flags & transaction_flags_TF_ONE_WAY) == 0 {
wait_for_response(UntilResponse::Reply)
} else {
wait_for_response(UntilResponse::TransactionComplete)
};
match waited {
Ok(reply) => Ok(reply),
Err(e) => {
THREAD_STATE
.with(|thread_state| discard_unflushed_commands(thread_state, queued_at, false));
Err(e)
}
}
}
fn discard_unflushed_commands(
thread_state: &RefCell<ThreadState>,
queued_at: (u64, usize),
retry_flush: bool,
) {
if retry_flush {
if let Err(e) = talk_with_driver(false) {
log::warn!("flush after failed reply also failed: {e}");
}
}
let (epoch, queued_at) = queued_at;
let mut ts = thread_state.borrow_mut();
if ts.out_flush_epoch == epoch && ts.out_parcel.data_size() > queued_at {
log::error!(
"discarding {} unflushed out_parcel bytes that reference released memory",
ts.out_parcel.data_size() - queued_at
);
let _ = ts.out_parcel.set_data_size(queued_at);
}
}
fn free_buffer(
parcel: Option<&Parcel>,
data: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
if let Some(parcel) = parcel {
parcel.close_file_descriptors()
}
THREAD_STATE.with(|thread_state| -> Result<()> {
let mut thread_state = thread_state.borrow_mut();
thread_state
.out_parcel
.write_cmd::<u32>(&binder::BC_FREE_BUFFER)?;
thread_state
.out_parcel
.write_cmd::<binder_uintptr_t>(&data)?;
Ok(())
})?;
flush_if_needed()?;
Ok(())
}
pub(crate) fn query_interface(handle: u32) -> Result<String> {
#[cfg(all(target_os = "android", feature = "android_10"))]
if handle == 0 && !crate::sdk_at_least(30) {
return Ok(crate::hub::android_10::SERVICE_MANAGER_DESCRIPTOR.to_owned());
}
let data = Parcel::new();
let reply = transact(handle, INTERFACE_TRANSACTION, &data, 0)?;
let interface: Option<String> = reply.ok_or(StatusCode::UnexpectedNull)?.read()?;
Ok(interface.unwrap_or_default())
}
pub(crate) fn ping_binder(handle: u32) -> Result<()> {
let data = Parcel::new();
let _reply = transact(handle, PING_TRANSACTION, &data, 0)?;
Ok(())
}
pub(crate) fn join_thread_pool(is_main: bool) -> Result<()> {
THREAD_STATE.with(|thread_state| -> Result<()> {
log::debug!(
"**** THREAD {:?} (PID {}) IS JOINING THE THREAD POOL",
std::thread::current().id(),
std::process::id()
);
ProcessState::as_self()
.current_threads
.fetch_add(1, Ordering::SeqCst);
struct ThreadCountGuard;
impl Drop for ThreadCountGuard {
fn drop(&mut self) {
ProcessState::as_self()
.current_threads
.fetch_sub(1, Ordering::SeqCst);
}
}
let _thread_count_guard = ThreadCountGuard;
let looper = if is_main {
binder::BC_ENTER_LOOPER
} else {
binder::BC_REGISTER_LOOPER
};
{
let mut thread_state = thread_state.borrow_mut();
thread_state.out_parcel.write_cmd::<u32>(&looper)?;
thread_state.is_looper = true;
}
let result;
loop {
if thread_state.borrow_mut().is_process_pending_derefs() {
process_pending_derefs()?;
}
if let Err(e) = get_and_execute_command() {
match e {
StatusCode::TimedOut if !is_main => {
result = e;
break;
}
StatusCode::Errno(errno)
if errno == -(rustix::io::Errno::CONNREFUSED.raw_os_error()) =>
{
result = e;
break;
}
_ => {
log::error!(
"get_and_execute_command() returned unexpected error {e}; \
leaving the thread pool"
);
result = e;
break;
}
}
}
}
log::debug!(
"**** THREAD {:?} (PID {}) IS LEAVING THE THREAD POOL err={}\n",
std::thread::current().id(),
std::process::id(),
result
);
{
let mut thread_state = thread_state.borrow_mut();
thread_state.is_looper = false;
thread_state
.out_parcel
.write_cmd::<u32>(&binder::BC_EXIT_LOOPER)?;
}
talk_with_driver(false)?;
Ok(())
})
}
pub(crate) fn request_death_notification(handle: u32) -> Result<()> {
log::trace!("request_death_notification: {handle}");
THREAD_STATE.with(|thread_state| -> Result<()> {
{
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&(binder::BC_REQUEST_DEATH_NOTIFICATION))?;
state.out_parcel.write_cmd::<u32>(&(handle))?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&(handle as _))?;
}
Ok(())
})
}
pub(crate) fn clear_death_notification(handle: u32) -> Result<()> {
log::trace!("clear_death_notification: {handle}");
THREAD_STATE.with(|thread_state| -> Result<()> {
{
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&(binder::BC_CLEAR_DEATH_NOTIFICATION))?;
state.out_parcel.write_cmd::<u32>(&(handle))?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&(handle as _))?;
}
Ok(())
})
}
#[derive(Debug)]
pub struct CallingContext {
pub pid: binder::pid_t,
pub uid: binder::uid_t,
pub sid: Option<CString>,
}
impl std::default::Default for CallingContext {
fn default() -> CallingContext {
if let Some((uid, pid)) = rpc_calling() {
return CallingContext {
pid,
uid,
sid: None,
};
}
if !ProcessState::is_initialized() {
return CallingContext {
pid: rustix::process::getpid().as_raw_nonzero().get() as _,
uid: rustix::process::getuid().as_raw(),
sid: None,
};
}
THREAD_STATE.with(|thread_state| -> CallingContext {
let thread_state = thread_state.borrow();
match thread_state.transaction.as_ref() {
Some(transaction) => {
let calling_sid = if !transaction.calling_sid.is_null() {
unsafe { Some(CStr::from_ptr(transaction.calling_sid as _).to_owned()) }
} else {
None
};
CallingContext {
pid: transaction.calling_pid,
uid: transaction.calling_uid,
sid: calling_sid,
}
}
None => {
log::debug!("CallingContext::new() called outside of transaction");
CallingContext {
pid: rustix::process::getpid().as_raw_nonzero().get() as _,
uid: rustix::process::getuid().as_raw(),
sid: None,
}
}
}
})
}
}
pub(crate) fn is_handling_transaction() -> bool {
if rpc_calling().is_some() {
return true;
}
ProcessState::is_initialized()
&& THREAD_STATE.with(|thread_state| thread_state.borrow().transaction.is_some())
}
pub fn get_calling_sid() -> Option<CString> {
if rpc_calling().is_some() || !ProcessState::is_initialized() {
return None;
}
THREAD_STATE.with(|thread_state| {
let thread_state = thread_state.borrow();
let transaction = thread_state.transaction.as_ref()?;
if transaction.calling_sid.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(transaction.calling_sid as _).to_owned() })
})
}
pub fn get_calling_pid() -> binder::pid_t {
if let Some((_, pid)) = rpc_calling() {
return pid;
}
if !ProcessState::is_initialized() {
return 0;
}
THREAD_STATE.with(|thread_state| {
thread_state
.borrow()
.transaction
.as_ref()
.map_or(0, |tr| tr.calling_pid)
})
}
pub fn get_calling_uid() -> binder::uid_t {
if let Some((uid, _)) = rpc_calling() {
return uid;
}
if !ProcessState::is_initialized() {
return 0;
}
THREAD_STATE.with(|thread_state| {
thread_state
.borrow()
.transaction
.as_ref()
.map_or(0, |tr| tr.calling_uid)
})
}
pub(crate) fn get_calling_uid_or_self() -> binder::uid_t {
if let Some((uid, _)) = rpc_calling() {
return uid;
}
if !ProcessState::is_initialized() {
return rustix::process::getuid().as_raw();
}
THREAD_STATE
.with(|thread_state| {
thread_state
.borrow()
.transaction
.as_ref()
.map(|tr| tr.calling_uid)
})
.unwrap_or_else(|| rustix::process::getuid().as_raw())
}
pub fn clear_calling_identity() -> i64 {
if !ProcessState::is_initialized() {
return 0;
}
THREAD_STATE.with(|thread_state| {
let mut thread_state = thread_state.borrow_mut();
let Some(ref mut tr) = thread_state.transaction else {
return 0;
};
let token = pack_calling_identity(tr.has_explicit_identity, tr.calling_uid, tr.calling_pid);
tr.calling_uid = rustix::process::getuid().as_raw();
tr.calling_pid = rustix::process::getpid().as_raw_nonzero().get() as _;
tr.calling_sid = std::ptr::null();
tr.has_explicit_identity = true;
token
})
}
pub fn restore_calling_identity(token: i64) {
if !ProcessState::is_initialized() {
return;
}
THREAD_STATE.with(|thread_state| {
let mut thread_state = thread_state.borrow_mut();
let Some(ref mut tr) = thread_state.transaction else {
return;
};
tr.calling_uid = unpack_calling_uid(token);
tr.calling_pid = unpack_calling_pid(token);
tr.has_explicit_identity = unpack_has_explicit_identity(token);
tr.calling_sid = std::ptr::null();
})
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_current_scheduler_policy() -> Result<i32> {
let raw = unsafe { libc::sched_getscheduler(0) };
if raw < 0 {
let errno = std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::EINVAL);
return Err(StatusCode::from(rustix::io::Errno::from_raw_os_error(
errno,
)));
}
Ok(raw)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn get_current_scheduler_policy() -> Result<i32> {
Err(StatusCode::InvalidOperation)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ExtendedError {
pub id: u32,
pub command: u32,
pub param: i32,
}
pub fn get_extended_error() -> Result<ExtendedError> {
if !ProcessState::is_initialized() {
return Err(StatusCode::InvalidOperation);
}
let mut ee = binder::binder_extended_error::default();
let driver = ProcessState::as_self().driver();
crate::sys::binder::get_extended_error(driver.as_ref(), &mut ee).map_err(|errno| {
if errno == rustix::io::Errno::NOTTY {
StatusCode::InvalidOperation
} else {
StatusCode::from(errno)
}
})?;
Ok(ExtendedError {
id: ee.id,
command: ee.command,
param: ee.param,
})
}
pub fn has_explicit_identity() -> bool {
if !ProcessState::is_initialized() {
return false;
}
THREAD_STATE.with(|thread_state| {
thread_state
.borrow()
.transaction
.as_ref()
.is_some_and(|tr| tr.has_explicit_identity)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "rpc")]
#[test]
fn rpc_calling_context_is_read_restored_and_failclosed() {
use crate::rpc::transport::PeerIdentity;
use std::sync::Arc;
assert_eq!(get_calling_uid(), 0);
assert_eq!(get_calling_pid(), 0);
assert!(!is_handling_transaction());
assert!(get_calling_sid().is_none());
assert!(calling_caller().is_none());
{
let _g = RpcCallingGuard::install(Arc::new(PeerIdentity::Local { uid: 1234, pid: 42 }));
assert_eq!(get_calling_uid(), 1234);
assert_eq!(get_calling_pid(), 42);
assert!(is_handling_transaction());
assert!(get_calling_sid().is_none());
assert_eq!(CallingContext::default().uid, 1234);
assert_eq!(CallingContext::default().pid, 42);
match calling_caller() {
Some(Caller::Rpc(PeerIdentity::Local { uid, pid })) => {
assert_eq!((uid, pid), (1234, 42));
}
other => panic!("expected Caller::Rpc(Local), got {other:?}"),
}
{
let _g2 = RpcCallingGuard::install(Arc::new(PeerIdentity::Vsock { cid: 7 }));
assert_eq!(get_calling_uid(), RPC_UNKNOWN_CALLING_UID);
assert_ne!(get_calling_uid(), 0, "sentinel must never read as root");
assert_eq!(get_calling_pid(), -1);
assert!(matches!(
calling_caller(),
Some(Caller::Rpc(PeerIdentity::Vsock { cid: 7 }))
));
}
assert_eq!(
get_calling_uid(),
1234,
"outer identity restored after nesting"
);
}
assert_eq!(get_calling_uid(), 0);
assert!(!is_handling_transaction());
assert!(calling_caller().is_none());
}
#[test]
fn test_return_to_str() {
assert_eq!(return_to_str(binder::BR_OK), "BR_OK");
assert_eq!(return_to_str(binder::BR_TRANSACTION), "BR_TRANSACTION");
assert_eq!(return_to_str(binder::BR_REPLY), "BR_REPLY");
assert_eq!(return_to_str(binder::BR_ACQUIRE), "BR_ACQUIRE");
assert_eq!(return_to_str(binder::BR_INCREFS), "BR_INCREFS");
assert_eq!(
return_to_str(binder::BR_ACQUIRE_RESULT),
"BR_ACQUIRE_RESULT"
);
assert_eq!(return_to_str(binder::BR_DEAD_BINDER), "BR_DEAD_BINDER");
assert_eq!(
return_to_str(binder::BR_CLEAR_DEATH_NOTIFICATION_DONE),
"BR_CLEAR_DEATH_NOTIFICATION_DONE"
);
assert_eq!(return_to_str(binder::BR_FAILED_REPLY), "BR_FAILED_REPLY");
assert_eq!(return_to_str(binder::BR_DEAD_REPLY), "BR_DEAD_REPLY");
assert_eq!(return_to_str(binder::BR_FINISHED), "BR_FINISHED");
assert_eq!(return_to_str(binder::BR_SPAWN_LOOPER), "BR_SPAWN_LOOPER");
assert_eq!(
return_to_str(binder::BR_ATTEMPT_ACQUIRE),
"BR_ATTEMPT_ACQUIRE"
);
assert_eq!(return_to_str(binder::BR_NOOP), "BR_NOOP");
assert_eq!(return_to_str(binder::BR_SPAWN_LOOPER), "BR_SPAWN_LOOPER");
assert_eq!(return_to_str(binder::BR_ERROR), "BR_ERROR");
assert_eq!(return_to_str(binder::BR_DEAD_REPLY), "BR_DEAD_REPLY");
assert_eq!(return_to_str(binder::BR_FAILED_REPLY), "BR_FAILED_REPLY");
assert_eq!(return_to_str(binder::BR_FROZEN_REPLY), "BR_FROZEN_REPLY");
assert_eq!(
return_to_str(binder::BR_TRANSACTION_SEC_CTX),
"BR_TRANSACTION_SEC_CTX"
);
assert_eq!(return_to_str(binder::BR_DECREFS), "BR_DECREFS");
assert_eq!(
return_to_str(binder::BR_TRANSACTION_COMPLETE),
"BR_TRANSACTION_COMPLETE"
);
assert_eq!(
return_to_str(binder::BR_ONEWAY_SPAM_SUSPECT),
"BR_ONEWAY_SPAM_SUSPECT"
);
assert_eq!(
return_to_str(binder::BR_TRANSACTION_PENDING_FROZEN),
"BR_TRANSACTION_PENDING_FROZEN"
);
assert_eq!(return_to_str(binder::BR_FROZEN_BINDER), "BR_FROZEN_BINDER");
assert_eq!(
return_to_str(binder::BR_CLEAR_FREEZE_NOTIFICATION_DONE),
"BR_CLEAR_FREEZE_NOTIFICATION_DONE"
);
}
#[test]
fn freeze_observer_br_constants_match_uapi() {
assert_eq!(binder::BR_TRANSACTION_PENDING_FROZEN, 29204);
assert_eq!(binder::BR_FROZEN_BINDER, 2148561429);
assert_eq!(binder::BR_CLEAR_FREEZE_NOTIFICATION_DONE, 2148037142);
}
#[test]
fn freeze_observer_bc_constants_match_uapi() {
assert_eq!(binder::BC_REQUEST_FREEZE_NOTIFICATION, 1074553619);
assert_eq!(binder::BC_CLEAR_FREEZE_NOTIFICATION, 1074553620);
assert_eq!(binder::BC_FREEZE_NOTIFICATION_DONE, 1074291477);
}
#[test]
fn binder_frozen_state_info_layout() {
use crate::sys::binder_frozen_state_info;
assert_eq!(std::mem::size_of::<binder_frozen_state_info>(), 16);
assert_eq!(std::mem::align_of::<binder_frozen_state_info>(), 8);
}
#[test]
fn test_command_to_str() {
assert_eq!(command_to_str(binder::BC_TRANSACTION), "BC_TRANSACTION");
assert_eq!(command_to_str(binder::BC_REPLY), "BC_REPLY");
assert_eq!(
command_to_str(binder::BC_ACQUIRE_RESULT),
"BC_ACQUIRE_RESULT"
);
assert_eq!(command_to_str(binder::BC_FREE_BUFFER), "BC_FREE_BUFFER");
assert_eq!(command_to_str(binder::BC_INCREFS), "BC_INCREFS");
assert_eq!(command_to_str(binder::BC_ACQUIRE), "BC_ACQUIRE");
assert_eq!(command_to_str(binder::BC_RELEASE), "BC_RELEASE");
assert_eq!(command_to_str(binder::BC_DECREFS), "BC_DECREFS");
assert_eq!(command_to_str(binder::BC_INCREFS_DONE), "BC_INCREFS_DONE");
assert_eq!(command_to_str(binder::BC_ACQUIRE_DONE), "BC_ACQUIRE_DONE");
assert_eq!(
command_to_str(binder::BC_ATTEMPT_ACQUIRE),
"BC_ATTEMPT_ACQUIRE"
);
assert_eq!(
command_to_str(binder::BC_REGISTER_LOOPER),
"BC_REGISTER_LOOPER"
);
assert_eq!(command_to_str(binder::BC_ENTER_LOOPER), "BC_ENTER_LOOPER");
assert_eq!(command_to_str(binder::BC_EXIT_LOOPER), "BC_EXIT_LOOPER");
assert_eq!(
command_to_str(binder::BC_REQUEST_DEATH_NOTIFICATION),
"BC_REQUEST_DEATH_NOTIFICATION"
);
assert_eq!(
command_to_str(binder::BC_CLEAR_DEATH_NOTIFICATION),
"BC_CLEAR_DEATH_NOTIFICATION"
);
assert_eq!(
command_to_str(binder::BC_DEAD_BINDER_DONE),
"BC_DEAD_BINDER_DONE"
);
assert_eq!(
command_to_str(binder::BC_TRANSACTION_SG),
"BC_TRANSACTION_SG"
);
assert_eq!(command_to_str(binder::BC_REPLY_SG), "BC_REPLY_SG");
assert_eq!(
command_to_str(binder::BC_REQUEST_FREEZE_NOTIFICATION),
"BC_REQUEST_FREEZE_NOTIFICATION"
);
assert_eq!(
command_to_str(binder::BC_CLEAR_FREEZE_NOTIFICATION),
"BC_CLEAR_FREEZE_NOTIFICATION"
);
assert_eq!(
command_to_str(binder::BC_FREEZE_NOTIFICATION_DONE),
"BC_FREEZE_NOTIFICATION_DONE"
);
}
#[test]
fn test_dispatch_transact_caught_isolates_panic() {
struct PanickingTransactable;
impl Transactable for PanickingTransactable {
fn transact(
&self,
_code: TransactionCode,
_reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
reply.write::<i32>(&0x6EAD_BEEFi32).ok();
panic!("simulated transactable panic");
}
}
let mut reader = Parcel::new();
let mut reply = Parcel::new();
let result = dispatch_transact_caught(&PanickingTransactable, 1, &mut reader, &mut reply);
assert!(
matches!(result, Err(StatusCode::Unknown)),
"expected Err(StatusCode::Unknown), got {result:?}"
);
assert_eq!(
reply.data_size(),
0,
"partial reply must be discarded after a panic so the \
client does not misparse half-formed data"
);
}
#[test]
fn test_dispatch_transact_caught_propagates_normal_result() {
struct OkTransactable;
impl Transactable for OkTransactable {
fn transact(
&self,
_code: TransactionCode,
_reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
reply.write::<i32>(&42i32)?;
Ok(())
}
}
struct ErrTransactable;
impl Transactable for ErrTransactable {
fn transact(
&self,
_code: TransactionCode,
_reader: &mut Parcel,
_reply: &mut Parcel,
) -> Result<()> {
Err(StatusCode::PermissionDenied)
}
}
let mut reader = Parcel::new();
let mut reply = Parcel::new();
assert!(dispatch_transact_caught(&OkTransactable, 1, &mut reader, &mut reply).is_ok());
assert_eq!(reply.data_size(), std::mem::size_of::<i32>());
let mut reply = Parcel::new();
let err = dispatch_transact_caught(&ErrTransactable, 1, &mut reader, &mut reply);
assert!(matches!(err, Err(StatusCode::PermissionDenied)));
}
#[test]
fn test_drive_dead_binder_handshake_orchestration() {
use std::cell::RefCell;
let order = RefCell::new(Vec::<&'static str>::new());
let push = |label: &'static str| order.borrow_mut().push(label);
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Ok(())
},
|| {
push("queue");
Ok(())
},
|| {
push("pin");
Ok(())
},
);
assert!(result.is_ok());
assert_eq!(*order.borrow(), vec!["obituary", "queue", "pin"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Err(StatusCode::DeadObject)
},
|| {
push("queue");
Ok(())
},
|| {
push("pin");
Ok(())
},
);
assert!(matches!(result, Err(StatusCode::DeadObject)));
assert_eq!(*order.borrow(), vec!["obituary", "queue", "pin"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Ok(())
},
|| {
push("queue");
Err(StatusCode::NoMemory)
},
|| {
push("pin");
Ok(())
},
);
assert!(matches!(result, Err(StatusCode::NoMemory)));
assert_eq!(*order.borrow(), vec!["obituary", "queue"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Ok(())
},
|| {
push("queue");
Ok(())
},
|| {
push("pin");
Err(StatusCode::DeadObject)
},
);
assert!(matches!(result, Err(StatusCode::DeadObject)));
assert_eq!(*order.borrow(), vec!["obituary", "queue", "pin"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Err(StatusCode::PermissionDenied)
},
|| {
push("queue");
Ok(())
},
|| {
push("pin");
Err(StatusCode::DeadObject)
},
);
assert!(
matches!(result, Err(StatusCode::PermissionDenied)),
"obituary error must take priority over pin error, got {result:?}"
);
assert_eq!(*order.borrow(), vec!["obituary", "queue", "pin"]);
}
#[test]
#[cfg_attr(
not(any(target_os = "linux", target_os = "android")),
ignore = "requires /dev/binder"
)]
#[serial_test::serial(binder)]
fn test_process_pending_derefs_handles_reentrant_push_from_drop() {
use std::sync::atomic::AtomicU64;
use std::sync::{self, Mutex};
let process = ProcessState::init_default().expect("init_default");
let drop_log: Arc<Mutex<Vec<u64>>> = Arc::new(Mutex::new(Vec::new()));
let pusher_target: Arc<AtomicU64> = Arc::new(AtomicU64::new(0));
struct DropFireSentinel {
my_id: Arc<AtomicU64>,
log: Arc<Mutex<Vec<u64>>>,
}
impl Drop for DropFireSentinel {
fn drop(&mut self) {
self.log
.lock()
.unwrap()
.push(self.my_id.load(Ordering::SeqCst));
}
}
impl IBinder for DropFireSentinel {
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 Transactable> {
None
}
fn descriptor(&self) -> &str {
"rsbinder.test.DropFireSentinel"
}
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(())
}
}
struct ReentrantPusher {
my_id: Arc<AtomicU64>,
target: Arc<AtomicU64>,
log: Arc<Mutex<Vec<u64>>>,
}
impl Drop for ReentrantPusher {
fn drop(&mut self) {
self.log
.lock()
.unwrap()
.push(self.my_id.load(Ordering::SeqCst));
let target_id = self.target.load(Ordering::SeqCst);
if target_id != 0 {
BINDER_DEREFS.with(|d| {
d.borrow_mut().pending_weak_derefs.push_back(target_id);
});
}
}
}
impl IBinder for ReentrantPusher {
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 Transactable> {
None
}
fn descriptor(&self) -> &str {
"rsbinder.test.ReentrantPusher"
}
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(())
}
}
let id_b_holder = Arc::new(AtomicU64::new(0));
let arc_b: Arc<dyn IBinder> = Arc::new(DropFireSentinel {
my_id: Arc::clone(&id_b_holder),
log: Arc::clone(&drop_log),
});
let id_b = process.publish_native(Arc::clone(&arc_b));
id_b_holder.store(id_b, Ordering::SeqCst);
process
.ref_native_kernel(id_b)
.expect("ref_native_kernel(id_b)");
drop(arc_b);
pusher_target.store(id_b, Ordering::SeqCst);
let id_a_holder = Arc::new(AtomicU64::new(0));
let arc_a: Arc<dyn IBinder> = Arc::new(ReentrantPusher {
my_id: Arc::clone(&id_a_holder),
target: Arc::clone(&pusher_target),
log: Arc::clone(&drop_log),
});
let id_a = process.publish_native(Arc::clone(&arc_a));
id_a_holder.store(id_a, Ordering::SeqCst);
process
.ref_native_kernel(id_a)
.expect("ref_native_kernel(id_a)");
drop(arc_a);
BINDER_DEREFS.with(|d| {
d.borrow_mut().pending_strong_derefs.push_back(id_a);
});
process_pending_derefs().expect("process_pending_derefs must not panic or error");
let log = drop_log.lock().unwrap();
assert_eq!(
log.len(),
2,
"both A and B must have dropped exactly once, got {log:?}"
);
assert!(log.contains(&id_a), "A's drop must fire, got {log:?}");
assert!(log.contains(&id_b), "B's drop must fire, got {log:?}");
BINDER_DEREFS.with(|d| {
let derefs = d.borrow();
assert!(
derefs.pending_weak_derefs.is_empty(),
"weak queue must be empty after drain"
);
assert!(
derefs.pending_strong_derefs.is_empty(),
"strong queue must be empty after drain"
);
});
assert!(process.lookup_native(id_a).is_none());
assert!(process.lookup_native(id_b).is_none());
}
#[cfg(target_os = "linux")]
struct FakeTransactionGuard {
previous: Option<TransactionState>,
}
#[cfg(target_os = "linux")]
impl FakeTransactionGuard {
fn install(
calling_uid: binder::uid_t,
calling_pid: binder::pid_t,
calling_sid: *const u8,
) -> Self {
let new_state = TransactionState {
calling_pid,
calling_sid,
calling_uid,
last_transaction_binder_flags: 0,
work_source: 0,
propagate_work_source: false,
has_explicit_identity: false,
};
let previous = THREAD_STATE.with(|ts| ts.borrow_mut().transaction.replace(new_state));
FakeTransactionGuard { previous }
}
}
#[cfg(target_os = "linux")]
impl Drop for FakeTransactionGuard {
fn drop(&mut self) {
THREAD_STATE.with(|ts| {
ts.borrow_mut().transaction = self.previous.take();
});
}
}
#[test]
#[cfg(target_os = "linux")]
#[serial_test::serial(binder)]
fn test_get_calling_outside_transaction_returns_defaults() {
ProcessState::init_default().expect("init_default");
let _ = THREAD_STATE.with(|ts| ts.borrow_mut().transaction.take());
assert!(!is_handling_transaction());
assert_eq!(get_calling_uid(), 0);
assert_eq!(get_calling_pid(), 0);
assert!(get_calling_sid().is_none());
assert!(!has_explicit_identity());
assert_eq!(clear_calling_identity(), 0);
restore_calling_identity(0xDEAD_BEEF_DEAD_BEEFu64 as i64); assert_eq!(get_calling_uid(), 0);
}
#[test]
#[cfg(target_os = "linux")]
#[serial_test::serial(binder)]
fn test_get_calling_inside_transaction_extracts_fields() {
ProcessState::init_default().expect("init_default");
let sid_cstring = std::ffi::CString::new("u:r:system_server:s0").unwrap();
let _guard = FakeTransactionGuard::install(1000, 9999, sid_cstring.as_ptr() as *const u8);
assert!(is_handling_transaction());
assert_eq!(get_calling_uid(), 1000);
assert_eq!(get_calling_pid(), 9999);
let sid = get_calling_sid().expect("SID present when secctx pointer non-null");
assert_eq!(sid.to_str().unwrap(), "u:r:system_server:s0");
let sid2 = get_calling_sid().expect("second call also returns Some");
assert_eq!(sid, sid2);
assert!(!std::ptr::eq(sid.as_ptr(), sid2.as_ptr()));
}
#[test]
#[cfg(target_os = "linux")]
#[serial_test::serial(binder)]
fn test_get_calling_sid_null_secctx_returns_none() {
ProcessState::init_default().expect("init_default");
let _guard = FakeTransactionGuard::install(1000, 9999, std::ptr::null());
assert!(is_handling_transaction());
assert_eq!(get_calling_uid(), 1000);
assert!(get_calling_sid().is_none());
}
#[test]
fn test_calling_identity_token_pack_unpack_round_trip() {
for &(has_explicit, uid, pid) in &[
(true, 1000u32, 9999i32),
(false, 1000u32, 9999i32),
(true, 1000u32, -1i32),
(false, 1000u32, -1i32),
(true, 0u32, 0i32),
(false, 0u32, 0i32),
] {
let tok = pack_calling_identity(has_explicit, uid, pid);
assert_eq!(
unpack_has_explicit_identity(tok),
has_explicit,
"has_explicit ({has_explicit},{uid},{pid})"
);
assert_eq!(
unpack_calling_uid(tok),
uid,
"uid ({has_explicit},{uid},{pid})"
);
assert_eq!(
unpack_calling_pid(tok),
pid,
"pid ({has_explicit},{uid},{pid})"
);
}
}
#[test]
#[cfg(target_os = "linux")]
#[serial_test::serial(binder)]
fn test_clear_and_restore_calling_identity_round_trip() {
ProcessState::init_default().expect("init_default");
let _guard = FakeTransactionGuard::install(1000, 9999, std::ptr::null());
assert_eq!(get_calling_uid(), 1000);
assert_eq!(get_calling_pid(), 9999);
assert!(!has_explicit_identity());
let token = clear_calling_identity();
assert_eq!(get_calling_uid(), rustix::process::getuid().as_raw());
assert_eq!(
get_calling_pid(),
rustix::process::getpid().as_raw_nonzero().get() as binder::pid_t
);
assert!(has_explicit_identity());
assert!(get_calling_sid().is_none());
restore_calling_identity(token);
assert_eq!(get_calling_uid(), 1000);
assert_eq!(get_calling_pid(), 9999);
assert!(!has_explicit_identity());
}
#[test]
#[cfg(target_os = "linux")]
#[serial_test::serial(binder)]
fn test_strict_mode_policy_round_trip() {
ProcessState::init_default().expect("init_default");
let saved = get_strict_mode_policy();
set_strict_mode_policy(0x1234_5678);
assert_eq!(get_strict_mode_policy(), 0x1234_5678);
set_strict_mode_policy(saved);
assert_eq!(get_strict_mode_policy(), saved);
}
#[test]
fn a_refused_command_is_named_from_the_offset_the_driver_stopped_at() {
let mut out = CommandStream::new();
out.write_cmd::<u32>(&binder::BC_INCREFS).unwrap();
out.write_cmd::<u32>(&7u32).unwrap();
let second = out.data_size();
out.write_cmd::<u32>(&binder::BC_FREE_BUFFER).unwrap();
out.write_cmd::<u32>(&0u32).unwrap();
let total = out.data_size();
out.set_data_position(4);
let msg = describe_refused_command(&mut out, second);
assert!(msg.contains("BC_FREE_BUFFER"), "{msg}");
assert!(
msg.contains(&format!("consumed {second} of {total}")),
"{msg}"
);
assert_eq!(
out.data_position(),
4,
"the probe must leave the read position where it found it"
);
assert_eq!(
describe_refused_command(&mut out, total),
format!("consumed {total} of {total}"),
"a fully consumed buffer has no command to blame"
);
}
}