use log::error;
use std::backtrace::Backtrace;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::ffi::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::*,
transaction_data::{TransactionData, TransactionDataSecctx},
};
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) };
static RPC_CALLING_CAPS: std::cell::Cell<Option<crate::TransportCaps>> =
const { std::cell::Cell::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>>,
previous_caps: Option<crate::TransportCaps>,
}
#[cfg(feature = "rpc")]
impl RpcCallingGuard {
pub(crate) fn install(
peer: std::sync::Arc<crate::rpc::transport::PeerIdentity>,
caps: crate::TransportCaps,
) -> Self {
let previous = RPC_CALLING.with(|c| c.borrow_mut().replace(peer));
let previous_caps = RPC_CALLING_CAPS.with(|c| c.replace(Some(caps)));
RpcCallingGuard {
previous,
previous_caps,
}
}
pub(crate) fn suspend() -> Self {
let previous = RPC_CALLING.with(|c| c.borrow_mut().take());
let previous_caps = RPC_CALLING_CAPS.with(|c| c.take());
RpcCallingGuard {
previous,
previous_caps,
}
}
}
#[cfg(feature = "rpc")]
impl Drop for RpcCallingGuard {
fn drop(&mut self) {
RPC_CALLING.with(|c| *c.borrow_mut() = self.previous.take());
RPC_CALLING_CAPS.with(|c| c.set(self.previous_caps.take()));
}
}
#[cfg(feature = "rpc")]
pub(crate) 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| Caller::Kernel {
uid: tr.calling_uid,
pid: tr.calling_pid,
sid: tr.calling_sid.clone(),
})
})
}
pub fn calling_caps() -> Option<crate::TransportCaps> {
#[cfg(feature = "rpc")]
{
if let Some(caps) = RPC_CALLING_CAPS.with(|c| c.get()) {
return Some(caps);
}
}
if !ProcessState::is_initialized() {
return None;
}
THREAD_STATE.with(|thread_state| {
let thread_state = thread_state.borrow();
thread_state
.transaction
.as_ref()
.map(|_| crate::TransportCaps::KERNEL)
})
}
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)]
struct TransactionState {
calling_pid: binder::pid_t,
calling_sid: Option<CString>,
calling_uid: binder::uid_t,
last_transaction_binder_flags: u32,
has_explicit_identity: bool,
}
impl TransactionState {
fn from_transaction_data(data: &TransactionData, calling_sid: Option<CString>) -> Self {
TransactionState {
calling_pid: data.sender_pid,
calling_sid,
calling_uid: data.sender_euid,
last_transaction_binder_flags: data.flags,
has_explicit_identity: false,
}
}
}
fn calling_sid_at(secctx: binder::binder_uintptr_t) -> Option<CString> {
let addr = usize::try_from(secctx).ok().filter(|&addr| addr != 0)?;
sid_at(
addr,
selinux_available(),
ProcessState::as_self().mapped_range(),
)
}
fn sid_at(addr: usize, selinux: bool, mapped: std::ops::Range<usize>) -> Option<CString> {
if !selinux {
return None;
}
if !mapped.contains(&addr) {
log::error!("BR_TRANSACTION_SEC_CTX: secctx {addr:#x} is outside the receive mapping");
return None;
}
copy_c_string(addr, mapped.end)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn copy_c_string(addr: usize, end: usize) -> Option<CString> {
let page = rustix::param::page_size();
let pid = rustix::process::getpid().as_raw_nonzero().get();
let mut bytes: Vec<u8> = Vec::new();
let mut at = addr;
while at < end {
let want = (page - at % page).min(end - at);
bytes.reserve(want);
let filled = bytes.len();
let local = libc::iovec {
iov_base: bytes.spare_capacity_mut().as_mut_ptr().cast(),
iov_len: want,
};
let remote = libc::iovec {
iov_base: at as *mut libc::c_void,
iov_len: want,
};
let got = unsafe { libc::process_vm_readv(pid, &local, 1, &remote, 1, 0) };
let got = usize::try_from(got).ok().filter(|&got| got <= want)?;
unsafe { bytes.set_len(filled + got) };
if let Some(nul) = bytes[filled..].iter().position(|&byte| byte == 0) {
bytes.truncate(filled + nul);
return CString::new(bytes).ok();
}
if got < want {
return None;
}
at += want;
}
None
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn copy_c_string(_addr: usize, _end: usize) -> Option<CString> {
None
}
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 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 tr = TransactionData::default();
tr.set_target_handle(handle);
tr.code = code;
if *status == StatusCode::Ok.into() {
tr.data_size = data.ipc_data_size() as _;
tr.offsets_size = (data.objects.len() * std::mem::size_of::<binder_size_t>()) as _;
tr.buffer = data.as_ptr() as _;
tr.offsets = data.objects.as_ptr() as _;
} else {
flags |= binder::TF_STATUS_CODE;
tr.data_size = std::mem::size_of::<i32>() as _;
tr.buffer = status as *const i32 as _;
}
tr.flags = flags;
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)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct WorkSource {
uid: binder::uid_t,
propagate: bool,
}
impl WorkSource {
const UNSET: WorkSource = WorkSource {
uid: UNSET_WORK_SOURCE as binder::uid_t,
propagate: false,
};
fn token(self) -> i64 {
((self.propagate as i64) << WORK_SOURCE_PROPAGATED_BIT_INDEX) | (self.uid as i64)
}
fn from_token(token: i64) -> Self {
WorkSource {
uid: token as u32 as binder::uid_t,
propagate: (token >> WORK_SOURCE_PROPAGATED_BIT_INDEX) & 1 == 1,
}
}
}
thread_local! {
static WORK_SOURCE: Cell<WorkSource> = const { Cell::new(WorkSource::UNSET) };
}
fn work_source() -> WorkSource {
WORK_SOURCE.with(Cell::get)
}
fn replace_work_source(ws: WorkSource) -> WorkSource {
WORK_SOURCE.with(|c| c.replace(ws))
}
pub(crate) struct WorkSourceDispatchGuard {
saved: WorkSource,
}
impl WorkSourceDispatchGuard {
pub(crate) fn enter() -> Self {
WorkSourceDispatchGuard {
saved: replace_work_source(WorkSource::UNSET),
}
}
}
impl Drop for WorkSourceDispatchGuard {
fn drop(&mut self) {
let _ = WORK_SOURCE.try_with(|c| c.set(self.saved));
}
}
pub fn set_calling_work_source_uid(uid: binder::uid_t) -> i64 {
replace_work_source(WorkSource {
uid,
propagate: true,
})
.token()
}
pub(crate) fn set_calling_work_source_uid_without_propagation(uid: binder::uid_t) -> i64 {
let current = work_source();
replace_work_source(WorkSource { uid, ..current }).token()
}
pub fn get_calling_work_source_uid() -> binder::uid_t {
work_source().uid
}
pub fn clear_calling_work_source() -> i64 {
set_calling_work_source_uid(UNSET_WORK_SOURCE as binder::uid_t)
}
pub fn restore_calling_work_source(token: i64) {
replace_work_source(WorkSource::from_token(token));
}
pub fn clear_propagate_work_source() {
let current = work_source();
replace_work_source(WorkSource {
propagate: false,
..current
});
}
pub fn should_propagate_work_source() -> bool {
work_source().propagate
}
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,
}
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
.as_ref()
.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
.as_ref()
.map_or(0, |state| state.calling_pid)
);
return Err(StatusCode::FailedTransaction);
}
binder::BR_ACQUIRE_RESULT => {
thread_state.borrow_mut().in_parcel.read_cmd::<i32>()?;
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) = (tr.buffer, tr.offsets);
if let UntilResponse::Reply = until {
if (tr.flags & TF_STATUS_CODE) == 0 {
let reply = unsafe {
Parcel::from_driver_buffer(
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, F>(
handle: binder::binder_uintptr_t,
obituary: O,
queue_done: Q,
flush: F,
) -> Result<()>
where
O: FnOnce() -> Result<()>,
Q: FnOnce() -> Result<()>,
F: 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 flush_result = flush();
if let Err(e) = &flush_result {
error!(
"finish_obituary failed for handle {handle:X}: {e:?}; \
obituary_result: {obituary_result:?}"
);
}
obituary_result?;
flush_result?;
Ok(())
}
fn drive_frozen_binder_handshake<N, Q>(
cookie: binder::binder_uintptr_t,
notify: N,
queue_done: Q,
) -> Result<()>
where
N: FnOnce(),
Q: FnOnce() -> Result<()>,
{
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(notify)).is_err() {
error!("BR_FROZEN_BINDER: notification for cookie {cookie:X} panicked");
}
queue_done()
}
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::with_capacity(0);
Err(StatusCode::Unknown)
}
}
}
fn dispatch_kernel_observed(
binder: &SIBinder,
transactable: &dyn Transactable,
tr: &TransactionData,
reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
let code = tr.code;
crate::observe::observed(
|| crate::observe::TxnContext {
descriptor: binder.descriptor(),
code,
method: transactable.transaction_name(code),
is_oneway: tr.flags & TF_ONE_WAY != 0,
calling_uid: tr.sender_euid,
calling_pid: tr.sender_pid,
transport: crate::TransportCaps::KERNEL,
},
|| dispatch_transact_caught(transactable, code, reader, reply),
)
.map_err(handler_reply_status)
}
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 {
TransactionDataSecctx {
transaction_data: thread_state.in_parcel.read_transaction()?,
secctx: 0,
}
}
};
let tr = &tr_secctx.transaction_data;
let mut reader = unsafe {
Parcel::from_driver_buffer(
tr.buffer as _,
tr.data_size as _,
tr.offsets as _,
(tr.offsets_size as usize) / std::mem::size_of::<binder::binder_size_t>(),
free_buffer,
)
};
let (transaction_old, strict_mode_policy_old) = {
let mut thread_state = thread_state.borrow_mut();
let transaction_old = thread_state.transaction.take();
let strict_mode_policy_old = thread_state.strict_mode_policy;
thread_state.transaction = Some(TransactionState::from_transaction_data(
&tr_secctx.transaction_data,
calling_sid_at(tr_secctx.secctx),
));
(transaction_old, strict_mode_policy_old)
};
let _work_source = WorkSourceDispatchGuard::enter();
#[cfg(feature = "rpc")]
let _rpc_suspended = RpcCallingGuard::suspend();
let mut reply = if tr_secctx.transaction_data.flags & TF_ONE_WAY != 0 {
Parcel::with_capacity(0)
} else {
Parcel::new()
};
let result = {
let target_ptr = tr.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_kernel_observed(
&strong,
t,
&tr_secctx.transaction_data,
&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_kernel_observed(
&context,
t,
&tr_secctx.transaction_data,
&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)
}
}
}
};
drop(reader);
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 & TF_ONE_WAY) == 0 {
let flags = 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,
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(())
}));
{
let mut thread_state = thread_state.borrow_mut();
thread_state.transaction = transaction_old;
thread_state.strict_mode_policy = strict_mode_policy_old;
}
match reply_result {
Ok(Ok(())) => {}
Ok(Err(e)) => log::error!(
"error in reply for code {}: {e}",
tr_secctx.transaction_data.code
),
Err(_payload) => {
discard_unflushed_commands(thread_state, queued_at, false);
log::error!(
"reply path panicked for code {}; reply dropped",
tr_secctx.transaction_data.code
);
}
}
}
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}");
#[cfg(feature = "rpc")]
let _rpc_suspended = RpcCallingGuard::suspend();
if let Err(e) = 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().finish_obituary(),
) {
log::error!("BR_DEAD_BINDER handshake failed for handle {handle:X}: {e:?}");
}
}
binder::BR_CLEAR_DEATH_NOTIFICATION_DONE => {
let handle = {
let mut state = thread_state.borrow_mut();
state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?
};
ProcessState::as_self().death_clear_done(handle as _);
}
binder::BR_FROZEN_BINDER => {
let (cookie, is_frozen) = {
let mut state = thread_state.borrow_mut();
let cookie = state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?;
let is_frozen = state.in_parcel.read_cmd::<u32>()? != 0;
let _reserved = state.in_parcel.read_cmd::<u32>()?;
(cookie, is_frozen)
};
log::trace!("BR_FROZEN_BINDER: cookie {cookie:X} is_frozen {is_frozen}");
#[cfg(feature = "rpc")]
let _rpc_suspended = RpcCallingGuard::suspend();
let notify = || match u32::try_from(cookie) {
Ok(handle) => ProcessState::as_self().frozen_state_changed(handle, is_frozen),
Err(_) => log::error!("BR_FROZEN_BINDER cookie {cookie:X} is not a handle"),
};
let queue_done = || {
let mut state = thread_state.borrow_mut();
state
.out_parcel
.write_cmd::<u32>(&(binder::BC_FREEZE_NOTIFICATION_DONE))?;
state
.out_parcel
.write_cmd::<binder::binder_uintptr_t>(&cookie)
};
if let Err(e) = drive_frozen_binder_handshake(cookie, notify, queue_done) {
log::error!(
"BR_FROZEN_BINDER: queue BC_FREEZE_NOTIFICATION_DONE failed: {e:?}"
);
}
}
binder::BR_CLEAR_FREEZE_NOTIFICATION_DONE => {
let cookie = {
let mut state = thread_state.borrow_mut();
state.in_parcel.read_cmd::<binder::binder_uintptr_t>()?
};
log::trace!("BR_CLEAR_FREEZE_NOTIFICATION_DONE: cookie {cookie:X}");
match u32::try_from(cookie) {
Ok(handle) => ProcessState::as_self().freeze_clear_done(handle),
Err(_) => log::error!(
"BR_CLEAR_FREEZE_NOTIFICATION_DONE cookie {cookie:X} is not a handle"
),
}
}
_ => {
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, short: bool) -> String {
let total = out.data_size();
if stopped_at >= total {
return format!("consumed {stopped_at} of {total}");
}
let saved = out.data_position();
let name = |cmd: u32| format!("{} ({cmd:#x})", command_to_str(cmd));
out.set_data_position(stopped_at);
let next = match out.read_cmd::<u32>() {
Ok(cmd) => name(cmd),
Err(e) => format!("<unreadable: {e}>"),
};
let detail = if short {
let failed = command_ending_at(out, stopped_at)
.map_or_else(|| format!("<no command ends at {stopped_at}>"), name);
format!("driver set its return_error at {failed}; stopped before {next}")
} else {
format!("driver stopped at {next}")
};
out.set_data_position(saved);
format!("consumed {stopped_at} of {total}; {detail}")
}
fn command_ending_at(out: &mut CommandStream, end: usize) -> Option<u32> {
let mut at = 0;
while at < end {
out.set_data_position(at);
let cmd = out.read_cmd::<u32>().ok()?;
at += 4 + ((cmd >> _IOC_SIZESHIFT) & _IOC_SIZEMASK) as usize;
if at == end {
return Some(cmd);
}
}
None
}
fn empty_write_read() -> binder::binder_write_read {
binder::binder_write_read {
write_size: 0,
write_consumed: 0,
write_buffer: 0,
read_size: 0,
read_consumed: 0,
read_buffer: 0,
}
}
fn talk_with_driver(do_receive: bool) -> Result<()> {
use std::io::Write as _;
THREAD_STATE.with(|thread_state| -> Result<()> {
let mut thread_state = thread_state.borrow_mut();
let thread_state = &mut *thread_state;
let need_read = thread_state.in_parcel.is_empty();
let receive = do_receive && need_read;
let write: &[u8] = if !do_receive || need_read {
thread_state.out_parcel.as_bytes()?
} else {
&[]
};
if write.is_empty() && !receive {
return Ok(());
}
if !write.is_empty() {
log::trace!("Sending command to driver:\n{:?}", thread_state.out_parcel);
log::trace!("need_read: {need_read}, do_receive: {do_receive}");
}
ensure_thread_exit_guard(&thread_state.driver);
let read: &mut [u8] = if receive {
thread_state.in_parcel.driver_read_buffer()
} else {
&mut []
};
let mut bwr = empty_write_read();
let res = loop {
match unsafe { binder::write_read(&thread_state.driver, write, read, &mut bwr) } {
Err(rustix::io::Errno::INTR) => {}
res => break res,
}
};
if receive {
let filled = if res.is_ok() {
bwr.read_consumed as _
} else {
0
};
thread_state.in_parcel.driver_read_done(filled)?;
}
if let Err(errno) = res {
let detail = describe_refused_command(
&mut thread_state.out_parcel,
bwr.write_consumed as _,
false,
);
log::error!(
"binder::write_read() error : {errno}; {detail}; \
this thread's connection to the driver may no longer work"
);
let consumed = bwr.write_consumed as usize;
if consumed == thread_state.out_parcel.data_size() {
if consumed > 0 {
thread_state.out_parcel.set_data_size(0)?;
thread_state.out_flush_epoch += 1;
}
} else if consumed > 0 {
let _ = writeln!(
std::io::stderr(),
"rsbinder FATAL: driver applied the write buffer up to a refused command —\n\
resending would apply the commands before it twice; dropping\n\
them would rewind the marks callers hold\n\
{detail}\nQueued commands:\n{:?}",
thread_state.out_parcel
);
std::process::abort();
}
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
);
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 _,
true,
);
let _ = writeln!(
std::io::stderr(),
"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 {
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(())
})
}
fn write_without_thread_state<T: NativeScalar>(cmd: u32, arg: T) -> Result<()> {
write_without_thread_state_with(|out| {
out.write_cmd::<u32>(&cmd)?;
out.write_cmd::<T>(&arg)
})
}
fn write_without_thread_state_with(
build: impl FnOnce(&mut CommandStream) -> Result<()>,
) -> Result<()> {
let mut out = CommandStream::new();
build(&mut out)?;
let driver = ProcessState::as_self().driver();
let write = out.as_bytes()?;
let mut bwr = empty_write_read();
loop {
match unsafe { binder::write_read(&*driver, write, &mut [], &mut bwr) } {
Ok(()) => break,
Err(errno) if errno == rustix::io::Errno::INTR => {}
Err(errno) => {
log::error!("binder::write_read() after thread-local teardown: {errno}");
return Err(StatusCode::from(errno));
}
}
}
if let Err(e) = binder::thread_exit(&*driver, 0) {
log::warn!("BINDER_THREAD_EXIT after thread-local teardown failed: {e}");
}
Ok(())
}
pub(crate) fn dec_strong_handle(handle: u32) -> Result<()> {
log::trace!("dec_strong_handle: {handle}");
let queued = THREAD_STATE.try_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(())
});
match queued {
Ok(result) => result,
Err(_) => write_without_thread_state::<u32>(binder::BC_RELEASE, handle),
}
}
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}");
let queued = THREAD_STATE.try_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(())
});
match queued {
Ok(result) => result,
Err(_) => write_without_thread_state::<u32>(binder::BC_DECREFS, handle),
}
}
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);
Ok(())
})?;
reader.update_work_source_request_header_pos();
let work_source: i32 = reader.read()?;
set_calling_work_source_uid_without_propagation(work_source as _);
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);
}
}
match crate::parcelable::read_string16_matches(reader, descriptor)? {
Ok(()) => Ok(true),
Err(parcel_interface) => {
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 |= TF_ACCEPT_FDS;
if (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 & 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(
_: Option<&Parcel>,
data: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
let queued = THREAD_STATE.try_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(())
});
match queued {
Ok(result) => result?,
Err(_) => {
return write_without_thread_state::<binder_uintptr_t>(binder::BC_FREE_BUFFER, data)
}
}
flush_if_needed()?;
Ok(())
}
pub(crate) fn query_interface(handle: u32) -> Result<String> {
#[cfg(target_os = "android")]
if handle == 0 && !crate::sdk_at_least(30) {
return Ok("android.os.IServiceManager".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 clear_death_notification(handle: u32) -> Result<()> {
log::trace!("clear_death_notification: {handle}");
ProcessState::note_death_clear(handle);
let write = |out: &mut CommandStream| {
write_handle_cookie(out, binder::BC_CLEAR_DEATH_NOTIFICATION, handle)
};
match THREAD_STATE.try_with(|thread_state| write(&mut thread_state.borrow_mut().out_parcel)) {
Ok(result) => result,
Err(_) => write_without_thread_state_with(write),
}
}
fn write_handle_cookie(out: &mut CommandStream, cmd: u32, handle: u32) -> Result<()> {
out.write_cmd::<u32>(&cmd)?;
out.write_cmd::<u32>(&handle)?;
out.write_cmd::<binder::binder_uintptr_t>(&(handle as _))
}
fn send_request_flushed(cmd: u32, handle: u32) -> Result<()> {
log::trace!("{}: {handle}", command_to_str(cmd));
THREAD_STATE
.try_with(|thread_state| -> Result<()> {
if thread_state.borrow().out_parcel.data_size() > 0 {
flush_commands()?;
}
let mark = thread_state.borrow().unflushed_mark();
let rewind = || {
let mut ts = thread_state.borrow_mut();
let rewound = ts.out_flush_epoch == mark.0 && ts.out_parcel.data_size() > mark.1;
if rewound {
let _ = ts.out_parcel.set_data_size(mark.1);
}
rewound
};
let written =
write_handle_cookie(&mut thread_state.borrow_mut().out_parcel, cmd, handle);
if let Err(err) = written {
rewind();
return Err(err);
}
match flush_commands() {
Ok(()) if rewind() => Err(StatusCode::InvalidOperation),
Ok(()) => Ok(()),
Err(err) if rewind() => Err(err),
Err(err) => {
log::warn!("flush after {}({handle}): {err:?}", command_to_str(cmd));
Ok(())
}
}
})
.unwrap_or(Err(StatusCode::InvalidOperation))
}
pub(crate) fn send_death_request(handle: u32) -> Result<()> {
send_request_flushed(binder::BC_REQUEST_DEATH_NOTIFICATION, handle)
}
pub(crate) fn send_freeze_request(handle: u32) -> Result<()> {
send_request_flushed(binder::BC_REQUEST_FREEZE_NOTIFICATION, handle)
}
pub(crate) fn flush_commands_if_alive() -> Result<()> {
match THREAD_STATE.try_with(|_| ()) {
Ok(()) => flush_commands(),
Err(_) => Ok(()),
}
}
pub(crate) fn clear_freeze_notification(handle: u32) -> Result<()> {
log::trace!("clear_freeze_notification: {handle}");
ProcessState::note_freeze_clear(handle);
let write = |out: &mut CommandStream| {
write_handle_cookie(out, binder::BC_CLEAR_FREEZE_NOTIFICATION, handle)
};
match THREAD_STATE.try_with(|thread_state| write(&mut thread_state.borrow_mut().out_parcel)) {
Ok(result) => result,
Err(_) => write_without_thread_state_with(write),
}
}
#[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) => CallingContext {
pid: transaction.calling_pid,
uid: transaction.calling_uid,
sid: transaction.calling_sid.clone(),
},
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();
thread_state.transaction.as_ref()?.calling_sid.clone()
})
}
pub fn get_calling_pid() -> binder::pid_t {
if let Some((_, pid)) = rpc_calling() {
return pid;
}
let own_pid = || rustix::process::getpid().as_raw_nonzero().get() as binder::pid_t;
if !ProcessState::is_initialized() {
return own_pid();
}
THREAD_STATE
.with(|thread_state| {
thread_state
.borrow()
.transaction
.as_ref()
.map(|tr| tr.calling_pid)
})
.unwrap_or_else(own_pid)
}
pub fn get_calling_uid() -> 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 rpc_calling().is_some() || !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 = None;
tr.has_explicit_identity = true;
token
})
}
pub fn restore_calling_identity(token: i64) {
if rpc_calling().is_some() || !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 = None;
})
}
#[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 {
return Err(last_os_error_status());
}
Ok(raw)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn last_os_error_status() -> StatusCode {
StatusCode::from(std::io::Error::last_os_error())
}
#[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 {
id: 0,
command: 0,
param: 0,
};
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::INVAL || 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 rpc_calling().is_some() || !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(any(feature = "rpc", target_os = "linux"))]
fn own_uid() -> binder::uid_t {
rustix::process::getuid().as_raw()
}
#[cfg(any(feature = "rpc", target_os = "linux"))]
fn own_pid() -> binder::pid_t {
rustix::process::getpid().as_raw_nonzero().get() as binder::pid_t
}
#[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(), own_uid());
assert_eq!(get_calling_pid(), own_pid());
assert!(!is_handling_transaction());
assert!(get_calling_sid().is_none());
assert!(calling_caller().is_none());
assert!(calling_caps().is_none());
let unix_caps = crate::TransportCaps::FD_PASSING
| crate::TransportCaps::TRUSTED_UID
| crate::TransportCaps::SAME_HOST;
{
let _g = RpcCallingGuard::install(
Arc::new(PeerIdentity::Local { uid: 1234, pid: 42 }),
unix_caps,
);
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:?}"),
}
assert_eq!(calling_caps(), Some(unix_caps));
assert_eq!(
calling_caps()
.unwrap()
.require(crate::TransportCaps::CALLBACKS, "a callback"),
Err(crate::StatusCode::InvalidOperation)
);
{
let _g2 = RpcCallingGuard::install(
Arc::new(PeerIdentity::Vsock { cid: 7 }),
crate::TransportCaps::NONE,
);
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!(calling_caps(), Some(crate::TransportCaps::NONE));
}
assert_eq!(
get_calling_uid(),
1234,
"outer identity restored after nesting"
);
assert_eq!(
calling_caps(),
Some(unix_caps),
"outer caps restored after nesting"
);
}
assert_eq!(get_calling_uid(), own_uid());
assert!(!is_handling_transaction());
assert!(calling_caller().is_none());
assert!(calling_caps().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>(),
std::mem::align_of::<u64>()
);
}
#[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 a_handler_dead_object_is_not_replied_as_dead_object() {
struct Fails(StatusCode);
impl Transactable for Fails {
fn transact(&self, _: TransactionCode, _: &mut Parcel, _: &mut Parcel) -> Result<()> {
Err(self.0)
}
}
struct Named;
impl Remotable for Named {
fn descriptor() -> &'static str {
"x.y.INamed"
}
fn on_transact(
&self,
_: TransactionCode,
_: &mut Parcel,
_: &mut Parcel,
) -> Result<()> {
Ok(())
}
fn on_dump(&self, _: &mut dyn std::io::Write, _: &[String]) -> Result<()> {
Ok(())
}
}
let binder = Interface::as_binder(&crate::native::Binder::new(Named));
let tr = TransactionData::default();
let replied = |status| {
let (mut reader, mut reply) = (Parcel::new(), Parcel::new());
dispatch_kernel_observed(&binder, &Fails(status), &tr, &mut reader, &mut reply)
};
assert_eq!(
replied(StatusCode::DeadObject),
Err(StatusCode::FailedTransaction)
);
assert_eq!(
replied(StatusCode::PermissionDenied),
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("flush");
Ok(())
},
);
assert!(result.is_ok());
assert_eq!(*order.borrow(), vec!["obituary", "queue", "flush"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Err(StatusCode::DeadObject)
},
|| {
push("queue");
Ok(())
},
|| {
push("flush");
Ok(())
},
);
assert!(matches!(result, Err(StatusCode::DeadObject)));
assert_eq!(*order.borrow(), vec!["obituary", "queue", "flush"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Ok(())
},
|| {
push("queue");
Err(StatusCode::NoMemory)
},
|| {
push("flush");
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("flush");
Err(StatusCode::DeadObject)
},
);
assert!(matches!(result, Err(StatusCode::DeadObject)));
assert_eq!(*order.borrow(), vec!["obituary", "queue", "flush"]);
order.borrow_mut().clear();
let result = drive_dead_binder_handshake(
42,
|| {
push("obituary");
Err(StatusCode::PermissionDenied)
},
|| {
push("queue");
Ok(())
},
|| {
push("flush");
Err(StatusCode::DeadObject)
},
);
assert!(
matches!(result, Err(StatusCode::PermissionDenied)),
"obituary error must take priority over flush error, got {result:?}"
);
assert_eq!(*order.borrow(), vec!["obituary", "queue", "flush"]);
}
#[test]
fn frozen_binder_handshake_queues_the_done_once_whatever_notify_does() {
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_frozen_binder_handshake(
7,
|| push("notify"),
|| {
push("queue");
Ok(())
},
);
assert!(result.is_ok());
assert_eq!(*order.borrow(), vec!["notify", "queue"]);
order.borrow_mut().clear();
let result = drive_frozen_binder_handshake(
7,
|| {
push("notify");
panic!("rsbinder bug inside the notification");
},
|| {
push("queue");
Ok(())
},
);
assert!(result.is_ok());
assert_eq!(*order.borrow(), vec!["notify", "queue"]);
order.borrow_mut().clear();
let result =
drive_frozen_binder_handshake(7, || push("notify"), || Err(StatusCode::NoMemory));
assert_eq!(result, Err(StatusCode::NoMemory));
assert_eq!(*order.borrow(), vec!["notify"]);
}
#[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: Option<CString>,
) -> Self {
let new_state = TransactionState {
calling_pid,
calling_sid,
calling_uid,
last_transaction_binder_flags: 0,
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(), own_uid());
assert_eq!(get_calling_pid(), own_pid());
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(), own_uid());
}
#[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, Some(sid_cstring));
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()));
}
#[cfg(all(target_os = "linux", feature = "rpc"))]
#[derive(Default)]
struct RecordingNative {
observed: std::sync::Mutex<Option<(Option<crate::TransportCaps>, Option<Caller>)>>,
}
#[cfg(all(target_os = "linux", feature = "rpc"))]
impl Transactable for RecordingNative {
fn transact(&self, _: TransactionCode, _: &mut Parcel, _: &mut Parcel) -> Result<()> {
*self.observed.lock().unwrap() = Some((calling_caps(), calling_caller()));
Ok(())
}
}
#[cfg(all(target_os = "linux", feature = "rpc"))]
impl IBinder for RecordingNative {
fn link_to_death(&self, _: std::sync::Weak<dyn DeathRecipient>) -> Result<()> {
Err(StatusCode::InvalidOperation)
}
fn unlink_to_death(&self, _: std::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> {
Some(self)
}
fn descriptor(&self) -> &str {
"rsbinder.test.RecordingNative"
}
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(all(target_os = "linux", feature = "rpc"))]
#[serial_test::serial(binder)]
fn nested_kernel_transaction_answers_for_the_kernel_caller() {
use crate::rpc::transport::PeerIdentity;
use std::sync::Arc;
ProcessState::init_default().expect("init_default");
let _ = THREAD_STATE.with(|ts| ts.borrow_mut().transaction.take());
let recorder = Arc::new(RecordingNative::default());
let id = ProcessState::as_self().publish_native(recorder.clone());
let unix_caps = crate::TransportCaps::FD_PASSING
| crate::TransportCaps::TRUSTED_UID
| crate::TransportCaps::SAME_HOST;
let _rpc = RpcCallingGuard::install(
Arc::new(PeerIdentity::Local { uid: 1234, pid: 42 }),
unix_caps,
);
assert_eq!(calling_caps(), Some(unix_caps));
let mut buffer = [0u8; 8];
let mut offsets = [0 as binder_size_t; 1];
let mut tr = TransactionData::default();
tr.set_target_ptr(id);
tr.code = 1;
tr.flags = binder::TF_ONE_WAY;
tr.sender_pid = 9999;
tr.sender_euid = 1000;
tr.buffer = buffer.as_mut_ptr() as _;
tr.offsets = offsets.as_mut_ptr() as _;
let (was_looper, mark) = THREAD_STATE.with(|ts| {
let mut ts = ts.borrow_mut();
let was_looper = ts.is_looper;
ts.is_looper = true;
let mark = ts.unflushed_mark();
ts.in_parcel.set_data_size(0).expect("reset in_parcel");
ts.in_parcel.set_data_position(0);
ts.in_parcel
.write_transaction(&tr)
.expect("forge BR_TRANSACTION payload");
ts.in_parcel.set_data_position(0);
(was_looper, mark)
});
let dispatched = execute_command(binder::BR_TRANSACTION as i32);
THREAD_STATE.with(|ts| {
discard_unflushed_commands(ts, mark, false);
let mut ts = ts.borrow_mut();
ts.is_looper = was_looper;
let _ = ts.in_parcel.set_data_size(0);
});
dispatched.expect("execute_command");
let (caps, caller) = recorder
.observed
.lock()
.unwrap()
.take()
.expect("the handler ran");
assert_eq!(caps, Some(crate::TransportCaps::KERNEL));
assert!(matches!(
caller,
Some(Caller::Kernel {
uid: 1000,
pid: 9999,
..
})
));
assert_eq!(calling_caps(), Some(unix_caps));
assert_eq!(get_calling_uid(), 1234);
}
#[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, None);
assert!(is_handling_transaction());
assert_eq!(get_calling_uid(), 1000);
assert!(get_calling_sid().is_none());
}
#[test]
#[cfg(target_os = "linux")]
#[serial_test::serial(binder)]
fn calling_sid_outlives_the_transaction_buffer() {
ProcessState::init_default().expect("init_default");
let mut buffer = b"u:r:system_server:s0\0".to_vec();
let mut data = TransactionData::default();
data.sender_euid = 1000;
let start = buffer.as_ptr() as usize;
let sid = copy_c_string(start, start + buffer.len());
let state = TransactionState::from_transaction_data(&data, sid);
buffer.fill(b'X');
drop(buffer);
let previous = THREAD_STATE.with(|ts| ts.borrow_mut().transaction.replace(state));
let sid = get_calling_sid();
let caller = calling_caller();
let context = CallingContext::default();
THREAD_STATE.with(|ts| ts.borrow_mut().transaction = previous);
let expected = CString::new("u:r:system_server:s0").unwrap();
assert_eq!(sid.as_ref(), Some(&expected));
match caller {
Some(Caller::Kernel { uid, sid, .. }) => {
assert_eq!((uid, sid.as_ref()), (1000, Some(&expected)));
}
other => panic!("{other:?}"),
}
assert_eq!(context.sid.as_ref(), Some(&expected));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
struct Pages {
ptr: *mut std::ffi::c_void,
len: usize,
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl Pages {
fn new(count: usize, writes: &[(usize, &[u8])], none: &[usize]) -> Self {
use rustix::mm::{MapFlags, MprotectFlags, ProtFlags};
let page = rustix::param::page_size();
let len = count * page;
let prot = ProtFlags::READ | ProtFlags::WRITE;
let ptr = unsafe {
rustix::mm::mmap_anonymous(std::ptr::null_mut(), len, prot, MapFlags::PRIVATE)
}
.unwrap();
let bytes = unsafe { std::slice::from_raw_parts_mut(ptr.cast::<u8>(), len) };
for &(at, data) in writes {
bytes[at..at + data.len()].copy_from_slice(data);
}
for &i in none {
assert!(i < count);
unsafe {
rustix::mm::mprotect(ptr.byte_add(i * page), page, MprotectFlags::empty())
}
.unwrap();
}
Pages { ptr, len }
}
fn range(&self) -> std::ops::Range<usize> {
self.ptr as usize..self.ptr as usize + self.len
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl Drop for Pages {
fn drop(&mut self) {
unsafe { rustix::mm::munmap(self.ptr, self.len) }.unwrap();
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn in_child_process(test: &str, body: fn()) {
use rustix::process::{getrlimit, setrlimit, Resource, Rlimit};
const CHILD: &str = "RSBINDER_THREAD_STATE_TEST_CHILD";
if std::env::var_os(CHILD).is_some() {
let maximum = getrlimit(Resource::Core).maximum;
let _ = setrlimit(
Resource::Core,
Rlimit {
current: Some(0),
maximum,
},
);
body();
return;
}
let out = std::process::Command::new(std::env::current_exe().unwrap())
.args([test, "--exact", "--test-threads=1"])
.env(CHILD, "1")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
out.status.success() && stdout.contains("1 passed"),
"child {test}: {:?}\n{stdout}\n{}",
out.status,
String::from_utf8_lossy(&out.stderr),
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
#[serial_test::serial(binder)]
fn a_refused_freeze_request_behind_queued_commands_fails() {
in_child_process(
"thread_state::tests::a_refused_freeze_request_behind_queued_commands_fails",
|| {
const NO_REF: u32 = 0x7fff_fff0;
ProcessState::init_default().expect("init_default");
THREAD_STATE.with(|ts| {
let mut ts = ts.borrow_mut();
for cmd in [binder::BC_INCREFS, binder::BC_DECREFS] {
ts.out_parcel.write_cmd::<u32>(&cmd).unwrap();
ts.out_parcel.write_cmd::<u32>(&0).unwrap();
}
});
assert!(send_freeze_request(NO_REF).is_err());
assert_eq!(
THREAD_STATE.with(|ts| ts.borrow().out_parcel.data_size()),
0,
"the queued commands went out and the request was rewound"
);
},
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn a_sid_copy_stops_at_a_page_it_cannot_read() {
in_child_process(
"thread_state::tests::a_sid_copy_stops_at_a_page_it_cannot_read",
|| {
let page = rustix::param::page_size();
let label = b"unconfined (enforce)";
let pages = Pages::new(3, &[(page - label.len(), label), (2 * page, b"\0")], &[1]);
let mapped = pages.range();
let addr = mapped.start + page - label.len();
assert_eq!(copy_c_string(addr, mapped.end), None);
assert_eq!(sid_at(addr, true, mapped), None);
},
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn a_sid_copy_crosses_pages_and_keeps_to_its_bound() {
let page = rustix::param::page_size();
let context = b"u:r:system_server:s0\0";
let expected = Some(CString::new("u:r:system_server:s0").unwrap());
let pages = Pages::new(2, &[(page - 8, context)], &[]);
let mapped = pages.range();
let addr = mapped.start + page - 8;
let nul = addr + context.len() - 1;
assert_eq!(copy_c_string(addr, mapped.end), expected);
assert_eq!(copy_c_string(addr, nul), None, "the NUL lies at the bound");
assert_eq!(copy_c_string(addr, addr), None);
assert_eq!(sid_at(addr, true, mapped.clone()), expected);
assert_eq!(
sid_at(addr, false, mapped.clone()),
None,
"a Smack or AppArmor host"
);
assert_eq!(
sid_at(mapped.end, true, mapped),
None,
"outside the receive mapping"
);
}
#[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, None);
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);
}
const UNSET_UID: binder::uid_t = UNSET_WORK_SOURCE as binder::uid_t;
#[test]
fn work_source_set_clear_restore_outside_a_transaction() {
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
assert!(!should_propagate_work_source());
let before_set = set_calling_work_source_uid(1234);
assert_eq!(get_calling_work_source_uid(), 1234);
assert!(should_propagate_work_source());
let before_clear = clear_calling_work_source();
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
assert!(
should_propagate_work_source(),
"clear sends the unset value on purpose, so it still propagates"
);
clear_propagate_work_source();
assert!(!should_propagate_work_source());
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
restore_calling_work_source(before_clear);
assert_eq!(get_calling_work_source_uid(), 1234);
assert!(should_propagate_work_source());
restore_calling_work_source(before_set);
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
assert!(
!should_propagate_work_source(),
"restoring an unset, non-propagating token must not turn propagation on"
);
}
#[test]
fn work_source_received_value_does_not_propagate() {
let token = set_calling_work_source_uid_without_propagation(77);
assert_eq!(get_calling_work_source_uid(), 77);
assert!(!should_propagate_work_source());
restore_calling_work_source(token);
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
}
#[test]
fn work_source_dispatch_guard_resets_and_restores_when_nested() {
set_calling_work_source_uid(10);
{
let _outer = WorkSourceDispatchGuard::enter();
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
assert!(!should_propagate_work_source());
set_calling_work_source_uid_without_propagation(20);
set_calling_work_source_uid(30);
{
let _inner = WorkSourceDispatchGuard::enter();
assert_eq!(get_calling_work_source_uid(), UNSET_UID);
set_calling_work_source_uid(40);
}
assert_eq!(get_calling_work_source_uid(), 30);
assert!(should_propagate_work_source());
}
assert_eq!(get_calling_work_source_uid(), 10);
assert!(should_propagate_work_source());
}
#[test]
fn work_source_dispatch_guard_restores_on_unwind() {
set_calling_work_source_uid(10);
let unwound = std::panic::catch_unwind(|| {
let _guard = WorkSourceDispatchGuard::enter();
set_calling_work_source_uid(99);
panic!("handler panicked");
});
assert!(unwound.is_err());
assert_eq!(get_calling_work_source_uid(), 10);
}
#[test]
fn work_source_is_written_to_the_request_header_only_when_propagating() {
fn header_work_source() -> i32 {
let mut p = Parcel::new();
p.write_interface_token("x.y.IZ").unwrap();
p.set_data_position(4);
p.read::<i32>().unwrap()
}
assert_eq!(header_work_source(), UNSET_WORK_SOURCE);
set_calling_work_source_uid(4321);
assert_eq!(header_work_source(), 4321);
clear_propagate_work_source();
assert_eq!(header_work_source(), UNSET_WORK_SOURCE);
}
#[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, false);
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, false),
format!("consumed {total} of {total}"),
"a fully consumed buffer has no command to blame"
);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn an_unset_errno_is_unknown_not_a_panic() {
#[cfg(target_os = "android")]
use libc::__errno as errno_location;
#[cfg(target_os = "linux")]
use libc::__errno_location as errno_location;
unsafe { *errno_location() = 0 };
assert_eq!(last_os_error_status(), StatusCode::Unknown);
unsafe { *errno_location() = libc::EPERM };
assert_eq!(last_os_error_status(), StatusCode::PermissionDenied);
}
#[test]
fn a_short_write_names_the_command_before_the_offset() {
let mut out = CommandStream::new();
out.write_cmd::<u32>(&binder::BC_INCREFS).unwrap();
out.write_cmd::<u32>(&7u32).unwrap();
out.write_cmd::<u32>(&binder::BC_TRANSACTION).unwrap();
out.write_transaction(&TransactionData::default()).unwrap();
let second = out.data_size();
out.write_cmd::<u32>(&binder::BC_FREE_BUFFER).unwrap();
out.write_cmd::<binder_uintptr_t>(&0).unwrap();
out.set_data_position(4);
let msg = describe_refused_command(&mut out, second, true);
assert!(
msg.contains("return_error at BC_TRANSACTION"),
"the command that failed: {msg}"
);
assert!(msg.contains("stopped before BC_FREE_BUFFER"), "{msg}");
assert_eq!(
out.data_position(),
4,
"the walk restores the read position"
);
let msg = describe_refused_command(&mut out, second - 4, true);
assert!(msg.contains("<no command ends at"), "{msg}");
}
}