use std::default::Default;
use std::os::fd::{BorrowedFd, OwnedFd, RawFd};
use std::vec::Vec;
use pretty_hex::*;
use crate::{
binder,
binder_object::{read_flat_binder, write_flat_binder, FlatBinderObject},
error::{Result, StatusCode},
parcelable::*,
sys::binder::binder_size_t,
sys::{binder_uintptr_t, BINDER_TYPE_FD, BINDER_TYPE_HANDLE},
thread_state,
};
const STRICT_MODE_PENALTY_GATHER: i32 = 1 << 31;
pub(crate) trait WireScalar: Copy {
type Bytes: AsRef<[u8]>;
fn to_wire(self) -> Self::Bytes;
fn from_wire(bytes: &[u8]) -> Result<Self>;
fn vec_from_wire(bytes: &[u8]) -> Vec<Self>;
fn encode_wire(src: &[Self], dst: &mut [u8]);
fn append_wire(out: &mut Vec<u8>, src: &[Self]);
}
pub(crate) trait NativeScalar: Copy {
type Bytes: AsRef<[u8]>;
fn to_native(self) -> Self::Bytes;
fn from_native(bytes: &[u8]) -> Result<Self>;
}
macro_rules! impl_scalar_codecs {
($($t:ty),* $(,)?) => { $(
impl WireScalar for $t {
type Bytes = [u8; std::mem::size_of::<$t>()];
fn to_wire(self) -> Self::Bytes {
self.to_le_bytes()
}
fn from_wire(bytes: &[u8]) -> Result<Self> {
Ok(<$t>::from_le_bytes(bytes.try_into()?))
}
fn vec_from_wire(bytes: &[u8]) -> Vec<Self> {
let chunks = bytes.chunks_exact(std::mem::size_of::<$t>());
let mut out = Vec::with_capacity(chunks.len());
out.extend(chunks.map(|c| {
let mut b = [0u8; std::mem::size_of::<$t>()];
b.copy_from_slice(c);
<$t>::from_le_bytes(b)
}));
out
}
fn encode_wire(src: &[Self], dst: &mut [u8]) {
for (d, v) in dst.chunks_exact_mut(std::mem::size_of::<$t>()).zip(src) {
d.copy_from_slice(&v.to_le_bytes());
}
}
fn append_wire(out: &mut Vec<u8>, src: &[Self]) {
out.extend(src.iter().flat_map(|v| v.to_le_bytes()));
}
}
impl NativeScalar for $t {
type Bytes = [u8; std::mem::size_of::<$t>()];
fn to_native(self) -> Self::Bytes {
self.to_ne_bytes()
}
fn from_native(bytes: &[u8]) -> Result<Self> {
Ok(<$t>::from_ne_bytes(bytes.try_into()?))
}
}
)* };
}
impl_scalar_codecs!(i8, u8, i16, u16, i32, u32, i64, u64, u128, f32, f64);
#[inline]
pub(crate) fn pad_size(len: usize) -> usize {
(len + 3) & (!3)
}
#[inline]
pub(crate) fn checked_array_layout(len: i32, elem_size: usize) -> Result<(usize, usize)> {
debug_assert!(
len >= 1,
"checked_array_layout: caller must validate len >= 1"
);
let size = (len as usize)
.checked_mul(elem_size)
.ok_or(StatusCode::BadValue)?;
let padded = size.checked_add(3).ok_or(StatusCode::BadValue)? & !3;
Ok((size, padded))
}
const MAX_OUT_VEC_BYTES: usize = 1_000_000;
fn check_out_vec_size<D>(len: usize) -> Result<()> {
match len.checked_mul(std::mem::size_of::<D>()) {
Some(bytes) if bytes < MAX_OUT_VEC_BYTES => Ok(()),
_ => Err(StatusCode::NoMemory),
}
}
pub(crate) trait CharType: Clone {
type Output;
fn as_i32(&self) -> i32;
fn from(v: &i32) -> Self::Output;
}
impl CharType for i16 {
type Output = i16;
fn as_i32(&self) -> i32 {
*self as _
}
fn from(v: &i32) -> Self::Output {
*v as _
}
}
impl CharType for u16 {
type Output = u16;
fn as_i32(&self) -> i32 {
*self as _
}
fn from(v: &i32) -> Self::Output {
*v as _
}
}
pub(crate) enum ParcelData<T: Clone + Default + 'static> {
Vec(Vec<T>),
Slice(&'static [T]),
}
impl<T: Clone + Default> ParcelData<T> {
fn new() -> Self {
ParcelData::Vec(Vec::new())
}
fn with_capacity(capacity: usize) -> Self {
ParcelData::Vec(Vec::with_capacity(capacity))
}
fn from_vec(data: Vec<T>) -> Self {
ParcelData::Vec(data)
}
unsafe fn from_raw_parts_mut(data: *mut T, len: usize) -> Self {
ParcelData::Slice(if data.is_null() {
debug_assert_eq!(len, 0, "non-zero length with null data is invalid");
&[]
} else {
unsafe { std::slice::from_raw_parts(data, len) }
})
}
fn as_slice(&self) -> &[T] {
match self {
ParcelData::Vec(v) => v.as_slice(),
ParcelData::Slice(s) => s,
}
}
fn as_mut_slice(&mut self) -> &mut [T] {
match self {
ParcelData::Vec(v) => v.as_mut_slice(),
_ => panic!("&[u8] can't support as_mut_slice()."),
}
}
pub(crate) fn as_ptr(&self) -> *const T {
match self {
ParcelData::Vec(ref v) => v.as_ptr(),
ParcelData::Slice(s) => s.as_ptr(),
}
}
pub(crate) fn len(&self) -> usize {
self.as_slice().len()
}
fn resize(&mut self, len: usize) {
match self {
ParcelData::Vec(v) => v.resize(len, T::default()),
_ => panic!("&[u8] can't support resize()."),
}
}
fn capacity(&self) -> usize {
match self {
ParcelData::Vec(v) => v.capacity(),
ParcelData::Slice(s) => s.len(),
}
}
fn reserve(&mut self, additional: usize) {
match self {
ParcelData::Vec(v) => v.reserve(additional),
_ => panic!("&[u8] can't support reserve()."),
}
}
fn push(&mut self, other: T) {
match self {
ParcelData::Vec(v) => v.push(other),
_ => panic!("push() is only available for ParcelData::Vec."),
}
}
fn truncate(&mut self, len: usize) {
match self {
ParcelData::Vec(v) => v.truncate(len),
_ => panic!("&[u8] can't support truncate()."),
}
}
}
impl ParcelData<u8> {
fn write_wire_at<S: WireScalar>(&mut self, pos: usize, src: &[S], pad: usize) {
let size = std::mem::size_of_val(src);
match self {
ParcelData::Vec(v) if pos == v.len() => {
v.reserve(size + pad);
S::append_wire(v, src);
v.resize(pos + size + pad, 0);
}
_ => {
self.write_at(pos, &[], size + pad);
S::encode_wire(src, &mut self.as_mut_slice()[pos..pos + size]);
}
}
}
fn write_at(&mut self, pos: usize, bytes: &[u8], pad: usize) {
let ParcelData::Vec(v) = self else {
panic!("&[u8] can't support write_at().");
};
if pos > v.len() {
v.resize(pos, 0);
}
let end = pos + bytes.len();
let overwrite = v.len().min(end) - pos;
v[pos..pos + overwrite].copy_from_slice(&bytes[..overwrite]);
v.extend_from_slice(&bytes[overwrite..]);
let pad_end = end + pad;
let fill_to = v.len().min(pad_end);
v[end..fill_to].fill(0);
if v.len() < pad_end {
v.resize(pad_end, 0);
}
}
}
pub(crate) type FnFreeBuffer =
fn(Option<&Parcel>, binder_uintptr_t, usize, binder_uintptr_t, usize) -> Result<()>;
#[cfg(feature = "rpc")]
pub(crate) trait RpcParcelOps: Send + Sync {
fn write_binder(
&self,
binder: Option<&crate::binder::SIBinder>,
parcel: &mut Parcel,
) -> Result<()>;
fn read_binder(&self, parcel: &mut Parcel) -> Result<Option<crate::binder::SIBinder>>;
fn cancel_leaving(&self, addrs: &[crate::rpc::RpcAddress]);
fn session_id(&self) -> *const ();
fn records_binder_positions(&self) -> Result<bool>;
fn acquire_copied(&self, objects: &[&[u8]]) -> Result<CopiedBinders>;
}
#[cfg(feature = "rpc")]
#[derive(Default)]
pub(crate) struct CopiedBinders {
pub(crate) leaving: Vec<crate::rpc::RpcAddress>,
pub(crate) pinned: Vec<crate::binder::SIBinder>,
}
#[cfg(feature = "rpc")]
struct StagedRpcCopy {
positions: Vec<usize>,
fds: Vec<(usize, std::os::fd::OwnedFd)>,
binders: CopiedBinders,
}
#[cfg(feature = "rpc")]
const RPC_TYPE_BINDER: i32 = 1;
#[cfg(feature = "rpc")]
fn le_i32_at(bytes: &[u8], pos: usize) -> Option<i32> {
let word = bytes.get(pos..pos.checked_add(4)?)?;
Some(i32::from_le_bytes(word.try_into().ok()?))
}
#[cfg(feature = "rpc")]
const RPC_BINDER_SIZE: usize = 4 + crate::rpc::wire_android13::A13_ADDR_LEN;
#[cfg(feature = "rpc")]
fn rpc_object_size(bytes: &[u8], off: usize) -> usize {
match le_i32_at(bytes, off) {
Some(0) => 4,
Some(crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR) => 8,
_ => RPC_BINDER_SIZE,
}
}
#[cfg(feature = "rpc")]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum RpcSendState {
NotSent = 0,
InFlight = 1,
Sent = 2,
Received = 3,
}
#[cfg(feature = "rpc")]
impl RpcSendState {
fn from_u8(v: u8) -> Self {
match v {
1 => Self::InFlight,
2 => Self::Sent,
3 => Self::Received,
_ => Self::NotSent,
}
}
}
#[derive(Default)]
struct RpcFields {
#[cfg(feature = "rpc")]
ops: Option<std::sync::Arc<dyn RpcParcelOps>>,
#[cfg(feature = "rpc")]
fd_mode: crate::rpc::FileDescriptorTransportMode,
#[cfg(feature = "rpc")]
fds_out: Vec<std::os::fd::OwnedFd>,
#[cfg(feature = "rpc")]
fds_in: Vec<Option<std::os::fd::OwnedFd>>,
#[cfg(feature = "rpc")]
object_positions: Vec<u32>,
#[cfg(feature = "rpc")]
record_fd_positions: bool,
#[cfg(feature = "rpc")]
leaving_addrs: Vec<crate::rpc::RpcAddress>,
#[cfg(feature = "rpc")]
send_state: std::sync::atomic::AtomicU8,
#[cfg(feature = "rpc")]
pinned: Vec<crate::binder::SIBinder>,
#[cfg(feature = "rpc")]
entered: Vec<(u32, crate::rpc::RpcAddress, crate::binder::SIBinder)>,
#[cfg(feature = "rpc")]
received_len: usize,
}
#[cfg(feature = "rpc")]
impl RpcFields {
fn send_state(&self) -> RpcSendState {
RpcSendState::from_u8(self.send_state.load(std::sync::atomic::Ordering::Acquire))
}
fn settle_unsent(&mut self) {
if self.send_state() == RpcSendState::Sent || self.leaving_addrs.is_empty() {
return;
}
let addrs = std::mem::take(&mut self.leaving_addrs);
if let Some(ops) = &self.ops {
ops.cancel_leaving(&addrs);
}
}
fn record_object_position(&mut self, pos: usize) {
let pos = pos as u32;
let at = self.object_positions.partition_point(|&p| p <= pos);
self.object_positions.insert(at, pos);
}
fn object_position_present(&self, pos: usize) -> bool {
let Ok(pos) = u32::try_from(pos) else {
return false;
};
self.object_positions.binary_search(&pos).is_ok()
}
fn push_out_fd(&mut self, fd: std::os::fd::OwnedFd) -> i32 {
let idx = self.fds_out.len() as i32;
self.fds_out.push(fd);
idx
}
fn set_in_fds(&mut self, fds: Vec<std::os::fd::OwnedFd>) {
self.fds_in = fds.into_iter().map(Some).collect();
}
fn take_in_fd(&mut self, index: usize) -> Option<std::os::fd::OwnedFd> {
self.fds_in.get_mut(index).and_then(Option::take)
}
fn source_fd(&self, index: usize) -> Option<std::os::fd::BorrowedFd<'_>> {
use std::os::fd::AsFd;
if self.fds_in.is_empty() {
self.fds_out.get(index).map(AsFd::as_fd)
} else {
self.fds_in.get(index)?.as_ref().map(AsFd::as_fd)
}
}
}
const MAX_NESTED_READ_DEPTH: usize = 1000;
pub struct Parcel {
data: ParcelData<u8>,
pub(crate) objects: ParcelData<binder_size_t>,
objects_end: u64,
pos: usize,
next_object_hint: usize,
read_boundary: Option<usize>,
nested_read_depth: usize,
request_header_present: bool,
work_source_request_header_pos: usize,
free_buffer: Option<FnFreeBuffer>,
rpc: Option<RpcFields>,
kernel_pinned: Vec<crate::binder::SIBinder>,
kernel_fds: Vec<(u64, OwnedFd)>,
fd_handles_borrowed: bool,
}
impl Default for Parcel {
fn default() -> Self {
Parcel::with_capacity(256)
}
}
impl Parcel {
pub fn new() -> Self {
Parcel::with_capacity(256)
}
pub fn with_capacity(capacity: usize) -> Self {
Parcel {
data: ParcelData::with_capacity(capacity),
objects: ParcelData::new(),
objects_end: 0,
pos: 0,
next_object_hint: 0,
read_boundary: None,
nested_read_depth: 0,
request_header_present: false,
work_source_request_header_pos: 0,
free_buffer: None,
rpc: None,
kernel_pinned: Vec::new(),
kernel_fds: Vec::new(),
fd_handles_borrowed: false,
}
}
pub unsafe fn from_ipc_parts(
data: *mut u8,
length: usize,
objects: *mut binder_size_t,
object_count: usize,
free_buffer: fn(
Option<&Parcel>,
binder_uintptr_t,
usize,
binder_uintptr_t,
usize,
) -> Result<()>,
) -> Self {
let mut parcel =
unsafe { Parcel::over_ipc_buffer(data, length, objects, object_count, free_buffer) };
parcel.fd_handles_borrowed = true;
parcel
}
pub(crate) unsafe fn from_driver_buffer(
data: *mut u8,
length: usize,
objects: *mut binder_size_t,
object_count: usize,
free_buffer: FnFreeBuffer,
) -> Self {
let mut parcel =
unsafe { Parcel::over_ipc_buffer(data, length, objects, object_count, free_buffer) };
let size = FlatBinderObject::SIZE as u64;
let mut fds: Vec<(u64, RawFd)> = Vec::new();
for &off in parcel.objects.as_slice() {
match read_flat_binder(parcel.data.as_slice(), off as usize) {
Ok(obj) if obj.header_type() == BINDER_TYPE_FD => {
fds.push((off, obj.handle() as RawFd))
}
Ok(_) => {}
Err(_) => log::error!("Parcel: driver object at {off} is out of the buffer"),
}
}
fds.sort_unstable_by_key(|&(off, _)| off);
let mut end = 0u64;
for (off, raw) in fds {
if off < end || raw < 0 {
log::error!("Parcel: driver fd object at {off} (fd {raw}) not adopted");
continue;
}
end = off + size;
let fd = unsafe { <OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(raw) };
parcel.kernel_fds.push((off, fd));
}
parcel
}
unsafe fn over_ipc_buffer(
data: *mut u8,
length: usize,
objects: *mut binder_size_t,
object_count: usize,
free_buffer: FnFreeBuffer,
) -> Self {
Parcel {
data: unsafe { ParcelData::from_raw_parts_mut(data, length) },
objects: if (objects as usize) % std::mem::align_of::<binder_size_t>() == 0 {
unsafe { ParcelData::from_raw_parts_mut(objects, object_count) }
} else {
ParcelData::Vec(
(0..object_count)
.map(|i| unsafe { objects.add(i).read_unaligned() })
.collect(),
)
},
objects_end: u64::MAX,
pos: 0,
next_object_hint: 0,
read_boundary: None,
nested_read_depth: 0,
request_header_present: false,
work_source_request_header_pos: 0,
free_buffer: Some(free_buffer),
rpc: None,
kernel_pinned: Vec::new(),
kernel_fds: Vec::new(),
fd_handles_borrowed: false,
}
}
pub(crate) fn from_vec(data: Vec<u8>) -> Self {
Parcel {
data: ParcelData::from_vec(data),
objects: ParcelData::new(),
objects_end: 0,
pos: 0,
next_object_hint: 0,
read_boundary: None,
nested_read_depth: 0,
request_header_present: false,
work_source_request_header_pos: 0,
free_buffer: None,
rpc: None,
kernel_pinned: Vec::new(),
kernel_fds: Vec::new(),
fd_handles_borrowed: false,
}
}
pub(crate) fn new_data_only() -> Self {
Parcel::data_only_from_vec(Vec::with_capacity(256))
}
pub(crate) fn from_slice(bytes: &[u8]) -> Self {
Parcel::data_only_from_vec(bytes.to_vec())
}
pub(crate) fn data_only_from_vec(bytes: Vec<u8>) -> Self {
let mut p = Parcel::from_vec(bytes);
p.rpc = Some(RpcFields::default());
p
}
pub(crate) fn refill_data_only(&mut self, bytes: &[u8]) {
debug_assert!(self.rpc.is_some() && self.is_self_contained(), "data-only");
match &mut self.data {
ParcelData::Vec(v) => {
v.clear();
v.extend_from_slice(bytes);
}
ParcelData::Slice(_) => self.data = ParcelData::Vec(bytes.to_vec()),
}
self.pos = 0;
self.next_object_hint = 0;
self.read_boundary = None;
self.nested_read_depth = 0;
}
pub(crate) fn is_self_contained(&self) -> bool {
if self.objects.len() != 0 {
return false;
}
#[cfg(feature = "rpc")]
if !self.rpc_object_positions().is_empty() || !self.rpc_out_fds().is_empty() {
return false;
}
true
}
pub(crate) fn as_bytes(&self) -> Result<&[u8]> {
if !self.is_self_contained() {
return Err(StatusCode::BadType);
}
Ok(self.data.as_slice())
}
pub(crate) fn into_bytes(self) -> Result<Vec<u8>> {
let mut this = self;
this.as_bytes()?;
match &mut this.data {
ParcelData::Vec(bytes) => {
debug_assert!(this.free_buffer.is_none(), "an owned buffer is never freed");
Ok(std::mem::take(bytes))
}
ParcelData::Slice(bytes) => Ok(bytes.to_vec()),
}
}
pub(crate) fn as_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
pub(crate) fn ipc_data_size(&self) -> usize {
self.data.len()
}
#[cfg(test)]
pub(crate) fn capacity(&self) -> usize {
self.data.capacity()
}
pub(crate) fn is_empty(&self) -> bool {
self.pos >= self.data.len()
}
#[cfg(any(test, all(feature = "rpc", feature = "test-util")))]
pub(crate) fn set_for_rpc(&mut self, yes: bool) -> Result<()> {
if self.data.len() != 0 || self.free_buffer.is_some() {
log::error!("Parcel::set_for_rpc: the mode is set before anything is written");
return Err(StatusCode::InvalidOperation);
}
debug_assert!(self.objects.len() == 0 && self.kernel_fds.is_empty());
if yes {
self.rpc.get_or_insert_with(RpcFields::default);
} else {
#[cfg(feature = "rpc")]
if let Some(rpc) = self.rpc.as_mut() {
rpc.settle_unsent();
}
self.rpc = None;
}
Ok(())
}
#[cfg(all(feature = "rpc", feature = "test-util"))]
#[doc(hidden)]
pub fn __set_for_rpc(&mut self, yes: bool) -> Result<()> {
self.set_for_rpc(yes)
}
pub fn is_kernel_backed(&self) -> bool {
self.rpc.is_none()
}
#[cfg_attr(
feature = "rpc",
doc = "[`FileDescriptorTransportMode::Unix`](crate::rpc::FileDescriptorTransportMode),"
)]
#[cfg_attr(
not(feature = "rpc"),
doc = "`FileDescriptorTransportMode::Unix` (`rpc` feature),"
)]
pub fn allow_fds(&self) -> bool {
if self.is_kernel_backed() {
return true;
}
#[cfg(feature = "rpc")]
{
self.rpc_fd_mode() == crate::rpc::FileDescriptorTransportMode::Unix
}
#[cfg(not(feature = "rpc"))]
false
}
#[deprecated(
since = "0.11.0",
note = "renamed and inverted: use `!parcel.is_kernel_backed()`. The old name \
described one consumer of the mode rather than what the flag controls, \
and read fail-open at the one security branch that uses it."
)]
pub fn is_for_rpc(&self) -> bool {
!self.is_kernel_backed()
}
#[cfg(feature = "rpc")]
pub(crate) fn attach_rpc_ops(&mut self, ops: std::sync::Arc<dyn RpcParcelOps>) {
self.rpc.get_or_insert_with(RpcFields::default).ops = Some(ops);
}
#[cfg(feature = "rpc")]
pub(crate) fn configure_rpc(
&mut self,
ops: std::sync::Arc<dyn RpcParcelOps>,
fd_mode: crate::rpc::FileDescriptorTransportMode,
record_fd_positions: bool,
) {
let rpc = self.rpc.get_or_insert_with(RpcFields::default);
rpc.ops = Some(ops);
rpc.fd_mode = fd_mode;
rpc.record_fd_positions = record_fd_positions;
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_ops(&self) -> Option<std::sync::Arc<dyn RpcParcelOps>> {
self.rpc.as_ref().and_then(|r| r.ops.clone())
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_data_bytes(&self) -> &[u8] {
self.data.as_slice()
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_object_positions(&self) -> &[u32] {
self.rpc
.as_ref()
.map_or(&[], |r| r.object_positions.as_slice())
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_set_object_positions(&mut self, positions: Vec<u32>) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.object_positions = positions;
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_record_leaving_addr(&mut self, addr: crate::rpc::RpcAddress) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.leaving_addrs.push(addr);
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_session_id(&self) -> Option<*const ()> {
self.rpc.as_ref()?.ops.as_ref().map(|ops| ops.session_id())
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_is_unsent(&self) -> bool {
self.rpc
.as_ref()
.is_none_or(|r| r.send_state() == RpcSendState::NotSent)
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_mark_received(&mut self) {
let len = self.data.len();
if let Some(rpc) = self.rpc.as_mut() {
rpc.send_state.store(
RpcSendState::Received as u8,
std::sync::atomic::Ordering::Release,
);
rpc.received_len = len;
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_received_at(&self, pos: usize) -> bool {
self.rpc
.as_ref()
.is_some_and(|r| r.send_state() == RpcSendState::Received && pos < r.received_len)
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_entered_at(
&self,
pos: usize,
) -> Option<(crate::rpc::RpcAddress, crate::binder::SIBinder)> {
let rpc = self.rpc.as_ref()?;
let pos = u32::try_from(pos).ok()?;
let at = rpc.entered.binary_search_by_key(&pos, |e| e.0).ok()?;
let (_, addr, binder) = &rpc.entered[at];
Some((*addr, binder.clone()))
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_record_entered(
&mut self,
pos: usize,
addr: crate::rpc::RpcAddress,
binder: crate::binder::SIBinder,
) {
let (Some(rpc), Ok(pos)) = (self.rpc.as_mut(), u32::try_from(pos)) else {
return;
};
match rpc.entered.binary_search_by_key(&pos, |e| e.0) {
Ok(at) => rpc.entered[at] = (pos, addr, binder),
Err(at) => rpc.entered.insert(at, (pos, addr, binder)),
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_begin_send(&self) -> Result<()> {
use std::sync::atomic::Ordering;
let Some(rpc) = self.rpc.as_ref() else {
return Ok(());
};
match rpc.send_state.compare_exchange(
RpcSendState::NotSent as u8,
RpcSendState::InFlight as u8,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => Ok(()),
Err(state) => {
let state = RpcSendState::from_u8(state);
log::error!("RPC: a parcel is sent once; build a new request (state {state:?})");
Err(StatusCode::InvalidOperation)
}
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_end_send(&self, sent: bool) {
let Some(rpc) = self.rpc.as_ref() else {
return;
};
let next = if sent {
RpcSendState::Sent
} else {
RpcSendState::NotSent
};
rpc.send_state
.store(next as u8, std::sync::atomic::Ordering::Release);
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_pin_binder(&mut self, binder: crate::binder::SIBinder) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.pinned.push(binder);
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_object_position_present(&self, pos: usize) -> bool {
self.rpc
.as_ref()
.is_some_and(|r| r.object_position_present(pos))
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_record_object_position(&mut self, pos: usize) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.record_object_position(pos);
}
}
#[cfg(all(feature = "rpc", any(test, feature = "fuzzing")))]
pub(crate) fn set_rpc_fd_mode(&mut self, mode: crate::rpc::FileDescriptorTransportMode) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.fd_mode = mode;
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_fd_mode(&self) -> crate::rpc::FileDescriptorTransportMode {
self.rpc.as_ref().map_or(Default::default(), |r| r.fd_mode)
}
#[cfg(all(feature = "rpc", any(test, feature = "fuzzing")))]
pub(crate) fn set_rpc_record_fd_positions(&mut self, yes: bool) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.record_fd_positions = yes;
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_record_fd_positions(&self) -> bool {
self.rpc.as_ref().is_some_and(|r| r.record_fd_positions)
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_push_out_fd(&mut self, fd: std::os::fd::OwnedFd) -> i32 {
self.rpc
.as_mut()
.expect("rpc_push_out_fd on kernel parcel")
.push_out_fd(fd)
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_out_fds(&self) -> &[std::os::fd::OwnedFd] {
self.rpc.as_ref().map_or(&[], |r| r.fds_out.as_slice())
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_set_in_fds(&mut self, fds: Vec<std::os::fd::OwnedFd>) {
if let Some(rpc) = self.rpc.as_mut() {
rpc.set_in_fds(fds);
}
}
#[cfg(feature = "rpc")]
pub(crate) fn rpc_take_in_fd(&mut self, index: usize) -> Option<std::os::fd::OwnedFd> {
self.rpc.as_mut().and_then(|r| r.take_in_fd(index))
}
pub(crate) fn set_data_size(&mut self, new_len: usize) -> Result<()> {
if new_len > self.data.len() {
log::error!(
"set_data_size({new_len}) would grow past the initialized length {}",
self.data.len()
);
return Err(StatusCode::BadValue);
}
if self.recorded_objects_end() > new_len as u64 {
log::error!("set_data_size({new_len}) would cut a recorded object");
return Err(StatusCode::InvalidOperation);
}
self.data.truncate(new_len);
if new_len < self.pos {
self.pos = new_len;
}
Ok(())
}
pub(crate) fn driver_read_buffer(&mut self) -> &mut [u8] {
let capacity = self.data.capacity();
self.data.resize(capacity);
self.data.as_mut_slice()
}
pub(crate) fn driver_read_done(&mut self, filled: usize) -> Result<()> {
self.pos = 0;
if filled > self.data.len() {
log::error!(
"driver_read_done({filled}) exceeds the buffer {}",
self.data.len()
);
self.data.truncate(0);
return Err(StatusCode::BadValue);
}
self.data.truncate(filled);
Ok(())
}
fn recorded_objects_end(&self) -> u64 {
let size = FlatBinderObject::SIZE as u64;
let kernel = self.objects.as_slice().iter().map(|&off| off + size).max();
#[cfg(feature = "rpc")]
let rpc = self.rpc_object_positions().iter().map(|&p| {
let p = p as usize;
(p + rpc_object_size(self.data.as_slice(), p)) as u64
});
#[cfg(not(feature = "rpc"))]
let rpc = std::iter::empty();
kernel.into_iter().chain(rpc).max().unwrap_or(0)
}
pub fn set_data_position(&mut self, pos: usize) {
if pos > i32::MAX as usize {
log::error!("Parcel::set_data_position({pos}) exceeds i32::MAX; ignored");
return;
}
self.pos = pos;
}
pub fn data_position(&self) -> usize {
self.pos
}
pub fn data_size(&self) -> usize {
if self.data.len() > self.pos {
self.data.len()
} else {
self.pos
}
}
pub fn read<D: Deserialize>(&mut self) -> Result<D> {
D::deserialize(self)
}
pub fn read_onto<D: Deserialize>(&mut self, x: &mut D) -> Result<()> {
x.deserialize_from(self)
}
pub fn write_i32(&mut self, val: i32) -> Result<()> {
self.write(&val)
}
pub fn write_u32(&mut self, val: u32) -> Result<()> {
self.write(&val)
}
pub fn write_i64(&mut self, val: i64) -> Result<()> {
self.write(&val)
}
pub fn write_u64(&mut self, val: u64) -> Result<()> {
self.write(&val)
}
pub fn write_f32(&mut self, val: f32) -> Result<()> {
self.write(&val)
}
pub fn write_f64(&mut self, val: f64) -> Result<()> {
self.write(&val)
}
pub fn write_bool(&mut self, val: bool) -> Result<()> {
self.write(&val)
}
pub fn write_i8(&mut self, val: i8) -> Result<()> {
self.write(&val)
}
pub fn write_u8(&mut self, val: u8) -> Result<()> {
self.write(&val)
}
pub fn read_i32(&mut self) -> Result<i32> {
self.read()
}
pub fn read_u32(&mut self) -> Result<u32> {
self.read()
}
pub fn read_i64(&mut self) -> Result<i64> {
self.read()
}
pub fn read_u64(&mut self) -> Result<u64> {
self.read()
}
pub fn read_f32(&mut self) -> Result<f32> {
self.read()
}
pub fn read_f64(&mut self) -> Result<f64> {
self.read()
}
pub fn read_bool(&mut self) -> Result<bool> {
self.read()
}
pub fn read_i8(&mut self) -> Result<i8> {
self.read()
}
pub fn read_u8(&mut self) -> Result<u8> {
self.read()
}
pub fn data_avail(&self) -> usize {
let result = self.data.len().saturating_sub(self.pos);
assert!(result < i32::MAX as _, "data too big: {result}");
result
}
pub(crate) fn read_aligned_data(&mut self, len: usize) -> Result<&[u8]> {
let aligned = pad_size(len);
let pos = self.pos;
if aligned <= self.data_avail() {
self.pos = pos + aligned;
Ok(&self.data.as_slice()[pos..pos + len])
} else {
log::error!(
"Not enough data to read aligned data.: {aligned} <= {}",
self.data_avail()
);
Err(StatusCode::NotEnoughData)
}
}
pub(crate) fn read_object(&mut self, null_meta: bool) -> Result<FlatBinderObject> {
if self.rpc.is_some() {
return Err(StatusCode::BadType);
}
let data_pos = self.pos as u64;
let size = FlatBinderObject::SIZE;
let obj = read_flat_binder(self.read_aligned_data(size)?, 0)?;
if !null_meta && obj.cookie == 0 && obj.pointer() == 0 {
return Ok(obj);
}
let objects = self.objects.as_slice();
let count = objects.len();
let mut opos = self.next_object_hint;
if count > 0 {
log::trace!("Parcel looking for obj at {data_pos}, hint={opos}");
if opos < count {
while opos < (count - 1) && objects[opos] < data_pos {
opos += 1;
}
} else {
opos = count - 1;
}
if objects[opos] == data_pos {
self.next_object_hint = opos + 1;
return Ok(obj);
}
while opos > 0 && objects[opos] > data_pos {
opos -= 1;
}
if objects[opos] == data_pos {
self.next_object_hint = opos + 1;
return Ok(obj);
}
}
log::error!("Parcel: unable to find object at index {data_pos}");
Err(StatusCode::BadType)
}
pub fn sized_read<F>(&mut self, f: F) -> Result<()>
where
for<'b> F: FnOnce(&mut Parcel) -> Result<()>,
{
let start = self.data_position();
let parcelable_size: i32 = self.read()?;
if parcelable_size < 4 {
log::error!("Parcel: bad size for object: {parcelable_size}");
return Err(StatusCode::BadValue);
}
let end = start.checked_add(parcelable_size as _).ok_or_else(|| {
log::error!("Parcel: check_add error: {parcelable_size}");
StatusCode::BadValue
})?;
if end > self.data_size() {
log::error!("Parcel: not enough data: {} > {}", end, self.data_size());
return Err(StatusCode::NotEnoughData);
}
if self.nested_read_depth >= MAX_NESTED_READ_DEPTH {
log::error!("Parcel: nested parcelable read depth exceeded {MAX_NESTED_READ_DEPTH}");
return Err(StatusCode::BadValue);
}
self.nested_read_depth += 1;
let prev_boundary = self.read_boundary;
self.read_boundary = Some(end);
let result = f(self);
self.read_boundary = prev_boundary;
self.nested_read_depth -= 1;
result?;
self.set_data_position(end);
Ok(())
}
pub fn has_more_data(&self) -> bool {
let end = self.read_boundary.unwrap_or_else(|| self.data_size());
self.pos < end
}
pub(crate) fn read_array<D: Deserialize + WireScalar>(&mut self) -> Result<Option<Vec<D>>> {
let len: i32 = self.read()?;
if len < -1 {
log::error!("Parcel: bad array length: {len}");
return Err(StatusCode::UnexpectedNull);
}
if len == -1 {
return Ok(None);
}
if len == 0 {
return Ok(Some(Vec::new()));
}
let (size, padded) = checked_array_layout(len, std::mem::size_of::<D>())?;
if padded > self.data_avail() {
log::error!(
"Parcel: not enough data to read array: {} > {}",
padded,
self.data_avail()
);
return Err(StatusCode::NotEnoughData);
}
let pos = self.pos;
let data_slice = self
.data
.as_slice()
.get(pos..pos + size)
.ok_or(StatusCode::NotEnoughData)?;
let result = D::vec_from_wire(data_slice);
self.set_data_position(pos + padded);
Ok(Some(result))
}
pub(crate) fn read_array_char<D: CharType>(
&mut self,
) -> Result<Option<Vec<<D as CharType>::Output>>> {
let len: i32 = self.read()?;
if len < -1 {
log::error!("Parcel: bad array length: {len}");
return Err(StatusCode::UnexpectedNull);
}
if len == -1 {
return Ok(None);
}
if len == 0 {
return Ok(Some(Vec::new()));
}
let (size, padded) = checked_array_layout(len, std::mem::size_of::<i32>())?;
if padded > self.data_avail() {
log::error!(
"Parcel: not enough data to read array char: {} > {}",
padded,
self.data_avail()
);
return Err(StatusCode::NotEnoughData);
}
let pos = self.pos;
let result = self.data.as_slice()[pos..pos + size]
.chunks_exact(std::mem::size_of::<i32>())
.map(|c| D::from(&i32::from_le_bytes([c[0], c[1], c[2], c[3]])))
.collect();
self.set_data_position(pos + padded);
Ok(Some(result))
}
pub fn resize_out_vec<D: Default + Deserialize>(&mut self, out_vec: &mut Vec<D>) -> Result<()> {
let len: i32 = self.read()?;
if len < 0 {
return Err(StatusCode::UnexpectedNull);
}
let len = len.try_into().or(Err(StatusCode::BadValue))?;
check_out_vec_size::<D>(len)?;
out_vec.resize_with(len, Default::default);
Ok(())
}
pub fn resize_nullable_out_vec<D: Default + Deserialize>(
&mut self,
out_vec: &mut Option<Vec<D>>,
) -> Result<()> {
let len: i32 = self.read()?;
if len < 0 {
*out_vec = None;
} else {
let len = len.try_into().or(Err(StatusCode::BadValue))?;
check_out_vec_size::<D>(len)?;
let mut vec = Vec::with_capacity(len);
vec.resize_with(len, Default::default);
*out_vec = Some(vec);
}
Ok(())
}
pub(crate) fn update_work_source_request_header_pos(&mut self) {
if !self.request_header_present {
self.work_source_request_header_pos = self.data.len();
self.request_header_present = true;
}
}
pub fn write<S: Serialize + ?Sized>(&mut self, parcelable: &S) -> Result<()> {
parcelable.serialize(self)
}
pub(crate) fn write_array<S: Serialize + WireScalar>(
&mut self,
parcelable: &[S],
) -> Result<()> {
let len = parcelable.len();
let len_i32: i32 = len.try_into().or(Err(StatusCode::BadValue))?;
self.write::<i32>(&len_i32)?;
if len == 0 {
return Ok(());
}
let size = std::mem::size_of_val(parcelable);
let padded = pad_size(size);
let pos = self.pos;
let end = pos
.checked_add(padded)
.filter(|&e| e <= i32::MAX as usize)
.ok_or(StatusCode::BadValue)?;
self.check_object_overlap(end)?;
self.data.write_wire_at(pos, parcelable, padded - size);
self.set_data_position(end);
Ok(())
}
pub(crate) fn write_array_char<S: CharType>(&mut self, parcelable: &[S]) -> Result<()> {
let len = parcelable.len();
let len_i32: i32 = len.try_into().or(Err(StatusCode::BadValue))?;
self.write::<i32>(&len_i32)?;
let size = len.checked_mul(4).ok_or(StatusCode::BadValue)?;
let padded = pad_size(size);
let end = self
.pos
.checked_add(padded)
.filter(|&e| e <= i32::MAX as usize)
.ok_or(StatusCode::BadValue)?;
self.data.reserve(end.saturating_sub(self.data.len()));
for c in parcelable {
self.write(&c.as_i32())?;
}
Ok(())
}
pub fn write_slice_size<T>(&mut self, slice: Option<&[T]>) -> Result<()> {
if let Some(slice) = slice {
let len: i32 = slice.len().try_into().or(Err(StatusCode::BadValue))?;
self.write(&len)
} else {
self.write(&-1i32)
}
}
pub(crate) fn write_le<T: WireScalar>(&mut self, val: &T) -> Result<()> {
self.write_aligned_data(val.to_wire().as_ref())
}
pub(crate) fn read_le<T: WireScalar>(&mut self) -> Result<T> {
let data = self.read_aligned_data(std::mem::size_of::<T>())?;
T::from_wire(data)
}
pub(crate) fn write_native<T: NativeScalar>(&mut self, val: &T) -> Result<()> {
self.write_aligned_data(val.to_native().as_ref())
}
pub(crate) fn read_native<T: NativeScalar>(&mut self) -> Result<T> {
let data = self.read_aligned_data(std::mem::size_of::<T>())?;
T::from_native(data)
}
pub(crate) fn write_aligned_data(&mut self, data: &[u8]) -> Result<()> {
let unaligned = data.len();
let aligned = pad_size(unaligned);
let pos = self.pos;
let end = pos
.checked_add(aligned)
.filter(|&e| e <= i32::MAX as usize)
.ok_or(StatusCode::BadValue)?;
self.check_object_overlap(end)?;
self.data.write_at(pos, data, aligned - unaligned);
self.set_data_position(end);
Ok(())
}
fn check_object_overlap(&self, end: usize) -> Result<()> {
if self.rpc.is_some() {
return self.check_rpc_object_overlap(end);
}
let pos = self.pos as u64;
if pos >= self.objects_end {
return Ok(());
}
let size = FlatBinderObject::SIZE as u64;
let end = end as u64;
if let Some(off) = self
.objects
.as_slice()
.iter()
.find(|&&off| pos < off + size && off < end)
{
log::error!("Parcel: a write of [{pos}, {end}) overlaps the object at {off}");
return Err(StatusCode::PermissionDenied);
}
Ok(())
}
#[cfg(feature = "rpc")]
fn check_rpc_object_overlap(&self, end: usize) -> Result<()> {
let Some(rpc) = self.rpc.as_ref() else {
return Ok(());
};
let pos = self.pos;
let low = pos.saturating_sub(RPC_BINDER_SIZE - 1);
let start = rpc
.object_positions
.partition_point(|&p| (p as usize) < low);
for &off in &rpc.object_positions[start..] {
let off = off as usize;
if off >= end {
break;
}
let size = rpc_object_size(self.data.as_slice(), off);
if pos < off + size {
log::error!("Parcel: a write of [{pos}, {end}) overlaps the RPC object at {off}");
return Err(StatusCode::PermissionDenied);
}
}
Ok(())
}
#[cfg(not(feature = "rpc"))]
fn check_rpc_object_overlap(&self, _end: usize) -> Result<()> {
Ok(())
}
fn record_object(&mut self, off: usize) {
let end = off as u64 + FlatBinderObject::SIZE as u64;
self.objects_end = self.objects_end.max(end);
self.objects.push(off as _);
}
pub(crate) fn write_object(&mut self, obj: &FlatBinderObject, null_meta: bool) -> Result<()> {
self.write_object_pinned(obj, null_meta, None)
}
pub(crate) fn write_binder_object(
&mut self,
obj: &FlatBinderObject,
binder: &crate::binder::SIBinder,
) -> Result<()> {
self.write_object_pinned(obj, false, Some(binder))
}
fn write_object_pinned(
&mut self,
obj: &FlatBinderObject,
null_meta: bool,
binder: Option<&crate::binder::SIBinder>,
) -> Result<()> {
if obj.header_type() == BINDER_TYPE_FD {
log::error!("Parcel::write_object: an fd goes through `write_kernel_fd`");
return Err(StatusCode::BadType);
}
if self.rpc.is_some() {
self.write_aligned_data(&obj.to_bytes())?;
return Ok(());
}
let data_pos = self.pos;
self.write_aligned_data(&obj.to_bytes())?;
if null_meta || obj.pointer() != 0 {
match binder.filter(|_| obj.header_type() == BINDER_TYPE_HANDLE) {
Some(b) => {
debug_assert_eq!(b.as_proxy().map(|p| p.handle()), Some(obj.handle()));
self.kernel_pinned.push(b.clone());
}
None => self.pin_kernel_handle(obj)?,
}
obj.acquire()?;
self.record_object(data_pos);
}
Ok(())
}
pub(crate) fn write_kernel_fd(&mut self, fd: OwnedFd) -> Result<()> {
if self.rpc.is_some() {
return Err(StatusCode::BadType);
}
let at = self.pos;
let obj = FlatBinderObject::new_with_fd(std::os::fd::AsRawFd::as_raw_fd(&fd), true);
self.write_aligned_data(&obj.to_bytes())?;
self.record_object(at);
let at = at as u64;
let i = self.kernel_fds.partition_point(|&(off, _)| off < at);
self.kernel_fds.insert(i, (at, fd));
Ok(())
}
pub(crate) fn read_kernel_fd_with<R>(
&mut self,
f: impl FnOnce(BorrowedFd<'_>) -> R,
) -> Result<R> {
let at = self.pos as u64;
let obj = self.read_object(true)?;
if obj.header_type() != BINDER_TYPE_FD {
return Err(StatusCode::BadType);
}
Ok(f(self.kernel_fd_at(at)?))
}
pub(crate) fn read_kernel_fd_dup(&mut self) -> Result<OwnedFd> {
Ok(self.read_kernel_fd_with(|fd| rustix::io::fcntl_dupfd_cloexec(fd, 0))??)
}
fn kernel_fd_at(&self, at: u64) -> Result<BorrowedFd<'_>> {
if let Ok(i) = self.kernel_fds.binary_search_by_key(&at, |&(off, _)| off) {
return Ok(std::os::fd::AsFd::as_fd(&self.kernel_fds[i].1));
}
let bytes = match self.data {
ParcelData::Slice(bytes) if self.fd_handles_borrowed => bytes,
_ => {
log::error!("Parcel: the fd object at {at} has no fd this parcel holds");
return Err(StatusCode::BadType);
}
};
let obj = usize::try_from(at)
.map_err(|_| StatusCode::BadType)
.and_then(|off| read_flat_binder(bytes, off))?;
let raw = obj.handle() as RawFd;
if obj.header_type() != BINDER_TYPE_FD || raw < 0 || !self.objects.as_slice().contains(&at)
{
log::error!("Parcel: no fd object at {at} in the objects table");
return Err(StatusCode::BadType);
}
Ok(unsafe { BorrowedFd::borrow_raw(raw) })
}
fn pin_kernel_handle(&mut self, obj: &FlatBinderObject) -> Result<()> {
if obj.header_type() == BINDER_TYPE_HANDLE {
let proxy = crate::process_state::ProcessState::as_self()
.strong_proxy_for_handle(obj.handle())?;
self.kernel_pinned.push(proxy);
}
Ok(())
}
pub(crate) fn write_interface_token(&mut self, interface: &str) -> Result<()> {
self.write(&(thread_state::get_strict_mode_policy() | STRICT_MODE_PENALTY_GATHER))?;
self.update_work_source_request_header_pos();
let work_source: i32 = if thread_state::should_propagate_work_source() {
thread_state::get_calling_work_source_uid() as _
} else {
thread_state::UNSET_WORK_SOURCE
};
self.write(&work_source)?;
if crate::sdk_at_least(30) {
self.write(&binder::INTERFACE_HEADER)?;
}
self.write(&interface)?;
Ok(())
}
pub fn sized_write<F>(&mut self, f: F) -> Result<()>
where
for<'b> F: FnOnce(&mut Parcel) -> Result<()>,
{
let start = self.data_position();
self.write(&0i32)?;
{
f(self)?;
}
let end = self.data_position();
if end < start {
return Err(StatusCode::BadValue);
}
self.set_data_position(start);
self.write::<i32>(&((end - start) as _))?;
self.set_data_position(end);
Ok(())
}
pub(crate) fn append_all_from(&mut self, other: &mut Parcel) -> Result<()> {
self.append_from(other, 0, other.data_size())
}
pub(crate) fn append_from(
&mut self,
other: &mut Parcel,
offset: usize,
size: usize,
) -> Result<()> {
if size == 0 {
return Ok(());
}
if size > i32::MAX as usize {
log::error!("Parcel::append_from: the size is too large: {size}");
return Err(StatusCode::BadValue);
}
let other_len = other.data.len();
if offset > other_len || size > other_len || (offset + size) > other_len {
log::error!("Parcel::append_from: The given offset({offset}) and size({size}) exceed the data range of the parcel.");
return Err(StatusCode::BadValue);
}
if self.rpc.is_none() && other.rpc.is_some() {
log::error!("Parcel::append_from: refusing RPC/data-only bytes into a kernel parcel");
return Err(StatusCode::BadType);
}
#[cfg(feature = "rpc")]
if other.rpc.as_ref().is_some_and(|r| r.ops.is_some())
&& self.rpc.as_ref().is_none_or(|r| r.ops.is_none())
{
log::error!("Parcel::append_from: refusing session bytes into a session-less parcel");
return Err(StatusCode::BadType);
}
#[cfg(feature = "rpc")]
if let Some(ours) = self.rpc_session_id() {
if other.rpc_session_id() != Some(ours) {
log::error!(
"Parcel::append_from: a session parcel takes bytes from its own session only"
);
return Err(StatusCode::BadType);
}
}
#[cfg(feature = "rpc")]
if other
.rpc
.as_ref()
.is_some_and(|r| !r.leaving_addrs.is_empty())
{
log::error!(
"Parcel::append_from: the source still owns binder reservations; append a \
received or binder-free parcel"
);
return Err(StatusCode::BadType);
}
#[cfg(feature = "rpc")]
if self
.rpc
.as_ref()
.is_some_and(|r| r.send_state() != RpcSendState::NotSent)
{
log::error!("Parcel::append_from: the destination is being sent, sent or received");
return Err(StatusCode::BadType);
}
let end = self
.pos
.checked_add(size)
.filter(|&e| e <= i32::MAX as usize)
.ok_or(StatusCode::BadValue)?;
self.check_object_overlap(end)?;
#[cfg(feature = "rpc")]
let staged = match self.rpc.as_ref().and_then(|r| r.ops.clone()) {
Some(ops) => Some(self.stage_rpc_copy(&*ops, other, offset, size)?),
None => None,
};
let start_pos = self.pos;
let mut first_idx: i32 = -1;
let mut last_idx: i32 = -2;
let object_size = FlatBinderObject::SIZE as u64;
{
let objects = other.objects.as_slice();
for (i, &off) in objects.iter().enumerate() {
if off >= offset as _ && (off + object_size) <= (offset + size) as u64 {
if first_idx == -1 {
first_idx = i as i32;
}
last_idx = i as i32;
}
}
}
let num_objects = last_idx - first_idx + 1;
if self.rpc.is_some() && num_objects > 0 {
let src_data = other.data.as_slice();
let src_objects = other.objects.as_slice();
let only_fds = (first_idx..=last_idx).all(|i| {
matches!(
read_flat_binder(src_data, src_objects[i as usize] as usize),
Ok(flat) if flat.header_type() == BINDER_TYPE_FD
)
});
return Err(if only_fds {
StatusCode::FdsNotAllowed
} else {
StatusCode::BadType
});
}
self.data
.write_at(self.pos, &other.data.as_slice()[offset..offset + size], 0);
self.set_data_position(end);
#[cfg(feature = "rpc")]
if let (Some(staged), Some(rpc)) = (staged, self.rpc.as_mut()) {
for rel in staged.positions {
rpc.record_object_position(start_pos + rel);
}
for (rel, fd) in staged.fds {
let at = start_pos + rel;
let idx = rpc.push_out_fd(fd);
self.data.as_mut_slice()[at..at + 4].copy_from_slice(&idx.to_le_bytes());
}
rpc.leaving_addrs.extend(staged.binders.leaving);
rpc.pinned.extend(staged.binders.pinned);
}
let skip_objects = self.rpc.is_some();
if num_objects > 0 && !skip_objects {
self.objects.reserve(num_objects as usize);
let src_objects = other.objects.as_slice();
for i in first_idx..=last_idx {
let src = src_objects[i as usize];
if src < offset as u64 || src + object_size > (offset + size) as u64 {
continue;
}
let off = src as usize - offset + start_pos;
let mut flat = read_flat_binder(other.data.as_slice(), src as usize)?;
self.pin_kernel_handle(&flat)?;
flat.acquire()?;
if flat.header_type() == BINDER_TYPE_FD {
let newfd = rustix::io::fcntl_dupfd_cloexec(other.kernel_fd_at(src)?, 0)?;
flat.set_handle(std::os::fd::AsRawFd::as_raw_fd(&newfd) as _);
flat.set_cookie(1);
write_flat_binder(self.data.as_mut_slice(), off, &flat)?;
let at = off as u64;
let i = self.kernel_fds.partition_point(|&(o, _)| o < at);
self.kernel_fds.insert(i, (at, newfd));
}
self.record_object(off);
}
}
Ok(())
}
#[cfg(feature = "rpc")]
fn stage_rpc_copy(
&self,
ops: &dyn RpcParcelOps,
other: &Parcel,
offset: usize,
size: usize,
) -> Result<StagedRpcCopy> {
if !ops.records_binder_positions()? {
log::error!(
"Parcel::append_from: this session's wire records no binder positions, so a \
copied binder could not take its reference; decode the payload and write it"
);
return Err(StatusCode::BadType);
}
let range = &other.data.as_slice()[offset..offset + size];
let positions: Vec<usize> = other
.rpc_object_positions()
.iter()
.map(|&p| p as usize)
.filter(|&p| offset <= p && p < offset + size)
.map(|p| p - offset)
.collect();
let mut binders: Vec<&[u8]> = Vec::new();
let mut fds = Vec::new();
for &rel in &positions {
match le_i32_at(range, rel) {
Some(RPC_TYPE_BINDER) => binders.push(&range[rel + 4..]),
Some(crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR) => {
if self.rpc_fd_mode() != crate::rpc::FileDescriptorTransportMode::Unix {
return Err(StatusCode::FdsNotAllowed);
}
let fd = le_i32_at(range, rel + 4)
.and_then(|idx| usize::try_from(idx).ok())
.and_then(|idx| other.rpc.as_ref()?.source_fd(idx))
.ok_or(StatusCode::BadValue)?;
let dup = rustix::io::fcntl_dupfd_cloexec(fd, 0)
.map_err(|e| StatusCode::from(std::io::Error::from(e)))?;
fds.push((rel + 4, dup));
}
Some(_) => {
log::error!("Parcel::append_from: an RPC object that is neither binder nor fd");
return Err(StatusCode::InvalidOperation);
}
None => return Err(StatusCode::BadValue),
}
}
let binders = if binders.is_empty() {
CopiedBinders::default()
} else {
ops.acquire_copied(&binders)?
};
Ok(StagedRpcCopy {
positions,
fds,
binders,
})
}
pub(crate) fn sub_parcel(&mut self, offset: usize, size: usize) -> Result<Parcel> {
if self.rpc.is_none() {
let mut sub = Parcel::new();
sub.append_from(self, offset, size)?;
return Ok(sub);
}
let end = offset
.checked_add(size)
.filter(|&e| e <= self.data.len() && size <= i32::MAX as usize)
.ok_or_else(|| {
log::error!("Parcel::sub_parcel: {offset} + {size} exceeds the parcel");
StatusCode::BadValue
})?;
let sub = Parcel::data_only_from_vec(self.data.as_slice()[offset..end].to_vec());
#[cfg(feature = "rpc")]
let sub = {
let mut sub = sub;
self.carry_rpc_objects(&mut sub, offset, end);
sub
};
Ok(sub)
}
#[cfg(feature = "rpc")]
fn carry_rpc_objects(&mut self, sub: &mut Parcel, offset: usize, end: usize) {
let (Some(src), Some(dst)) = (self.rpc.as_mut(), sub.rpc.as_mut()) else {
return;
};
dst.ops = src.ops.clone();
dst.fd_mode = src.fd_mode;
dst.record_fd_positions = src.record_fd_positions;
if src.send_state() == RpcSendState::Received {
dst.send_state.store(
RpcSendState::Received as u8,
std::sync::atomic::Ordering::Release,
);
dst.received_len = end - offset;
}
for (pos, addr, binder) in &src.entered {
let pos = *pos as usize;
if offset <= pos && pos < end {
dst.entered
.push(((pos - offset) as u32, *addr, binder.clone()));
}
}
let data = sub.data.as_mut_slice();
use crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR;
for i in 0..src.object_positions.len() {
let pos = src.object_positions[i] as usize;
if pos < offset || pos >= end {
continue;
}
let rel = pos - offset;
dst.object_positions.push(rel as u32);
let is_fd = src.record_fd_positions
&& le_i32_at(data, rel) == Some(TYPE_NATIVE_FILE_DESCRIPTOR);
let Some(idx) = le_i32_at(data, rel + 4).filter(|_| is_fd) else {
continue;
};
let fd = usize::try_from(idx).ok().and_then(|i| src.take_in_fd(i));
let new_idx = dst.fds_in.len() as i32;
dst.fds_in.push(fd);
data[rel + 4..rel + 8].copy_from_slice(&new_idx.to_le_bytes());
}
}
fn release_objects(&self) {
if self.rpc.is_some() {
return;
}
if self.objects.len() == 0 {
return;
}
for pos in self.objects.as_slice() {
let Ok(obj) = read_flat_binder(self.data.as_slice(), *pos as usize) else {
log::error!("Parcel: unable to read object at position {pos}");
continue;
};
obj.release()
.map_err(|e| log::error!("Parcel: unable to release object: {e:?}"))
.ok();
}
}
}
impl Drop for Parcel {
fn drop(&mut self) {
drop(std::mem::take(&mut self.kernel_fds));
match self.free_buffer {
Some(free_buffer) => {
if let Err(e) = free_buffer(
Some(self),
self.data.as_ptr() as _,
self.data.len(),
self.objects.as_ptr() as _,
self.objects.len(),
) {
log::error!("Failed to free parcel buffer ({e}); leaking the kernel buffer");
}
}
None => {
#[cfg(feature = "rpc")]
if let Some(rpc) = self.rpc.as_mut() {
rpc.settle_unsent();
}
self.release_objects();
}
}
}
}
impl std::fmt::Debug for Parcel {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Parcel: pos {}, len {}", self.pos, self.data.len())?;
if self.objects.len() > 0 {
let bytes: Vec<u8> = self
.objects
.as_slice()
.iter()
.flat_map(|off| off.to_ne_bytes())
.collect();
writeln!(
f,
"Object count {}\n{}",
self.objects.len(),
pretty_hex(&bytes)
)?;
}
write!(f, "{}", pretty_hex(&self.data.as_slice()))
}
}
impl<const N: usize> TryFrom<&mut Parcel> for [u8; N] {
type Error = StatusCode;
fn try_from(parcel: &mut Parcel) -> Result<Self> {
let data = parcel.read_aligned_data(N)?;
Ok(<[u8; N] as TryFrom<&[u8]>>::try_from(data)?)
}
}
pub fn to_bytes<T: Serialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
let mut parcel = Parcel::new_data_only();
parcel.write(value)?;
let bytes = parcel.into_bytes()?;
if bytes.capacity() > 2 * bytes.len() {
return Ok(bytes.as_slice().to_vec());
}
Ok(bytes)
}
pub fn from_bytes<T: Deserialize>(bytes: &[u8]) -> Result<T> {
if bytes.len() >= i32::MAX as usize {
return Err(StatusCode::BadValue);
}
let mut parcel = Parcel::from_slice(bytes);
let value = parcel.read::<T>()?;
if parcel.data_avail() != 0 {
return Err(StatusCode::BadValue);
}
Ok(value)
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
#[should_panic(expected = "can't support as_mut_slice()")]
fn parcel_data_slice_refuses_as_mut_slice() {
let leaked: &'static mut [u8] = Box::leak(vec![0u8, 1, 2, 3].into_boxed_slice());
let ptr = leaked.as_mut_ptr();
let len = leaked.len();
let mut pd: super::ParcelData<u8> =
unsafe { super::ParcelData::from_raw_parts_mut(ptr, len) };
assert_eq!(pd.as_slice(), &[0u8, 1, 2, 3][..]);
assert_eq!(pd.as_ptr(), ptr as *const u8);
let _ = pd.as_mut_slice();
}
#[test]
fn write_array_zeroes_trailing_pad() {
let mut parcel = Parcel::new();
parcel.write(&(-1i32)).unwrap();
parcel.write(&(-1i32)).unwrap();
parcel.set_data_position(0);
let payload: &[u8] = &[0xAB];
SerializeArray::serialize_array(payload, &mut parcel).unwrap();
let bytes = parcel.data.as_slice();
assert_eq!(&bytes[4..8], &[0xAB, 0x00, 0x00, 0x00]);
}
#[test]
fn write_at_covers_gap_overwrite_append_and_pad() {
let mut pd = super::ParcelData::from_vec(vec![0xFFu8; 6]);
pd.write_at(4, &[1, 2, 3], 1);
assert_eq!(pd.as_slice(), &[0xFF, 0xFF, 0xFF, 0xFF, 1, 2, 3, 0]);
pd.write_at(0, &[7], 2);
assert_eq!(pd.as_slice(), &[7, 0, 0, 0xFF, 1, 2, 3, 0]);
pd.write_at(10, &[9], 0);
assert_eq!(pd.as_slice(), &[7, 0, 0, 0xFF, 1, 2, 3, 0, 0, 0, 9]);
}
#[test]
fn write_wire_at_encodes_le_when_appending_and_overwriting() {
let mut pd = super::ParcelData::from_vec(vec![0xFFu8; 2]);
pd.write_wire_at(2, &[0x0102_0304u32], 2);
assert_eq!(pd.as_slice(), &[0xFF, 0xFF, 4, 3, 2, 1, 0, 0]);
pd.write_wire_at(5, &[0x0A0Bu16, 0x0C0D], 1);
assert_eq!(
pd.as_slice(),
&[0xFF, 0xFF, 4, 3, 2, 0x0B, 0x0A, 0x0D, 0x0C, 0]
);
pd.write_wire_at(0, &[-2i8], 1);
assert_eq!(
pd.as_slice(),
&[0xFE, 0, 4, 3, 2, 0x0B, 0x0A, 0x0D, 0x0C, 0]
);
}
#[test]
#[should_panic(expected = "can't support write_at()")]
fn parcel_data_slice_refuses_write_at() {
let leaked: &'static mut [u8] = Box::leak(vec![0u8; 4].into_boxed_slice());
let mut pd: super::ParcelData<u8> =
unsafe { super::ParcelData::from_raw_parts_mut(leaked.as_mut_ptr(), 4) };
pd.write_at(0, &[1], 0);
}
#[test]
fn checked_array_layout_normal_case() {
let (size, padded) = super::checked_array_layout(10, 4).unwrap();
assert_eq!((size, padded), (40, 40));
let (size, padded) = super::checked_array_layout(3, 5).unwrap();
assert_eq!((size, padded), (15, 16));
}
#[test]
fn checked_array_layout_rejects_size_mul_overflow() {
assert_eq!(
super::checked_array_layout(i32::MAX, usize::MAX),
Err(StatusCode::BadValue)
);
}
#[test]
fn checked_array_layout_rejects_pad_overflow() {
assert_eq!(
super::checked_array_layout(1, usize::MAX),
Err(StatusCode::BadValue)
);
assert_eq!(
super::checked_array_layout(7, usize::MAX / 3),
Err(StatusCode::BadValue)
);
}
#[test]
fn checked_array_layout_passes_i32_max_on_64_bit_and_rejects_it_on_32() {
#[cfg(target_pointer_width = "64")]
{
let (size, padded) = super::checked_array_layout(i32::MAX, 4).unwrap();
assert_eq!(size, (i32::MAX as usize) * 4);
assert_eq!(padded, super::pad_size(size));
}
#[cfg(not(target_pointer_width = "64"))]
assert_eq!(
super::checked_array_layout(i32::MAX, 4),
Err(StatusCode::BadValue)
);
}
#[test]
fn read_array_rejects_hostile_len_gracefully() {
let mut parcel = Parcel::new();
parcel.write::<i32>(&1_000_000_000).unwrap();
parcel.set_data_position(0);
let r = parcel.read_array::<i32>();
assert!(r.is_err(), "expected Err, got {r:?}");
}
#[test]
fn read_array_char_rejects_hostile_len_gracefully() {
let mut parcel = Parcel::new();
parcel.write::<i32>(&1_000_000_000).unwrap();
parcel.set_data_position(0);
let r = parcel.read_array_char::<u16>();
assert!(r.is_err(), "expected Err, got {r:?}");
}
#[test]
fn test_primitives() -> Result<()> {
let v_i32: i32 = 1234;
let v_f32: f32 = 5678.0;
let v_u32: u32 = 9012;
let v_i64: i64 = 3456;
let v_u64: u64 = 7890;
let v_f64: f64 = 9876.0;
let v_str = "Hello World".to_owned();
let mut parcel = Parcel::new();
{
parcel.write::<i32>(&v_i32)?;
parcel.write::<u32>(&v_u32)?;
parcel.write::<f32>(&v_f32)?;
parcel.write::<i64>(&v_i64)?;
parcel.write::<u64>(&v_u64)?;
parcel.write::<f64>(&v_f64)?;
parcel.write(&v_str)?;
}
parcel.set_data_position(0);
{
assert_eq!(parcel.read::<i32>()?, v_i32);
assert_eq!(parcel.read::<u32>()?, v_u32);
assert_eq!(parcel.read::<f32>()?, v_f32);
assert_eq!(parcel.read::<i64>()?, v_i64);
assert_eq!(parcel.read::<u64>()?, v_u64);
assert_eq!(parcel.read::<f64>()?, v_f64);
assert_eq!(parcel.read::<String>()?, v_str);
}
Ok(())
}
#[test]
fn test_array_byte() {
let array = vec![255u8, 0u8, 127u8];
let mut reverse = array.clone();
reverse.reverse();
let mut parcel = Parcel::new();
parcel.write_array(&array).unwrap();
parcel.write_array(&reverse).unwrap();
parcel.set_data_position(0);
let res = parcel.read_array::<u8>().unwrap();
assert_eq!(array, res.unwrap());
let res = parcel.read_array::<u8>().unwrap();
assert_eq!(reverse, res.unwrap());
}
#[test]
fn parcel_array_empty_is_not_null() {
let mut parcel = Parcel::new();
parcel.write_array::<u8>(&[]).unwrap();
parcel.write_array_char::<u16>(&[]).unwrap();
parcel.set_data_position(0);
assert_eq!(parcel.read_array::<u8>(), Ok(Some(Vec::new())));
assert_eq!(parcel.read_array_char::<u16>(), Ok(Some(Vec::new())));
}
#[test]
fn vec_deserialize_rejects_null_but_accepts_empty() {
let mut parcel = Parcel::new();
parcel.write(&-1i32).unwrap();
parcel.write(&0i32).unwrap();
parcel.write(&0i32).unwrap();
parcel.write(&-2i32).unwrap();
parcel.set_data_position(0);
assert_eq!(parcel.read::<Vec<u8>>(), Err(StatusCode::UnexpectedNull));
assert_eq!(parcel.read::<Vec<u8>>(), Ok(Vec::new()));
assert_eq!(parcel.read::<Option<Vec<u8>>>(), Ok(Some(Vec::<u8>::new())));
assert_eq!(parcel.read::<Vec<u8>>(), Err(StatusCode::UnexpectedNull));
}
#[test]
fn test_array_double() {
let array = vec![1.0f64 / 3.0f64, 1.0f64 / 7.0f64, 42.0f64];
let mut reverse = array.clone();
reverse.reverse();
let mut parcel = Parcel::new();
parcel.write_array(&array).unwrap();
parcel.write_array(&reverse).unwrap();
println!("{parcel:?}");
parcel.set_data_position(0);
let res = parcel.read_array::<f64>().unwrap();
assert_eq!(array, res.unwrap());
let res = parcel.read_array::<f64>().unwrap();
assert_eq!(reverse, res.unwrap());
}
#[test]
fn test_array_char() {
let array = vec![255u16, 0u16, 127u16];
let mut reverse = array.clone();
reverse.reverse();
let mut parcel = Parcel::new();
parcel.write_array_char(&array).unwrap();
parcel.write_array_char(&reverse).unwrap();
parcel.set_data_position(0);
let res = parcel.read_array_char::<u16>().unwrap();
assert_eq!(array, res.unwrap());
let res = parcel.read_array_char::<u16>().unwrap();
assert_eq!(reverse, res.unwrap());
}
#[test]
fn test_typed_scalar_helpers() -> Result<()> {
let mut p = Parcel::new();
p.write_i32(-7)?;
p.write_u32(7)?;
p.write_i64(-8)?;
p.write_u64(8)?;
p.write_f32(1.5)?;
p.write_f64(2.5)?;
p.write_bool(true)?;
p.write_i8(-9)?;
p.write_u8(9)?;
p.set_data_position(0);
assert_eq!(p.read_i32()?, -7);
assert_eq!(p.read_u32()?, 7);
assert_eq!(p.read_i64()?, -8);
assert_eq!(p.read_u64()?, 8);
assert_eq!(p.read_f32()?, 1.5);
assert_eq!(p.read_f64()?, 2.5);
assert!(p.read_bool()?);
assert_eq!(p.read_i8()?, -9);
assert_eq!(p.read_u8()?, 9);
let mut q = Parcel::new();
q.write_i32(0x1234_5678)?;
q.set_data_position(0);
assert_eq!(q.read::<i32>()?, 0x1234_5678);
Ok(())
}
#[test]
fn from_ipc_parts_preserves_data_pointer_when_length_is_zero() {
use crate::sys::binder::binder_uintptr_t;
use std::sync::atomic::{AtomicUsize, Ordering};
static FREED_DATA_PTR: AtomicUsize = AtomicUsize::new(0);
fn capture(
_: Option<&Parcel>,
data: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
FREED_DATA_PTR.store(data as usize, Ordering::SeqCst);
Ok(())
}
let mut backing = vec![0u8; 4096];
let original = backing.as_mut_ptr();
{
let parcel =
unsafe { Parcel::from_ipc_parts(original, 0, std::ptr::null_mut(), 0, capture) };
assert_eq!(
parcel.as_ptr() as usize,
original as usize,
"as_ptr() must return the original buffer pointer for empty IPC parcels",
);
}
assert_eq!(
FREED_DATA_PTR.load(Ordering::SeqCst),
original as usize,
"BC_FREE_BUFFER must be issued with the original kernel-supplied pointer",
);
}
#[test]
fn from_ipc_parts_with_null_data_uses_empty_slice() {
fn noop(
_: Option<&Parcel>,
_: crate::sys::binder::binder_uintptr_t,
_: usize,
_: crate::sys::binder::binder_uintptr_t,
_: usize,
) -> Result<()> {
Ok(())
}
let parcel = unsafe {
Parcel::from_ipc_parts(std::ptr::null_mut(), 0, std::ptr::null_mut(), 0, noop)
};
assert_eq!(parcel.data_size(), 0);
drop(parcel);
}
#[test]
fn from_ipc_parts_copies_a_misaligned_offsets_array() {
fn noop(
_: Option<&Parcel>,
_: crate::sys::binder::binder_uintptr_t,
_: usize,
_: crate::sys::binder::binder_uintptr_t,
_: usize,
) -> Result<()> {
Ok(())
}
let mut backing = vec![0u64; 3];
let base = backing.as_mut_ptr() as *mut u8;
let skew = std::mem::align_of::<crate::sys::binder::binder_size_t>() / 2;
let objects = unsafe { base.add(skew) } as *mut crate::sys::binder::binder_size_t;
unsafe {
objects.write_unaligned(8);
objects.add(1).write_unaligned(24);
}
let mut data = [0u8; 32];
let parcel = unsafe { Parcel::from_ipc_parts(data.as_mut_ptr(), 32, objects, 2, noop) };
assert!(matches!(parcel.objects, super::ParcelData::Vec(_)));
assert_eq!(parcel.objects.as_slice(), &[8, 24]);
drop(parcel);
}
#[test]
fn set_data_size_only_shrinks() {
let mut parcel = Parcel::new();
parcel.write(&0u64).expect("write u64");
assert_eq!(parcel.data_size(), 8);
assert!(parcel.capacity() > 8);
assert_eq!(parcel.set_data_size(9), Err(StatusCode::BadValue));
assert_eq!(parcel.data_size(), 8);
assert!(parcel.set_data_size(4).is_ok());
assert_eq!(parcel.data_size(), 4);
assert_eq!(parcel.data_position(), 4);
assert!(parcel.set_data_size(0).is_ok());
}
#[test]
fn driver_read_buffer_lends_the_zeroed_capacity_and_keeps_what_was_filled() {
let mut parcel = Parcel::new();
parcel.write(&-1i32).expect("write i32");
let cap = parcel.capacity();
let buf = parcel.driver_read_buffer();
assert_eq!(buf.len(), cap);
assert!(
buf[4..].iter().all(|&b| b == 0),
"the added bytes are zeroed"
);
buf[..8].fill(0xAB);
assert!(parcel.driver_read_done(8).is_ok());
assert_eq!(parcel.data.as_slice(), &[0xAB; 8]);
assert_eq!(parcel.data_position(), 0);
assert_eq!(
parcel.capacity(),
cap,
"lending the buffer does not reallocate it"
);
let len = parcel.driver_read_buffer().len();
assert_eq!(parcel.driver_read_done(len + 1), Err(StatusCode::BadValue));
assert_eq!(parcel.data_size(), 0);
assert!(parcel.is_empty());
}
#[test]
fn ipc_data_size_never_exceeds_backing_buffer() {
let mut parcel = Parcel::new();
parcel.write(&1u32).expect("write");
parcel.set_data_position(1024);
assert_eq!(parcel.data_size(), 1024, "AOSP dataSize() = max(len, pos)");
assert_eq!(
parcel.ipc_data_size(),
4,
"only the written bytes go to the kernel"
);
parcel.write(&2u32).expect("write past gap");
assert_eq!(parcel.ipc_data_size(), 1028);
assert_eq!(parcel.ipc_data_size(), parcel.data_size());
}
#[test]
fn set_data_position_rejects_past_i32_max() {
let mut parcel = Parcel::new();
parcel.write(&7u32).expect("write");
parcel.set_data_position(0);
parcel.set_data_position(i32::MAX as usize + 1);
assert_eq!(parcel.data_position(), 0, "out-of-range seek is ignored");
parcel.set_data_position(i32::MAX as usize);
assert_eq!(parcel.data_position(), i32::MAX as usize);
}
#[test]
fn data_avail_saturates_when_pos_past_end() {
let mut parcel = Parcel::new();
parcel.write(&0u64).expect("write u64");
assert_eq!(parcel.data_avail(), 0, "cursor at end → nothing available");
parcel.set_data_position(0);
assert_eq!(parcel.data_avail(), 8, "8 bytes available from start");
parcel.set_data_position(9999);
assert_eq!(
parcel.data_avail(),
0,
"saturating_sub, not underflow panic"
);
}
#[test]
fn sized_read_field_truncation_via_has_more_data() {
let mut wv1 = Parcel::new();
wv1.sized_write(|p| {
p.write(&11i32)?;
p.write(&22i32)
})
.expect("v1 write");
wv1.write(&0x7777_7777i32).expect("sentinel");
wv1.set_data_position(0);
let (mut a, mut b, mut c) = (0i32, 0i32, -1i32);
wv1.sized_read(|p| {
if !p.has_more_data() {
return Ok(());
}
a = p.read()?;
if !p.has_more_data() {
return Ok(());
}
b = p.read()?;
if !p.has_more_data() {
return Ok(());
}
c = p.read()?;
Ok(())
})
.expect("v3 read of v1 data");
assert_eq!((a, b), (11, 22), "present fields read");
assert_eq!(c, -1, "absent trailing field left at default, no over-read");
assert_eq!(wv1.read::<i32>().expect("sentinel"), 0x7777_7777);
let mut wv3 = Parcel::new();
wv3.sized_write(|p| {
p.write(&1i32)?;
p.write(&2i32)?;
p.write(&3i32)
})
.expect("v3 write");
wv3.write(&0x5555_5555i32).expect("sentinel");
wv3.set_data_position(0);
let (mut x, mut y) = (0i32, 0i32);
wv3.sized_read(|p| {
if !p.has_more_data() {
return Ok(());
}
x = p.read()?;
if !p.has_more_data() {
return Ok(());
}
y = p.read()?;
Ok(())
})
.expect("v1 read of v3 data");
assert_eq!((x, y), (1, 2), "first two fields read");
assert_eq!(
wv3.read::<i32>().expect("sentinel"),
0x5555_5555,
"extra field skipped to block end, sentinel intact"
);
}
#[test]
fn sized_read_depth_is_bounded() {
fn write_nested(p: &mut Parcel, depth: usize) -> Result<()> {
p.sized_write(|s| {
s.write(&(depth as i32))?;
if depth > 1 {
write_nested(s, depth - 1)?;
}
Ok(())
})
}
fn read_nested(p: &mut Parcel) -> Result<()> {
p.sized_read(|s| {
let _marker: i32 = s.read()?;
if s.has_more_data() {
read_nested(s)?;
}
Ok(())
})
}
let mut over = Parcel::new();
write_nested(&mut over, super::MAX_NESTED_READ_DEPTH + 50).expect("write over-deep");
over.set_data_position(0);
assert_eq!(read_nested(&mut over).unwrap_err(), StatusCode::BadValue);
let mut ok = Parcel::new();
write_nested(&mut ok, 8).expect("write shallow");
for _ in 0..2 {
ok.set_data_position(0);
read_nested(&mut ok).expect("shallow read");
}
}
#[cfg(feature = "rpc")]
#[test]
fn rpc_object_position_table_is_aosp_faithful_and_sorted() {
use crate::rpc::wire::{WireCodec, WireMessage, WireTransaction};
use crate::rpc::wire_android13::Android13PlusCodec;
let mut kparcel = Parcel::new();
kparcel.write(&7i32).unwrap();
kparcel.rpc_record_object_position(0); assert!(
kparcel.rpc_object_positions().is_empty(),
"kernel parcel must never grow an object table"
);
let mut p = Parcel::new();
p.set_for_rpc(true).unwrap();
p.set_rpc_record_fd_positions(true);
p.write(&0xDEAD_BEEFu32).unwrap(); p.write(&"iface".to_owned()).unwrap();
let mut expect: Vec<u32> = Vec::new();
let pos = p.data_position();
expect.push(pos as u32);
p.write(&1i32).unwrap(); p.write_aligned_data(&[0u8; 8]).unwrap(); p.write(&0x0Ci32).unwrap(); p.rpc_record_object_position(pos);
p.write(&123i64).unwrap();
let pos = p.data_position();
expect.push(pos as u32);
p.write(&crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR)
.unwrap();
p.write(&0i32).unwrap(); p.rpc_record_object_position(pos);
let pos = p.data_position();
expect.push(pos as u32);
p.write(&1i32).unwrap();
p.write_aligned_data(&[0u8; 8]).unwrap();
p.write(&0x0Ci32).unwrap();
p.rpc_record_object_position(pos);
assert_eq!(
p.rpc_object_positions(),
&expect[..],
"AOSP dataPos offsets"
);
assert!(
p.rpc_object_positions().windows(2).all(|w| w[0] < w[1]),
"table strictly ascending"
);
let mut q = Parcel::new();
q.set_for_rpc(true).unwrap();
for pos in [40u32, 8, 24, 8, 0] {
q.rpc_record_object_position(pos as usize);
}
assert_eq!(
q.rpc_object_positions(),
&[0, 8, 8, 24, 40],
"upper_bound insert keeps the table sorted (dups allowed)"
);
for &good in &[0u32, 8, 24, 40] {
assert!(q.rpc_object_position_present(good as usize), "pos {good}");
}
for bad in [4usize, 12, 41, 9999] {
assert!(
!q.rpc_object_position_present(bad),
"unrecorded pos {bad} must fail the v2 binary_search"
);
}
let c = Android13PlusCodec::android16();
let data = p.rpc_data_bytes().to_vec();
let positions = p.rpc_object_positions().to_vec();
let txn = WireTransaction {
address: crate::rpc::address::RpcAddress::zero(),
code: 1,
flags: 0,
async_number: 0,
data: data.clone(),
object_positions: positions.clone(),
};
let enc = c.encode_transact(&txn).unwrap();
let body = u32::from_le_bytes([enc[4], enc[5], enc[6], enc[7]]) as usize;
assert_eq!(
body,
40 + data.len() + 4 * positions.len(),
"bodySize = fixed(40) + parcelDataSize + 4·N"
);
match c.decode_message(&enc).unwrap() {
WireMessage::Transact(d) => {
assert_eq!(d.data, data, "parcel data intact");
assert_eq!(d.object_positions, positions, "object table intact");
}
o => panic!("expected Transact, got {o:?}"),
}
}
#[test]
fn a_write_over_a_recorded_object_is_refused() {
use std::os::fd::AsRawFd;
let victim = std::fs::File::open("/dev/null").unwrap();
let pfd = ParcelFileDescriptor::new(std::fs::File::open("/dev/null").unwrap());
let mut p = Parcel::new();
p.write(&pfd).unwrap();
assert_eq!(p.objects.as_slice(), &[8]);
p.set_data_position(16);
assert_eq!(
p.write(&victim.as_raw_fd()),
Err(StatusCode::PermissionDenied)
);
p.set_data_position(4);
assert_eq!(p.write_array(&[0i32]), Err(StatusCode::PermissionDenied));
let mut src = Parcel::new();
src.write(&0i64).unwrap();
p.set_data_position(24);
assert_eq!(
p.append_all_from(&mut src),
Err(StatusCode::PermissionDenied)
);
p.set_data_position(0);
assert_eq!(p.write(&pfd), Err(StatusCode::PermissionDenied));
assert_eq!(
p.objects.as_slice(),
&[8],
"the offset is not recorded twice"
);
p.set_data_position(32);
p.write(&7i32).unwrap();
drop(p);
assert!(
still_open(victim),
"dropping the parcel closed an fd it never owned"
);
}
#[cfg(feature = "rpc")]
#[test]
fn a_write_over_a_recorded_rpc_object_is_refused() {
let mut p = Parcel::new_data_only();
p.set_rpc_fd_mode(crate::rpc::FileDescriptorTransportMode::Unix);
p.set_rpc_record_fd_positions(true);
let pfd = ParcelFileDescriptor::new(std::fs::File::open("/dev/null").unwrap());
p.write(&pfd).unwrap();
let binder_at = p.data_position();
p.write(&super::RPC_TYPE_BINDER).unwrap();
p.write_aligned_data(&[0xa5u8; 8]).unwrap();
p.rpc_record_object_position(binder_at);
p.write(&0x0Ci32).unwrap();
assert_eq!(p.rpc_object_positions(), &[8, binder_at as u32]);
let before = p.rpc_data_bytes().to_vec();
p.set_data_position(12);
assert_eq!(p.write(&1i32), Err(StatusCode::PermissionDenied));
p.set_data_position(binder_at + 8);
assert_eq!(p.write(&0i32), Err(StatusCode::PermissionDenied));
p.set_data_position(4);
assert_eq!(p.write_array(&[0i32]), Err(StatusCode::PermissionDenied));
let mut src = Parcel::new_data_only();
src.write(&0i64).unwrap();
p.set_data_position(binder_at + 4);
assert_eq!(
p.append_all_from(&mut src),
Err(StatusCode::PermissionDenied)
);
assert_eq!(p.rpc_data_bytes()[8..], before[8..]);
p.set_data_position(binder_at + 12);
p.write(&0x0Ci32).unwrap();
}
#[test]
fn sub_parcel_skips_an_out_of_range_object_of_an_unsorted_table() {
let pfd = ParcelFileDescriptor::new(std::fs::File::open("/dev/null").unwrap());
let mut p = Parcel::new();
for at in [100, 0, 200] {
p.set_data_position(at);
p.write(&pfd).unwrap();
}
assert_eq!(p.objects.as_slice(), &[108, 8, 208]);
let sub = p.sub_parcel(68, 164).unwrap();
assert_eq!(sub.objects.as_slice(), &[40, 140]);
}
use super::{binder_size_t, binder_uintptr_t, FlatBinderObject};
use std::os::fd::{AsRawFd, OwnedFd};
fn pipe_probe() -> (OwnedFd, OwnedFd) {
let (r, w) = ParcelFileDescriptor::pipe().unwrap();
rustix::fs::fcntl_setfl(&r, rustix::fs::OFlags::NONBLOCK).unwrap();
(r.into(), w.into())
}
fn writers_gone(read_end: &OwnedFd) -> bool {
match rustix::io::read(read_end, &mut [0u8; 1]) {
Ok(0) => true,
Err(rustix::io::Errno::AGAIN) => false,
other => panic!("unexpected pipe read: {other:?}"),
}
}
fn still_open(file: std::fs::File) -> bool {
let open = rustix::io::fcntl_getfd(&file).is_ok();
if !open {
std::mem::forget(file);
}
open
}
fn fd_object_buffer(fd: i32) -> Vec<u8> {
let mut data = vec![0u8; FlatBinderObject::SIZE];
let obj = FlatBinderObject::new_with_fd(fd, true);
super::write_flat_binder(&mut data, 0, &obj).unwrap();
data
}
#[test]
fn a_written_fd_is_closed_by_drop_and_only_then() {
let (r, w) = pipe_probe();
let mut p = Parcel::new();
p.write(&ParcelFileDescriptor::new(w)).unwrap();
assert!(!writers_gone(&r), "the parcel holds its dup until it drops");
drop(p);
assert!(writers_gone(&r), "drop closed the dup");
}
#[test]
fn overwritten_fd_bytes_do_not_choose_what_drop_closes() {
let victim = std::fs::File::open("/dev/null").unwrap();
let (r, w) = pipe_probe();
let mut p = Parcel::new();
p.write(&ParcelFileDescriptor::new(w)).unwrap();
assert_eq!(p.objects.as_slice(), &[8]);
let mut obj = super::read_flat_binder(p.data.as_slice(), 8).unwrap();
obj.set_handle(victim.as_raw_fd() as u32);
super::write_flat_binder(p.data.as_mut_slice(), 8, &obj).unwrap();
drop(p);
assert!(writers_gone(&r), "drop closed the fd the parcel owns");
assert!(still_open(victim), "drop closed the fd its bytes named");
}
#[test]
fn a_read_fd_is_a_dup_that_outlives_the_parcel() {
let (r, w) = pipe_probe();
let mut p = Parcel::new();
p.write(&ParcelFileDescriptor::new(w)).unwrap();
p.set_data_position(0);
let read: ParcelFileDescriptor = p.read().unwrap();
let table_fd = p.kernel_fds[0].1.as_raw_fd();
assert_ne!(read.as_raw_fd(), table_fd, "a dup, not the parcel's fd");
drop(p);
assert!(!writers_gone(&r), "the read dup keeps the pipe open");
drop(read);
assert!(writers_gone(&r));
}
#[test]
fn an_appended_fd_outlives_its_source() {
let (r, w) = pipe_probe();
let mut src = Parcel::new();
src.write(&ParcelFileDescriptor::new(w)).unwrap();
let mut dst = Parcel::new();
dst.append_all_from(&mut src).unwrap();
drop(src);
assert!(!writers_gone(&r), "the destination holds its own dup");
dst.set_data_position(0);
drop(dst.read::<ParcelFileDescriptor>().unwrap());
assert!(!writers_gone(&r));
drop(dst);
assert!(writers_gone(&r), "the destination closed its dup");
}
#[test]
fn write_object_refuses_an_fd_object() {
let file = std::fs::File::open("/dev/null").unwrap();
let mut p = Parcel::new();
let obj = FlatBinderObject::new_with_fd(file.as_raw_fd(), true);
assert_eq!(p.write_object(&obj, true), Err(StatusCode::BadType));
assert_eq!((p.data_size(), p.objects.len()), (0, 0));
drop(p);
assert!(still_open(file));
}
#[test]
fn a_refused_fd_write_closes_its_dup_once() {
let (r, w) = pipe_probe();
let (r2, w2) = pipe_probe();
let mut p = Parcel::new();
p.write(&ParcelFileDescriptor::new(w)).unwrap();
p.set_data_position(8);
assert_eq!(p.write_kernel_fd(w2), Err(StatusCode::PermissionDenied));
assert!(writers_gone(&r2), "the refused fd was closed");
assert_eq!(p.kernel_fds.len(), 1);
drop(p);
assert!(writers_gone(&r));
}
thread_local! {
static FREE_PROBE: std::cell::RefCell<Option<(OwnedFd, Option<bool>)>> =
const { std::cell::RefCell::new(None) };
}
fn probe_free(
_: Option<&Parcel>,
_: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
FREE_PROBE.with_borrow_mut(|probe| {
let (r, seen) = probe.as_mut().expect("probe installed");
*seen = Some(writers_gone(r));
});
Ok(())
}
#[test]
fn a_driver_buffer_adopts_each_fd_once_and_closes_it_before_the_free() {
let (r, w) = pipe_probe();
let probe = rustix::io::fcntl_dupfd_cloexec(&r, 0).unwrap();
FREE_PROBE.with_borrow_mut(|p| *p = Some((probe, None)));
let mut data = fd_object_buffer(std::os::fd::IntoRawFd::into_raw_fd(w));
let mut objects: Vec<binder_size_t> = vec![0, 0];
let mut p = unsafe {
Parcel::from_driver_buffer(
data.as_mut_ptr(),
data.len(),
objects.as_mut_ptr(),
2,
probe_free,
)
};
assert_eq!(p.kernel_fds.len(), 1);
drop(p.read_kernel_fd_dup().unwrap());
assert!(!writers_gone(&r), "adopted, not closed by a read");
drop(p);
assert!(writers_gone(&r), "drop closed the adopted fd");
let seen = FREE_PROBE.with_borrow_mut(|p| p.take().unwrap().1);
assert_eq!(seen, Some(true), "the fd was closed before free_buffer ran");
}
#[test]
fn public_from_ipc_parts_reads_its_fds_and_adopts_none() {
fn noop(
_: Option<&Parcel>,
_: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
Ok(())
}
let (r, w) = pipe_probe();
let w = std::mem::ManuallyDrop::new(w);
let mut data = fd_object_buffer(w.as_raw_fd());
let mut objects: Vec<binder_size_t> = vec![0];
let mut p = unsafe {
Parcel::from_ipc_parts(data.as_mut_ptr(), data.len(), objects.as_mut_ptr(), 1, noop)
};
let read = p
.read_kernel_fd_dup()
.expect("the caller's fd reads as a dup");
assert!(p.kernel_fds.is_empty());
drop(p);
drop(read);
assert!(!writers_gone(&r), "the parcel closed the caller's fd");
drop(std::mem::ManuallyDrop::into_inner(w));
assert!(writers_gone(&r));
}
#[test]
fn append_from_dups_the_fd_the_source_bytes_name() {
fn noop(
_: Option<&Parcel>,
_: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
Ok(())
}
let ino = |fd: &OwnedFd| rustix::fs::fstat(fd).unwrap().st_ino;
let (_ra, a) = pipe_probe();
let (_rb, b) = pipe_probe();
for second in [12usize, 16] {
let mut data = fd_object_buffer(a.as_raw_fd());
data.resize(40, 0);
data[second..second + 4].copy_from_slice(&super::BINDER_TYPE_FD.to_ne_bytes());
data[second + 4..second + 8].fill(0);
data[second + 8..second + 12].copy_from_slice(&b.as_raw_fd().to_ne_bytes());
let mut objects: Vec<binder_size_t> = vec![0, second as binder_size_t];
let mut src = unsafe {
Parcel::from_ipc_parts(data.as_mut_ptr(), data.len(), objects.as_mut_ptr(), 2, noop)
};
let mut dst = Parcel::new();
dst.append_all_from(&mut src).unwrap();
let offsets: Vec<u64> = dst.kernel_fds.iter().map(|&(off, _)| off).collect();
assert_eq!(offsets, [0, second as u64], "object 1 at {second}");
assert_eq!(ino(&dst.kernel_fds[0].1), ino(&a), "object 0 at {second}");
assert_eq!(ino(&dst.kernel_fds[1].1), ino(&b), "object 1 at {second}");
}
}
#[test]
fn the_mode_of_a_written_or_received_parcel_cannot_change() {
let victim = std::fs::File::open("/dev/null").unwrap();
let (r, w) = pipe_probe();
let mut p = Parcel::new();
p.write(&ParcelFileDescriptor::new(w)).unwrap();
assert_eq!(p.set_for_rpc(true), Err(StatusCode::InvalidOperation));
assert!(p.is_kernel_backed());
p.set_data_position(16);
assert_eq!(
p.write(&victim.as_raw_fd()),
Err(StatusCode::PermissionDenied)
);
drop(p);
assert!(writers_gone(&r));
assert!(still_open(victim));
fn noop(
_: Option<&Parcel>,
_: binder_uintptr_t,
_: usize,
_: binder_uintptr_t,
_: usize,
) -> Result<()> {
Ok(())
}
let mut received = unsafe {
Parcel::from_ipc_parts(std::ptr::null_mut(), 0, std::ptr::null_mut(), 0, noop)
};
assert_eq!(
received.set_for_rpc(true),
Err(StatusCode::InvalidOperation)
);
let mut empty = Parcel::new();
assert_eq!(empty.set_for_rpc(true), Ok(()));
assert_eq!(empty.set_for_rpc(false), Ok(()));
}
#[test]
fn a_shrink_through_a_recorded_object_is_refused() {
let (r, w) = pipe_probe();
let mut p = Parcel::new();
p.write(&ParcelFileDescriptor::new(w)).unwrap();
p.write(&7i32).unwrap();
for cut in [0, 8, 31] {
assert_eq!(p.set_data_size(cut), Err(StatusCode::InvalidOperation));
}
assert_eq!(p.data_size(), 36);
assert!(!writers_gone(&r));
assert_eq!(
p.set_data_size(32),
Ok(()),
"a cut past every object is a shrink"
);
drop(p);
assert!(writers_gone(&r));
}
}
#[cfg(test)]
mod wire_golden {
use super::*;
fn enc<S: Serialize + ?Sized>(value: &S) -> Vec<u8> {
let mut parcel = Parcel::new();
parcel.write(value).unwrap();
parcel.data.as_slice().to_vec()
}
#[test]
fn scalar_wire_is_absolute_little_endian() {
assert_eq!(enc(&true), [0x01, 0x00, 0x00, 0x00]);
assert_eq!(enc(&false), [0x00, 0x00, 0x00, 0x00]);
assert_eq!(enc(&0x0102_0304i32), [0x04, 0x03, 0x02, 0x01]);
assert_eq!(enc(&0xDEAD_BEEFu32), [0xEF, 0xBE, 0xAD, 0xDE]);
assert_eq!(
enc(&0x0102_0304_0506_0708i64),
[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
);
assert_eq!(
enc(&0xDEAD_BEEF_CAFE_BABEu64),
[0xBE, 0xBA, 0xFE, 0xCA, 0xEF, 0xBE, 0xAD, 0xDE]
);
assert_eq!(
enc(&0x0102_0304_0506_0708_090A_0B0C_0D0E_0F10u128),
[
0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03,
0x02, 0x01
]
);
assert_eq!(enc(&1.0f32), [0x00, 0x00, 0x80, 0x3F]);
assert_eq!(enc(&-2.0f32), [0x00, 0x00, 0x00, 0xC0]);
assert_eq!(
enc(&1.0f64),
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F]
);
}
#[test]
fn scalar_widening_matches_the_aidl_wire() {
assert_eq!(enc(&-2i8), [0xFE, 0xFF, 0xFF, 0xFF]);
assert_eq!(enc(&0xABu8), [0xAB, 0x00, 0x00, 0x00]);
assert_eq!(enc(&-2i16), [0xFE, 0xFF, 0xFF, 0xFF]);
assert_eq!(enc(&0xBEEFu16), [0xEF, 0xBE, 0x00, 0x00]);
}
#[test]
fn array_wire_is_absolute_little_endian() {
assert_eq!(
enc(&[0xABu8, 0xCD, 0xEF][..]),
[0x03, 0x00, 0x00, 0x00, 0xAB, 0xCD, 0xEF, 0x00]
);
assert_eq!(
enc(&[-2i8, 1][..]),
[0x02, 0x00, 0x00, 0x00, 0xFE, 0x01, 0x00, 0x00]
);
assert_eq!(
enc(&[-2i16, 3][..]),
[0x02, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00]
);
assert_eq!(
enc(&[0xBEEFu16][..]),
[0x01, 0x00, 0x00, 0x00, 0xEF, 0xBE, 0x00, 0x00]
);
assert_eq!(
enc(&[0x0102_0304i32, -1][..]),
[0x02, 0x00, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01, 0xFF, 0xFF, 0xFF, 0xFF]
);
assert_eq!(
enc(&[1i64][..]),
[0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
);
assert_eq!(
enc(&[1.0f64][..]),
[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F]
);
assert_eq!(enc(&[0i32; 0][..]), [0x00, 0x00, 0x00, 0x00]);
assert_eq!(enc(&None::<Vec<i32>>), [0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn string16_wire_is_absolute_little_endian() {
assert_eq!(
enc("AB"),
[0x02, 0x00, 0x00, 0x00, 0x41, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00]
);
assert_eq!(enc("한"), [0x01, 0x00, 0x00, 0x00, 0x5C, 0xD5, 0x00, 0x00]);
assert_eq!(enc(""), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
assert_eq!(enc(&None::<String>), [0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn stability_word_is_little_endian() {
let level = i32::from(crate::Stability::Vintf);
assert_eq!(
enc(&level),
[
(level & 0xFF) as u8,
((level >> 8) & 0xFF) as u8,
((level >> 16) & 0xFF) as u8,
((level >> 24) & 0xFF) as u8,
]
);
#[cfg(not(target_os = "android"))]
assert_eq!(enc(&level), [0x3F, 0x00, 0x00, 0x00]);
}
#[test]
fn a_parcel_of_mixed_fields_keeps_every_slot_aligned() {
let mut parcel = Parcel::new();
parcel.write(&-2i8).unwrap();
parcel.write("한").unwrap();
parcel.write(&[0x0102_0304i32][..]).unwrap();
assert_eq!(
parcel.data.as_slice().to_vec(),
[
0xFE, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x5C, 0xD5, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x03, 0x02, 0x01, ]
);
}
#[test]
fn the_command_stream_is_native_not_wire() {
let cmd: u32 = crate::sys::binder::BC_ACQUIRE;
let mut native = Parcel::new();
native.write_native::<u32>(&cmd).unwrap();
assert_eq!(native.data.as_slice().to_vec(), cmd.to_ne_bytes());
let mut wire = Parcel::new();
wire.write_le::<u32>(&cmd).unwrap();
assert_eq!(wire.data.as_slice().to_vec(), cmd.to_le_bytes());
if cfg!(target_endian = "big") {
assert_ne!(native.data.as_slice(), wire.data.as_slice());
}
native.set_data_position(0);
assert_eq!(native.read_native::<u32>().unwrap(), cmd);
}
#[test]
fn null_binder_stays_a_native_island() {
let mut parcel = Parcel::new();
SerializeOption::serialize_option(None::<&crate::SIBinder>, &mut parcel).unwrap();
let bytes = parcel.data.as_slice();
let obj_len = FlatBinderObject::SIZE;
assert_eq!(
bytes[..4],
crate::sys::BINDER_TYPE_BINDER.to_ne_bytes()[..],
"object header is native, not little-endian"
);
assert!(
bytes[4..obj_len].iter().all(|&b| b == 0),
"a null binder carries no handle, cookie or flags"
);
}
}
#[cfg(test)]
mod data_serde {
use super::*;
#[test]
fn a_value_survives_the_round_trip_and_encodes_the_same_way_twice() {
let value: Vec<i32> = vec![1, -2, 0x0102_0304];
let bytes = to_bytes(&value).unwrap();
assert_eq!(
bytes,
to_bytes(&value).unwrap(),
"encoding is deterministic"
);
assert_eq!(from_bytes::<Vec<i32>>(&bytes).unwrap(), value);
let text = String::from("한 quiet");
assert_eq!(
from_bytes::<String>(&to_bytes(&text).unwrap()).unwrap(),
text
);
}
#[test]
fn the_bytes_are_the_ipc_bytes() {
let value = -2i64;
let mut kernel = Parcel::new();
kernel.write(&value).unwrap();
assert_eq!(to_bytes(&value).unwrap(), kernel.data.as_slice());
}
#[test]
fn trailing_bytes_are_an_error_rather_than_a_partial_read() {
let mut bytes = to_bytes(&7i32).unwrap();
bytes.extend_from_slice(&[0, 0, 0, 0]);
assert_eq!(from_bytes::<i32>(&bytes), Err(StatusCode::BadValue));
}
#[test]
fn a_truncated_input_is_not_enough_data() {
let bytes = to_bytes(&7i64).unwrap();
assert_eq!(
from_bytes::<i64>(&bytes[..4]),
Err(StatusCode::NotEnoughData)
);
}
#[test]
fn a_binder_field_is_refused_at_write_time() {
let mut parcel = Parcel::new_data_only();
let binder: Option<&crate::SIBinder> = None;
assert_eq!(
crate::SerializeOption::serialize_option(binder, &mut parcel),
Err(StatusCode::BadType),
"even a null binder needs an object table it will not get"
);
}
#[test]
fn a_file_descriptor_field_is_refused_before_the_dup() {
let (_r, w) = crate::ParcelFileDescriptor::pipe().expect("pipe");
let ino = rustix::fs::fstat(&w).expect("fstat").st_ino;
let mut parcel = Parcel::new_data_only();
assert_eq!(parcel.write(&w), Err(StatusCode::FdsNotAllowed));
let untouched = rustix::fs::fstat(&w).map(|s| s.st_ino) == Ok(ino);
if !untouched {
std::mem::forget(w);
}
assert!(untouched, "the caller's fd is untouched by the refusal");
}
#[test]
fn a_forged_object_in_the_input_never_becomes_a_binder() {
let mut forged = Vec::new();
forged.extend_from_slice(&crate::sys::BINDER_TYPE_BINDER.to_ne_bytes());
forged.extend_from_slice(&[0u8; 20]);
assert_eq!(
Parcel::from_vec(forged.clone()).read_object(true).err(),
Some(StatusCode::BadType),
"the object decoder refuses an object with no offset-table entry"
);
assert_eq!(
Parcel::from_vec(vec![0u8; 8]).read_object(true).err(),
Some(StatusCode::NotEnoughData),
"too few bytes to be an object fails before the table lookup"
);
assert_eq!(
from_bytes::<crate::SIBinder>(&forged),
Err(StatusCode::BadType)
);
assert_eq!(
Parcel::data_only_from_vec(forged).read::<crate::SIBinder>(),
Err(StatusCode::BadType)
);
}
#[test]
fn owned_bytes_move_out_instead_of_being_copied() {
let mut parcel = Parcel::new_data_only();
parcel.write(&vec![7u8; 1000]).unwrap();
let (ptr, len) = (parcel.as_bytes().unwrap().as_ptr(), parcel.data_size());
let bytes = parcel.into_bytes().unwrap();
assert_eq!(
(bytes.as_ptr(), bytes.len()),
(ptr, len),
"the buffer itself"
);
let adopted = Parcel::data_only_from_vec(bytes);
assert_eq!(
adopted.as_bytes().unwrap().as_ptr(),
ptr,
"adopted, not copied"
);
}
#[test]
fn stored_bytes_keep_at_most_twice_their_length() {
for bytes in [
to_bytes(&42i32).unwrap(),
to_bytes("short").unwrap(),
to_bytes(&vec![0u8; 100_000]).unwrap(),
] {
assert!(
bytes.capacity() <= 2 * bytes.len(),
"{} bytes held in {}",
bytes.len(),
bytes.capacity()
);
}
}
#[cfg(feature = "rpc")]
#[test]
fn a_parcel_holding_a_reference_refuses_to_hand_out_its_bytes() {
let mut parcel = Parcel::new_data_only();
parcel.write(&1i32).unwrap();
assert!(parcel.as_bytes().is_ok());
parcel.rpc_record_object_position(0);
assert_eq!(
parcel.as_bytes().err(),
Some(StatusCode::BadType),
"an object position makes the bytes incomplete"
);
assert_eq!(parcel.into_bytes().err(), Some(StatusCode::BadType));
}
#[test]
fn an_appended_object_table_is_refused_before_the_bytes_are_copied() {
for (is_fd, expected) in [
(false, StatusCode::BadType),
(true, StatusCode::FdsNotAllowed),
] {
let mut source = Parcel::new();
if is_fd {
let dup = std::os::fd::OwnedFd::from(std::fs::File::open("/dev/null").unwrap());
source.write_kernel_fd(dup).unwrap();
} else {
let object = FlatBinderObject::new_binder_with_flags(0);
source.write_object(&object, true).unwrap();
}
let mut sink = Parcel::new_data_only();
assert_eq!(sink.append_all_from(&mut source), Err(expected));
assert!(
sink.as_bytes().unwrap().is_empty(),
"refused before the copy — no object bytes reached the sink"
);
}
}
#[cfg(feature = "rpc")]
#[test]
fn a_holder_cut_from_a_session_parcel_cannot_be_exported() {
use crate::Parcelable;
struct SessionOps;
impl RpcParcelOps for SessionOps {
fn write_binder(&self, _b: Option<&crate::SIBinder>, _p: &mut Parcel) -> Result<()> {
Err(StatusCode::DeadObject)
}
fn read_binder(&self, _p: &mut Parcel) -> Result<Option<crate::SIBinder>> {
Err(StatusCode::DeadObject)
}
fn cancel_leaving(&self, _addrs: &[crate::rpc::RpcAddress]) {}
fn session_id(&self) -> *const () {
std::ptr::null()
}
fn records_binder_positions(&self) -> Result<bool> {
Ok(true)
}
fn acquire_copied(&self, _objects: &[&[u8]]) -> Result<CopiedBinders> {
Err(StatusCode::DeadObject)
}
}
const ADDR: [u8; 8] = *b"\xde\xad\xbe\xef\xfe\xed\xfa\xce";
let mut txn = Parcel::new();
txn.set_for_rpc(true).unwrap();
txn.attach_rpc_ops(std::sync::Arc::new(SessionOps));
txn.write(&0i32).unwrap(); txn.write(&12i32).unwrap(); let obj_pos = txn.data_position();
txn.write(&1i32).unwrap(); txn.write_aligned_data(&ADDR).unwrap();
txn.rpc_record_object_position(obj_pos);
txn.set_data_position(0);
let mut holder = crate::ParcelableHolder::new(crate::Stability::Local);
holder.read_from_parcel(&mut txn).unwrap();
assert_eq!(
holder.with_payload_parcel(|p| p.map(|p| p.rpc_object_positions().to_vec())),
Some(vec![0]),
"the sub-parcel keeps the binder's position, shifted to its own start"
);
let exported = to_bytes(&holder);
assert!(
!exported
.as_deref()
.is_ok_and(|b| b.windows(ADDR.len()).any(|w| w == ADDR)),
"the session address reached the exported bytes"
);
assert_eq!(exported.err(), Some(StatusCode::BadType));
}
#[cfg(feature = "rpc")]
#[derive(Default)]
struct RecordingOps(std::sync::Mutex<Vec<Vec<crate::rpc::RpcAddress>>>);
#[cfg(feature = "rpc")]
impl RpcParcelOps for RecordingOps {
fn write_binder(&self, _b: Option<&crate::SIBinder>, _p: &mut Parcel) -> Result<()> {
Err(StatusCode::DeadObject)
}
fn read_binder(&self, _p: &mut Parcel) -> Result<Option<crate::SIBinder>> {
Err(StatusCode::DeadObject)
}
fn cancel_leaving(&self, addrs: &[crate::rpc::RpcAddress]) {
self.0.lock().unwrap().push(addrs.to_vec());
}
fn session_id(&self) -> *const () {
(self as *const Self).cast()
}
fn records_binder_positions(&self) -> Result<bool> {
Ok(true)
}
fn acquire_copied(&self, _objects: &[&[u8]]) -> Result<CopiedBinders> {
Ok(CopiedBinders::default())
}
}
#[cfg(feature = "rpc")]
fn inode_of(fd: impl std::os::fd::AsFd) -> u64 {
rustix::fs::fstat(fd).unwrap().st_ino as u64
}
#[cfg(feature = "rpc")]
#[test]
fn append_from_refuses_a_session_less_source_into_a_session_parcel() {
let mut source = Parcel::new_data_only();
source.write(&1i32).unwrap();
let mut dest = parcel_with_reservations(std::sync::Arc::default(), 0);
assert_eq!(dest.append_all_from(&mut source), Err(StatusCode::BadType));
assert_eq!(dest.data_size(), 0, "refused before the copy");
}
#[cfg(feature = "rpc")]
#[test]
fn append_from_refuses_a_kernel_source_into_a_session_parcel() {
let mut source = Parcel::new();
source.write(&1i32).unwrap();
let mut dest = parcel_with_reservations(std::sync::Arc::default(), 0);
assert_eq!(dest.append_all_from(&mut source), Err(StatusCode::BadType));
assert_eq!(dest.data_size(), 0, "refused before the copy");
}
#[cfg(feature = "rpc")]
#[test]
fn append_from_refuses_an_rpc_source_into_a_kernel_parcel() {
let session = parcel_with_reservations(std::sync::Arc::default(), 0);
for mut source in [session, Parcel::new_data_only()] {
source.write(&1i32).unwrap();
let mut dest = Parcel::new();
assert_eq!(dest.append_all_from(&mut source), Err(StatusCode::BadType));
assert_eq!(dest.data_size(), 0, "refused before the copy");
}
}
#[cfg(feature = "rpc")]
#[test]
fn holder_sub_parcel_positions_are_shifted_and_bounded() {
use crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR;
use crate::Parcelable;
let (outside, inside) = std::os::unix::net::UnixStream::pair().unwrap();
let (outside_ino, inside_ino) = (inode_of(&outside), inode_of(&inside));
let mut txn = Parcel::new();
txn.configure_rpc(
std::sync::Arc::new(RecordingOps::default()),
crate::rpc::FileDescriptorTransportMode::Unix,
true,
);
let binder = |p: &mut Parcel| {
let at = p.data_position();
p.write(&RPC_TYPE_BINDER).unwrap();
p.write_aligned_data(&[0xa5u8; 8]).unwrap();
p.rpc_record_object_position(at);
};
let fd = |p: &mut Parcel, idx: i32| {
let at = p.data_position();
p.write(&TYPE_NATIVE_FILE_DESCRIPTOR).unwrap();
p.write(&idx).unwrap();
p.rpc_record_object_position(at);
};
binder(&mut txn); txn.write(&0i32).unwrap(); txn.write(&20i32).unwrap(); fd(&mut txn, 1); binder(&mut txn); fd(&mut txn, 0); txn.rpc_set_in_fds(vec![outside.into(), inside.into()]);
txn.set_data_position(12);
let mut holder = crate::ParcelableHolder::new(crate::Stability::Local);
holder.read_from_parcel(&mut txn).unwrap();
assert_eq!(
txn.data_position(),
40,
"the outer read resumes after the payload"
);
holder.with_payload_parcel(|sub| {
let sub = sub.expect("an undecoded payload");
assert_eq!(sub.data_size(), 20);
assert_eq!(
sub.rpc_object_positions(),
&[0, 8],
"in range only, minus its start"
);
assert_eq!(
sub.rpc_fd_mode(),
crate::rpc::FileDescriptorTransportMode::Unix
);
assert!(sub.rpc_record_fd_positions());
assert_eq!(
le_i32_at(sub.rpc_data_bytes(), 4),
Some(0),
"the index names the sub-parcel's own table"
);
assert_eq!(sub.rpc_take_in_fd(0).map(inode_of), Some(inside_ino));
});
assert!(
txn.rpc_take_in_fd(1).is_none(),
"the payload's fd moved out"
);
assert_eq!(txn.rpc_take_in_fd(0).map(inode_of), Some(outside_ino));
}
#[cfg(feature = "rpc")]
#[test]
fn append_from_rewrites_a_copied_fd_index_into_the_destination_table() {
use crate::ParcelFileDescriptor;
let ops = std::sync::Arc::new(RecordingOps::default());
let rpc_parcel = || {
let mut p = Parcel::new();
p.configure_rpc(
ops.clone(),
crate::rpc::FileDescriptorTransportMode::Unix,
true,
);
p
};
let (first, copied) = std::os::unix::net::UnixStream::pair().unwrap();
let (first_ino, copied_ino) = (inode_of(&first), inode_of(&copied));
let mut source = rpc_parcel();
source.write(&ParcelFileDescriptor::new(copied)).unwrap();
let mut dest = rpc_parcel();
dest.write(&ParcelFileDescriptor::new(first)).unwrap();
let start = dest.data_position();
dest.append_all_from(&mut source).unwrap();
let shifted: Vec<u32> = source
.rpc_object_positions()
.iter()
.map(|&p| p + start as u32)
.collect();
assert_eq!(&dest.rpc_object_positions()[1..], &shifted[..]);
let copied_pos = shifted[0] as usize;
assert_eq!(le_i32_at(dest.rpc_data_bytes(), copied_pos + 4), Some(1));
let inodes: Vec<u64> = dest.rpc_out_fds().iter().map(inode_of).collect();
assert_eq!(inodes, vec![first_ino, copied_ino]);
assert_eq!(
source.rpc_out_fds().len(),
1,
"the source keeps its fd: the copy is a dup"
);
}
#[cfg(feature = "rpc")]
fn parcel_with_reservations(ops: std::sync::Arc<RecordingOps>, n: u64) -> Parcel {
use crate::rpc::{AddressSpace, RpcAddress};
let mut p = Parcel::new();
p.configure_rpc(ops, crate::rpc::FileDescriptorTransportMode::None, false);
let mut counter = 0u64;
for _ in 0..n {
p.rpc_record_leaving_addr(RpcAddress::unique(&mut counter, AddressSpace::Acceptor));
}
p
}
#[cfg(feature = "rpc")]
#[test]
fn unsent_rpc_parcel_settles_once_on_drop() {
let ops = std::sync::Arc::new(RecordingOps::default());
let p = parcel_with_reservations(ops.clone(), 2);
assert!(
ops.0.lock().unwrap().is_empty(),
"nothing settles before the drop"
);
drop(p);
{
let calls = ops.0.lock().unwrap();
assert_eq!(calls.len(), 1, "one `cancel_leaving` per dropped parcel");
assert_eq!(calls[0].len(), 2, "every recorded address is handed back");
}
let mut p = parcel_with_reservations(ops.clone(), 1);
p.set_for_rpc(false).unwrap();
assert_eq!(ops.0.lock().unwrap().len(), 2);
drop(p);
assert_eq!(
ops.0.lock().unwrap().len(),
2,
"no second settlement after `set_for_rpc`"
);
}
#[cfg(feature = "rpc")]
#[test]
fn sent_rpc_parcel_does_not_settle() {
let ops = std::sync::Arc::new(RecordingOps::default());
let p = parcel_with_reservations(ops.clone(), 1);
p.rpc_begin_send().unwrap();
p.rpc_end_send(true);
drop(p);
assert!(
ops.0.lock().unwrap().is_empty(),
"a `Sent` parcel's bumps belong to the peer's DEC_STRONG"
);
}
#[cfg(feature = "rpc")]
#[test]
fn in_flight_rpc_parcel_settles_on_drop() {
let ops = std::sync::Arc::new(RecordingOps::default());
let p = parcel_with_reservations(ops.clone(), 1);
p.rpc_begin_send().unwrap();
drop(p);
assert_eq!(ops.0.lock().unwrap().len(), 1);
}
#[cfg(feature = "rpc")]
#[test]
fn append_from_refuses_another_session() {
let mut source = parcel_with_reservations(std::sync::Arc::default(), 0);
source.write(&7i32).unwrap();
let mut dest = parcel_with_reservations(std::sync::Arc::default(), 0);
assert_eq!(dest.append_all_from(&mut source), Err(StatusCode::BadType));
assert!(
dest.as_bytes().unwrap().is_empty(),
"refused before the copy"
);
}
#[cfg(feature = "rpc")]
#[test]
fn kernel_parcel_ignores_send_state() {
let p = Parcel::new();
assert_eq!(p.rpc_begin_send(), Ok(()));
assert_eq!(
p.rpc_begin_send(),
Ok(()),
"no state to claim: every claim succeeds"
);
p.rpc_end_send(true);
p.rpc_end_send(false);
assert!(p.rpc.is_none(), "the calls do not create `RpcFields`");
drop(p);
}
#[cfg(feature = "rpc")]
#[test]
fn append_from_refuses_a_source_with_reservations() {
let ops = std::sync::Arc::new(RecordingOps::default());
let mut source = parcel_with_reservations(ops.clone(), 1);
source.write(&7i32).unwrap();
let mut dest = parcel_with_reservations(ops.clone(), 0);
assert_eq!(dest.append_all_from(&mut source), Err(StatusCode::BadType));
assert!(
dest.as_bytes().unwrap().is_empty(),
"refused before the copy"
);
let mut clean = parcel_with_reservations(ops.clone(), 0);
clean.write(&7i32).unwrap();
assert_eq!(dest.append_all_from(&mut clean), Ok(()));
dest.rpc_begin_send().unwrap();
dest.rpc_end_send(true);
assert_eq!(dest.append_all_from(&mut clean), Err(StatusCode::BadType));
}
#[test]
fn parcel_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Parcel>();
}
}