use crate::error::{Result, StatusCode};
use crate::{
binder_object::flat_binder_object, Deserialize, DeserializeArray, DeserializeOption, Parcel,
Serialize, SerializeArray, SerializeOption,
};
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd};
#[derive(Debug)]
pub struct ParcelFileDescriptor(OwnedFd);
impl ParcelFileDescriptor {
pub fn new<F: Into<OwnedFd>>(fd: F) -> Self {
Self(fd.into())
}
pub fn try_clone(&self) -> Result<Self> {
let dup = rustix::io::fcntl_dupfd_cloexec(&self.0, 0)?;
Ok(Self(dup))
}
}
impl AsRef<OwnedFd> for ParcelFileDescriptor {
fn as_ref(&self) -> &OwnedFd {
&self.0
}
}
impl AsFd for ParcelFileDescriptor {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl From<ParcelFileDescriptor> for OwnedFd {
fn from(fd: ParcelFileDescriptor) -> OwnedFd {
fd.0
}
}
impl From<OwnedFd> for ParcelFileDescriptor {
fn from(fd: OwnedFd) -> Self {
Self(fd)
}
}
impl From<std::fs::File> for ParcelFileDescriptor {
fn from(file: std::fs::File) -> Self {
Self(file.into())
}
}
impl AsRawFd for ParcelFileDescriptor {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for ParcelFileDescriptor {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
impl PartialEq for ParcelFileDescriptor {
fn eq(&self, other: &Self) -> bool {
self.as_raw_fd() == other.as_raw_fd()
}
}
impl Eq for ParcelFileDescriptor {}
impl Serialize for ParcelFileDescriptor {
fn serialize(&self, parcel: &mut Parcel) -> Result<()> {
#[cfg(feature = "rpc")]
if parcel.is_for_rpc() {
use crate::rpc::FileDescriptorTransportMode as M;
match parcel.rpc_fd_mode() {
M::None => return Err(StatusCode::BadType),
M::Unix => {
let dup = rustix::io::fcntl_dupfd_cloexec(&self.0, 0)?;
let idx = parcel.rpc_push_out_fd(dup);
if parcel.rpc_record_fd_positions() {
parcel.write::<i32>(&1)?; parcel.write::<i32>(&0)?; let obj_pos = parcel.data_position();
parcel.write::<i32>(
&crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR,
)?;
parcel.write::<i32>(&idx)?; parcel.rpc_record_object_position(obj_pos);
} else {
parcel.write::<i32>(&1)?; parcel.write::<i32>(&idx)?; }
return Ok(());
}
}
}
parcel.write::<i32>(&1)?;
let dup_fd = rustix::io::fcntl_dupfd_cloexec(&self.0, 0)?;
parcel.write::<i32>(&0)?;
let obj = flat_binder_object::new_with_fd(dup_fd.as_raw_fd(), true);
parcel.write_object(&obj, true)?;
let _ = dup_fd.into_raw_fd();
Ok(())
}
}
impl SerializeArray for ParcelFileDescriptor {}
impl SerializeOption for ParcelFileDescriptor {
fn serialize_option(this: Option<&Self>, parcel: &mut Parcel) -> Result<()> {
if let Some(f) = this {
f.serialize(parcel)
} else {
parcel.write::<i32>(&0)
}
}
}
impl DeserializeOption for ParcelFileDescriptor {
fn deserialize_option(parcel: &mut Parcel) -> Result<Option<Self>> {
#[cfg(feature = "rpc")]
if parcel.is_for_rpc() {
use crate::rpc::FileDescriptorTransportMode as M;
let present = parcel.read::<i32>()?;
if present == 0 {
return Ok(None);
}
return match parcel.rpc_fd_mode() {
M::None => Err(StatusCode::BadType),
M::Unix if parcel.rpc_record_fd_positions() => {
let has_comm = parcel.read::<i32>()?;
if has_comm != 0 {
return Err(StatusCode::BadValue);
}
let pos = parcel.data_position();
if !parcel.rpc_object_position_present(pos) {
return Err(StatusCode::BadType);
}
let ty = parcel.read::<i32>()?;
if ty != crate::rpc::wire_android13::TYPE_NATIVE_FILE_DESCRIPTOR {
return Err(StatusCode::BadType);
}
let idx = parcel.read::<i32>()?;
if idx < 0 {
return Err(StatusCode::BadValue);
}
let fd = parcel
.rpc_take_in_fd(idx as usize)
.ok_or(StatusCode::BadValue)?;
Ok(Some(ParcelFileDescriptor::new(fd)))
}
M::Unix => {
let idx = parcel.read::<i32>()?;
if idx < 0 {
return Err(StatusCode::BadValue);
}
let fd = parcel
.rpc_take_in_fd(idx as usize)
.ok_or(StatusCode::BadValue)?;
Ok(Some(ParcelFileDescriptor::new(fd)))
}
};
}
let present = parcel.read::<i32>()?;
if present == 0 {
return Ok(None);
}
let has_comm = parcel.read::<i32>()?;
if has_comm != 0 {
return Err(StatusCode::BadValue);
}
let obj = parcel.read_object(true)?;
if obj.header_type() != crate::sys::BINDER_TYPE_FD {
return Err(StatusCode::BadType);
}
let fd = rustix::io::fcntl_dupfd_cloexec(obj.borrowed_fd(), 0)?;
Ok(Some(ParcelFileDescriptor::new(fd)))
}
}
impl Deserialize for ParcelFileDescriptor {
fn deserialize(parcel: &mut Parcel) -> Result<Self> {
Deserialize::deserialize(parcel)
.transpose()
.unwrap_or(Err(StatusCode::UnexpectedNull))
}
}
impl DeserializeArray for ParcelFileDescriptor {}
#[cfg(feature = "rpc")]
#[doc(hidden)]
pub fn __fuzz_rpc_fd_index(input: &[u8]) {
let mut p = Parcel::from_vec(input.to_vec());
p.set_for_rpc(true);
p.set_rpc_fd_mode(crate::rpc::FileDescriptorTransportMode::Unix);
let _ = <ParcelFileDescriptor as DeserializeOption>::deserialize_option(&mut p);
}
#[cfg(feature = "rpc")]
#[doc(hidden)]
pub fn __fuzz_rpc_fd_index_v1(input: &[u8]) {
let (n_pos, rest) = match input.split_first() {
Some((&n, rest)) => ((n % 16) as usize, rest),
None => (0, input),
};
let take = (n_pos * 4).min(rest.len());
let positions: Vec<u32> = rest[..take]
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
let mut p = Parcel::from_vec(rest[take..].to_vec());
p.set_for_rpc(true);
p.set_rpc_fd_mode(crate::rpc::FileDescriptorTransportMode::Unix);
p.set_rpc_record_fd_positions(true); p.rpc_set_object_positions(positions);
let _ = <ParcelFileDescriptor as DeserializeOption>::deserialize_option(&mut p);
}
#[cfg(test)]
mod tests {
use std::os::fd::FromRawFd;
use super::*;
#[test]
fn test_parcel_file_descriptor() {
let fd = unsafe { OwnedFd::from_raw_fd(1) };
let pfd = ParcelFileDescriptor::new(fd);
assert_eq!(pfd.as_raw_fd(), 1);
let owned_fd: OwnedFd = pfd.into();
let pfd = ParcelFileDescriptor::new(owned_fd);
assert_eq!(pfd.into_raw_fd(), 1);
}
#[test]
fn test_pfd_conversions_and_try_clone() {
let f = std::fs::OpenOptions::new()
.read(true)
.open("/dev/null")
.expect("/dev/null");
let pfd: ParcelFileDescriptor = f.into();
let _borrowed: BorrowedFd<'_> = pfd.as_fd();
assert!(pfd.as_raw_fd() >= 0);
let cloned = pfd.try_clone().expect("try_clone");
assert_ne!(
pfd.as_raw_fd(),
cloned.as_raw_fd(),
"dup must allocate a new fd"
);
let owned: OwnedFd = cloned.into();
let _pfd2: ParcelFileDescriptor = owned.into();
}
#[cfg(feature = "rpc")]
fn dev_null_pfd() -> ParcelFileDescriptor {
let f = std::fs::OpenOptions::new()
.read(true)
.open("/dev/null")
.expect("/dev/null");
ParcelFileDescriptor::new(f)
}
#[cfg(feature = "rpc")]
fn rpc_parcel(record_fd_positions: bool) -> Parcel {
use crate::rpc::FileDescriptorTransportMode as M;
let mut p = Parcel::new();
p.set_for_rpc(true);
p.set_rpc_fd_mode(M::Unix);
p.set_rpc_record_fd_positions(record_fd_positions);
p
}
#[cfg(feature = "rpc")]
#[test]
fn rpc_fd_v1_body_golden() {
let mut p = rpc_parcel(true);
dev_null_pfd().serialize(&mut p).expect("serialize v1+ fd");
let mut want = Vec::new();
want.extend_from_slice(&1i32.to_le_bytes()); want.extend_from_slice(&0i32.to_le_bytes()); want.extend_from_slice(&2i32.to_le_bytes()); want.extend_from_slice(&0i32.to_le_bytes()); assert_eq!(p.rpc_data_bytes(), &want[..], "v1+ AOSP fd body");
assert_eq!(
p.rpc_object_positions(),
&[8u32],
"recorded position = TYPE int32 offset (after not-null+hasComm), not +0"
);
}
#[cfg(feature = "rpc")]
#[test]
fn rpc_fd_r34_body_byte_unchanged() {
let mut p = rpc_parcel(false);
dev_null_pfd().serialize(&mut p).expect("serialize r34 fd");
let mut want = Vec::new();
want.extend_from_slice(&1i32.to_le_bytes()); want.extend_from_slice(&0i32.to_le_bytes()); assert_eq!(p.rpc_data_bytes(), &want[..], "R34 legacy fd body");
assert!(
p.rpc_object_positions().is_empty(),
"R34/v0 has no object table"
);
}
#[cfg(feature = "rpc")]
#[test]
fn rpc_fd_null_body_unchanged_both_profiles() {
for record in [false, true] {
let mut p = rpc_parcel(record);
<ParcelFileDescriptor as SerializeOption>::serialize_option(None, &mut p)
.expect("serialize None fd");
assert_eq!(
p.rpc_data_bytes(),
&0i32.to_le_bytes()[..],
"null fd = writeInt32(0) (record_fd_positions={record})"
);
assert!(
p.rpc_object_positions().is_empty(),
"null fd records no position (record_fd_positions={record})"
);
}
}
#[cfg(feature = "rpc")]
fn v1_reader(body: &[u8], positions: Vec<u32>) -> Parcel {
use crate::rpc::FileDescriptorTransportMode as M;
let mut p = Parcel::from_vec(body.to_vec());
p.set_for_rpc(true);
p.set_rpc_fd_mode(M::Unix);
p.set_rpc_record_fd_positions(true);
p.rpc_set_object_positions(positions);
p.set_data_position(0);
p
}
#[cfg(feature = "rpc")]
#[test]
fn rpc_fd_v1_strict_read_rejects_mutants() {
let ok_body = {
let mut b = Vec::new();
b.extend_from_slice(&1i32.to_le_bytes()); b.extend_from_slice(&0i32.to_le_bytes()); b.extend_from_slice(&2i32.to_le_bytes()); b.extend_from_slice(&0i32.to_le_bytes()); b
};
let de = <ParcelFileDescriptor as DeserializeOption>::deserialize_option;
let mut legacy = Vec::new();
legacy.extend_from_slice(&1i32.to_le_bytes());
legacy.extend_from_slice(&7i32.to_le_bytes());
assert_eq!(
de(&mut v1_reader(&legacy, vec![8])).unwrap_err(),
StatusCode::BadValue,
"legacy [present|idx] vs v1+ reader (hasComm!=0)"
);
assert_eq!(
de(&mut v1_reader(&ok_body, vec![])).unwrap_err(),
StatusCode::BadType,
"unrecorded fd position rejected (strict v1+)"
);
assert_eq!(
de(&mut v1_reader(&ok_body, vec![0])).unwrap_err(),
StatusCode::BadType,
"position must point at the TYPE int32 (start+8), not +0"
);
let mut wrong_ty = ok_body.clone();
wrong_ty[8..12].copy_from_slice(&1i32.to_le_bytes());
assert_eq!(
de(&mut v1_reader(&wrong_ty, vec![8])).unwrap_err(),
StatusCode::BadType,
"TYPE != TYPE_NATIVE_FILE_DESCRIPTOR rejected"
);
let mut comm = ok_body.clone();
comm[4..8].copy_from_slice(&1i32.to_le_bytes());
assert_eq!(
de(&mut v1_reader(&comm, vec![8])).unwrap_err(),
StatusCode::BadValue,
"hasComm != 0 rejected (AC-11.5)"
);
assert_eq!(
de(&mut v1_reader(&ok_body, vec![8])).unwrap_err(),
StatusCode::BadValue,
"well-formed body reaches the fd lookup (no in-fd installed)"
);
}
}