use crate::error::{Result, StatusCode};
use crate::{
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))
}
pub fn pipe() -> Result<(Self, Self)> {
#[cfg(not(target_vendor = "apple"))]
let (read, write) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC)?;
#[cfg(target_vendor = "apple")]
let (read, write) = {
let (read, write) = rustix::pipe::pipe()?;
rustix::io::fcntl_setfd(&read, rustix::io::FdFlags::CLOEXEC)?;
rustix::io::fcntl_setfd(&write, rustix::io::FdFlags::CLOEXEC)?;
(read, write)
};
Ok((Self(read), Self(write)))
}
}
impl std::io::Read for ParcelFileDescriptor {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
(&*self).read(buf)
}
}
impl std::io::Read for &ParcelFileDescriptor {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
Ok(rustix::io::read(&self.0, buf)?)
}
}
impl std::io::Write for ParcelFileDescriptor {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
(&*self).write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl std::io::Write for &ParcelFileDescriptor {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(rustix::io::write(&self.0, buf)?)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
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_attr(
not(feature = "rpc"),
allow(
dead_code,
reason = "the fd paths name the type in every build; only `rpc` builds construct it"
)
)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum RpcFdProfile {
V0,
V1Plus,
}
fn rpc_fd_profile(parcel: &Parcel) -> Result<Option<RpcFdProfile>> {
if parcel.is_kernel_backed() {
return Ok(None);
}
#[cfg(feature = "rpc")]
{
use crate::rpc::FileDescriptorTransportMode as M;
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)),
}
}
#[cfg(not(feature = "rpc"))]
Err(StatusCode::FdsNotAllowed)
}
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> {
rpc_fd_profile(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(());
}
parcel.write_kernel_fd(dup)
}
pub(crate) fn read_raw_fd(parcel: &mut Parcel) -> Result<OwnedFd> {
let profile = rpc_fd_profile(parcel)?;
#[cfg(not(feature = "rpc"))]
let _ = profile;
#[cfg(feature = "rpc")]
if let Some(profile) = profile {
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);
}
parcel.read_kernel_fd_dup()
}
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);
}
let profile = rpc_fd_profile(parcel)?;
#[cfg(not(feature = "rpc"))]
let _ = profile;
#[cfg(feature = "rpc")]
if let Some(profile) = profile {
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 {
parcel.read_kernel_fd_with(send_detached)?;
}
Ok(Some(ParcelFileDescriptor::new(fd)))
}
}
fn send_detached(comm_fd: BorrowedFd<'_>) {
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_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;
}
}
}
}
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::data_only_from_vec(input.to_vec());
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::data_only_from_vec(rest[take..].to_vec());
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::data_only_from_vec(rest.to_vec());
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 a_pipe_reads_and_writes_like_a_file() {
use std::io::{Read, Write};
let (read_end, write_end) = ParcelFileDescriptor::pipe().expect("pipe");
for end in [&read_end, &write_end] {
let flags = rustix::io::fcntl_getfd(end).expect("F_GETFD");
assert!(
flags.contains(rustix::io::FdFlags::CLOEXEC),
"a pipe end must be O_CLOEXEC"
);
}
(&write_end).write_all(b"hello").expect("write_all");
let mut buf = [0u8; 5];
(&read_end).read_exact(&mut buf).expect("read_exact");
assert_eq!(&buf, b"hello");
drop(write_end);
let mut rest = Vec::new();
(&read_end).read_to_end(&mut rest).expect("read_to_end");
assert!(rest.is_empty());
let (read_end, write_end) = ParcelFileDescriptor::pipe().expect("pipe");
drop(read_end);
let err = (&write_end)
.write_all(b"x")
.expect_err("a broken pipe fails");
assert_eq!(err.kind(), std::io::ErrorKind::BrokenPipe);
}
#[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).unwrap();
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::data_only_from_vec(body.to_vec());
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)"
);
}
}