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 {}
#[cfg(feature = "rpc")]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum RpcFdProfile {
V0,
V1Plus,
}
#[cfg(feature = "rpc")]
fn rpc_fd_profile(parcel: &Parcel) -> Result<Option<RpcFdProfile>> {
use crate::rpc::FileDescriptorTransportMode as M;
if parcel.is_kernel_backed() {
return Ok(None);
}
match parcel.rpc_fd_mode() {
M::None => Err(StatusCode::FdsNotAllowed),
M::Unix if parcel.rpc_record_fd_positions() => Ok(Some(RpcFdProfile::V1Plus)),
M::Unix => Ok(Some(RpcFdProfile::V0)),
}
}
pub(crate) fn write_raw_fd(parcel: &mut Parcel, fd: BorrowedFd<'_>) -> Result<()> {
write_raw_owned_fd(parcel, dup_for_parcel(parcel, fd)?)
}
fn dup_for_parcel(parcel: &Parcel, fd: BorrowedFd<'_>) -> Result<OwnedFd> {
#[cfg(feature = "rpc")]
rpc_fd_profile(parcel)?;
#[cfg(not(feature = "rpc"))]
let _ = parcel;
Ok(rustix::io::fcntl_dupfd_cloexec(fd, 0)?)
}
fn write_raw_owned_fd(parcel: &mut Parcel, dup: OwnedFd) -> Result<()> {
#[cfg(feature = "rpc")]
if let Some(profile) = rpc_fd_profile(parcel)? {
let idx = parcel.rpc_out_fds().len() as i32;
match profile {
RpcFdProfile::V1Plus => {
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);
}
RpcFdProfile::V0 => parcel.write::<i32>(&idx)?,
}
let pushed = parcel.rpc_push_out_fd(dup);
debug_assert_eq!(pushed, idx);
return Ok(());
}
let obj = flat_binder_object::new_with_fd(dup.as_raw_fd(), true);
parcel.write_object(&obj, true)?;
let _ = dup.into_raw_fd();
Ok(())
}
pub(crate) fn read_raw_fd(parcel: &mut Parcel) -> Result<OwnedFd> {
#[cfg(feature = "rpc")]
if let Some(profile) = rpc_fd_profile(parcel)? {
if profile == RpcFdProfile::V1Plus {
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);
}
return parcel
.rpc_take_in_fd(idx as usize)
.ok_or(StatusCode::BadValue);
}
let obj = parcel.read_object(true)?;
if obj.header_type() != crate::sys::BINDER_TYPE_FD {
return Err(StatusCode::BadType);
}
Ok(rustix::io::fcntl_dupfd_cloexec(obj.borrowed_fd(), 0)?)
}
impl Serialize for ParcelFileDescriptor {
fn serialize(&self, parcel: &mut Parcel) -> Result<()> {
let dup = dup_for_parcel(parcel, self.0.as_fd())?;
#[cfg(feature = "rpc")]
if let Some(profile) = rpc_fd_profile(parcel)? {
parcel.write::<i32>(&1)?; if profile == RpcFdProfile::V1Plus {
parcel.write::<i32>(&0)?; }
return write_raw_owned_fd(parcel, dup);
}
parcel.write::<i32>(&1)?;
parcel.write::<i32>(&0)?;
write_raw_owned_fd(parcel, dup)
}
}
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>> {
let present = parcel.read::<i32>()?;
if present == crate::NULL_PARCELABLE_FLAG {
return Ok(None);
}
if present != crate::NON_NULL_PARCELABLE_FLAG {
return Err(StatusCode::UnexpectedNull);
}
#[cfg(feature = "rpc")]
if let Some(profile) = rpc_fd_profile(parcel)? {
if profile == RpcFdProfile::V1Plus {
let has_comm = parcel.read::<i32>()?;
if has_comm != 0 {
return Err(StatusCode::BadValue);
}
}
return Ok(Some(ParcelFileDescriptor::new(read_raw_fd(parcel)?)));
}
let has_comm = parcel.read::<i32>()?;
let fd = read_raw_fd(parcel)?;
if has_comm != 0 {
let comm = parcel.read_object(true)?;
if comm.header_type() != crate::sys::BINDER_TYPE_FD {
return Err(StatusCode::BadType);
}
const DETACHED: i32 = 2;
let notice = DETACHED.to_be_bytes();
#[cfg(any(target_os = "linux", target_os = "android"))]
let flags = rustix::net::SendFlags::NOSIGNAL;
#[cfg(not(any(target_os = "linux", target_os = "android")))]
let flags = rustix::net::SendFlags::empty();
loop {
match rustix::net::send(comm.borrowed_fd(), ¬ice, flags) {
Ok(n) if n == notice.len() => break,
Ok(n) => {
log::error!("short write of the DETACHED status to the comm fd: {n} bytes");
break;
}
Err(rustix::io::Errno::INTR) => continue,
Err(e) => {
log::error!("failed to write the DETACHED status to the comm fd: {e}");
break;
}
}
}
}
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(all(feature = "rpc", feature = "fuzzing"))]
#[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(all(feature = "rpc", feature = "fuzzing"))]
#[doc(hidden)]
pub fn __fuzz_rpc_fd_index_v1(input: &[u8]) {
let mut p = fuzz_v1_parcel(input);
let _ = <ParcelFileDescriptor as DeserializeOption>::deserialize_option(&mut p);
}
#[cfg(all(feature = "rpc", feature = "fuzzing"))]
fn fuzz_v1_parcel(input: &[u8]) -> Parcel {
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);
p
}
#[cfg(all(feature = "rpc", feature = "fuzzing"))]
#[doc(hidden)]
pub fn __fuzz_rpc_raw_fd(input: &[u8]) {
let Some((&profile, rest)) = input.split_first() else {
return;
};
let mut p = if profile % 2 == 0 {
let mut p = Parcel::from_vec(rest.to_vec());
p.set_for_rpc(true);
p.set_rpc_fd_mode(crate::rpc::FileDescriptorTransportMode::Unix);
p
} else {
fuzz_v1_parcel(rest)
};
let _ = read_raw_fd(&mut p);
}
#[cfg(test)]
mod tests {
use std::os::fd::FromRawFd;
use super::*;
#[test]
fn test_parcel_file_descriptor() {
let f = std::fs::File::open("/dev/null").expect("/dev/null");
let raw = f.as_raw_fd();
let pfd = ParcelFileDescriptor::from(f);
assert_eq!(pfd.as_raw_fd(), raw);
let owned_fd: OwnedFd = pfd.into();
let pfd = ParcelFileDescriptor::new(owned_fd);
assert_eq!(pfd.into_raw_fd(), raw);
drop(unsafe { OwnedFd::from_raw_fd(raw) });
}
#[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)"
);
}
}