use fuchsia_runtime::HandleInfo;
use std::ffi::CString;
use std::{fmt, mem, num};
use thiserror::Error;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
#[derive(Error, Debug)]
pub enum ProcessargsError {
TryFromInt(num::TryFromIntError),
SizeTooLarge(usize),
TooManyHandles(usize),
}
impl ProcessargsError {
pub fn as_zx_status(&self) -> zx::Status {
match self {
ProcessargsError::TryFromInt(_)
| ProcessargsError::SizeTooLarge(_)
| ProcessargsError::TooManyHandles(_) => zx::Status::INVALID_ARGS,
}
}
}
impl fmt::Display for ProcessargsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProcessargsError::TryFromInt(e) => {
write!(f, "Value > u32 MAX when building processargs message: {}", e)
}
ProcessargsError::SizeTooLarge(v) => write!(
f,
"Cannot build processargs message, byte size too large ({} > {})",
v,
zx::sys::ZX_CHANNEL_MAX_MSG_BYTES
),
ProcessargsError::TooManyHandles(v) => write!(
f,
"Cannot build processargs message, too many handles ({} > {})",
v,
zx::sys::ZX_CHANNEL_MAX_MSG_HANDLES
),
}
}
}
const ZX_PROCARGS_PROTOCOL: u32 = 0x4150585d;
const ZX_PROCARGS_VERSION: u32 = 0x00001000;
#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Default)]
#[repr(C)]
pub(crate) struct MessageHeader {
pub protocol: u32,
pub version: u32,
pub handle_info_off: u32,
pub args_off: u32,
pub args_num: u32,
pub environ_off: u32,
pub environ_num: u32,
names_off: u32,
names_num: u32,
}
pub struct StartupHandle {
pub handle: zx::Handle,
pub info: HandleInfo,
}
#[derive(Default)]
pub struct MessageContents {
pub args: Vec<CString>,
pub environment_vars: Vec<CString>,
pub namespace_paths: Vec<CString>,
pub handles: Vec<StartupHandle>,
}
pub struct Message {
bytes: Vec<u8>,
handles: Vec<zx::Handle>,
}
type HandleInfoRaw = u32;
impl Message {
pub fn build(contents: MessageContents) -> Result<Message, ProcessargsError> {
let (header, size) = Self::build_header(&contents)?;
let mut data = Vec::with_capacity(size);
data.extend_from_slice(header.as_bytes());
assert!(data.len() == header.handle_info_off as usize);
let mut handles = Vec::with_capacity(contents.handles.len());
for handle in contents.handles {
let raw_info = handle.info.as_raw();
static_assertions::assert_eq_size_val!(raw_info, 0 as HandleInfoRaw);
data.extend_from_slice(&raw_info.to_ne_bytes());
handles.push(handle.handle);
}
assert!(data.len() == header.args_off as usize);
for arg in &contents.args {
data.extend_from_slice(arg.as_bytes_with_nul());
}
assert!(data.len() == header.environ_off as usize);
for var in &contents.environment_vars {
data.extend_from_slice(var.as_bytes_with_nul());
}
assert!(data.len() == header.names_off as usize);
for path in &contents.namespace_paths {
data.extend_from_slice(path.as_bytes_with_nul());
}
assert!(data.len() == size);
Ok(Message { bytes: data, handles })
}
pub fn calculate_size(contents: &MessageContents) -> Result<usize, ProcessargsError> {
let (_, size) = Self::build_header(contents)?;
Ok(size)
}
fn build_header(config: &MessageContents) -> Result<(MessageHeader, usize), ProcessargsError> {
let num_handles = config.handles.len();
if num_handles > zx::sys::ZX_CHANNEL_MAX_MSG_HANDLES as usize {
return Err(ProcessargsError::TooManyHandles(num_handles));
}
let mut header = MessageHeader {
protocol: ZX_PROCARGS_PROTOCOL,
version: ZX_PROCARGS_VERSION,
..Default::default()
};
let mut size = mem::size_of_val(&header);
let mut f = || {
header.handle_info_off = u32::try_from(size)?;
size += mem::size_of::<HandleInfoRaw>() * num_handles;
header.args_off = u32::try_from(size)?;
header.args_num = u32::try_from(config.args.len())?;
for arg in &config.args {
size += arg.as_bytes_with_nul().len();
}
header.environ_off = u32::try_from(size)?;
header.environ_num = u32::try_from(config.environment_vars.len())?;
for var in &config.environment_vars {
size += var.as_bytes_with_nul().len();
}
header.names_off = u32::try_from(size)?;
header.names_num = u32::try_from(config.namespace_paths.len())?;
for path in &config.namespace_paths {
size += path.as_bytes_with_nul().len();
}
Ok(())
};
f().map_err(|e| ProcessargsError::TryFromInt(e))?;
if size > zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize {
return Err(ProcessargsError::SizeTooLarge(size));
}
Ok((header, size))
}
pub fn write(self, channel: &zx::Channel) -> Result<(), zx::Status> {
let mut handles = self.handles;
channel.write(self.bytes.as_slice(), &mut handles)
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Error;
use fuchsia_runtime::HandleType;
use std::iter;
use zx::{AsHandleRef, HandleBased};
#[test]
fn build_and_write_message() -> Result<(), Error> {
let (dum0, dum1, dum2) = (zx::Vmo::create(1)?, zx::Vmo::create(1)?, zx::Vmo::create(1)?);
let handles = vec![
StartupHandle {
handle: dum0.into_handle(),
info: HandleInfo::new(HandleType::User1, 0x1234),
},
StartupHandle {
handle: dum1.into_handle(),
info: HandleInfo::new(HandleType::NamespaceDirectory, 0),
},
StartupHandle {
handle: dum2.into_handle(),
info: HandleInfo::new(HandleType::NamespaceDirectory, 1),
},
];
let handle_koids: Vec<zx::Koid> =
handles.iter().map(|h| h.handle.get_koid()).collect::<Result<_, _>>()?;
let config = MessageContents {
args: vec![CString::new("arg1")?, CString::new("arg2")?, CString::new("arg3")?],
environment_vars: vec![CString::new("FOO=BAR")?],
namespace_paths: vec![CString::new("/data")?, CString::new("/pkg")?],
handles,
};
let calculated_size = Message::calculate_size(&config)?;
let message = Message::build(config)?;
assert_eq!(calculated_size, message.bytes.len());
let (chan_wr, chan_rd) = zx::Channel::create();
message.write(&chan_wr)?;
let mut read_buf = zx::MessageBuf::new();
chan_rd.read(&mut read_buf)?;
let (read_bytes, read_handles) = read_buf.split();
let mut correct = Vec::new();
correct.extend_from_slice(b"\x5d\x58\x50\x41"); correct.extend_from_slice(b"\x00\x10\x00\x00"); correct.extend_from_slice(b"\x24\x00\x00\x00"); correct.extend_from_slice(b"\x30\x00\x00\x00"); correct.extend_from_slice(b"\x03\x00\x00\x00"); correct.extend_from_slice(b"\x3F\x00\x00\x00"); correct.extend_from_slice(b"\x01\x00\x00\x00"); correct.extend_from_slice(b"\x47\x00\x00\x00"); correct.extend_from_slice(b"\x02\x00\x00\x00"); correct.extend_from_slice(b"\xF1\x00\x34\x12"); correct.extend_from_slice(b"\x20\x00\x00\x00"); correct.extend_from_slice(b"\x20\x00\x01\x00"); correct.extend_from_slice(b"arg1\0"); correct.extend_from_slice(b"arg2\0"); correct.extend_from_slice(b"arg3\0"); correct.extend_from_slice(b"FOO=BAR\0"); correct.extend_from_slice(b"/data\0"); correct.extend_from_slice(b"/pkg\0");
assert_eq!(read_bytes.len(), calculated_size);
assert_eq!(read_bytes, correct);
let read_koids: Vec<zx::Koid> =
read_handles.iter().map(|h| h.get_koid()).collect::<Result<_, _>>()?;
assert_eq!(read_koids, handle_koids);
Ok(())
}
#[test]
fn byte_limit() -> Result<(), Error> {
const LIMIT: usize = zx::sys::ZX_CHANNEL_MAX_MSG_BYTES as usize;
const ARG_LIMIT: usize = LIMIT - 1 - mem::size_of::<MessageHeader>();
let (chan_wr, chan_rd) = zx::Channel::create();
let mut read_buf = zx::MessageBuf::new();
let make_bytes = iter::repeat_with(|| b'a');
let arg: CString = CString::new(make_bytes.take(ARG_LIMIT).collect::<Vec<u8>>())?;
let config = MessageContents { args: vec![arg], ..Default::default() };
Message::build(config)?.write(&chan_wr)?;
chan_rd.read(&mut read_buf)?;
assert_eq!(read_buf.bytes().len(), LIMIT);
let arg2: CString = CString::new(make_bytes.take(ARG_LIMIT + 1).collect::<Vec<u8>>())?;
let config2 = MessageContents { args: vec![arg2], ..Default::default() };
let result = Message::build(config2);
match result {
Err(ProcessargsError::SizeTooLarge(_)) => {}
Err(err) => {
panic!("Unexpected error type: {}", err);
}
Ok(_) => {
panic!("build message unexpectedly succeeded with too large argument");
}
}
Ok(())
}
#[test]
fn handle_limit() -> Result<(), Error> {
const LIMIT: usize = zx::sys::ZX_CHANNEL_MAX_MSG_HANDLES as usize;
let make_handles = iter::repeat_with(|| StartupHandle {
handle: zx::Vmo::create(1).expect("Failed to create VMO").into_handle(),
info: HandleInfo::new(HandleType::User1, 0),
});
let handles: Vec<StartupHandle> = make_handles.take(LIMIT).collect();
let config = MessageContents { handles, ..Default::default() };
let (chan_wr, chan_rd) = zx::Channel::create();
Message::build(config)?.write(&chan_wr)?;
let mut read_buf = zx::MessageBuf::new();
chan_rd.read(&mut read_buf)?;
assert_eq!(read_buf.n_handles(), LIMIT);
let handles2: Vec<StartupHandle> = make_handles.take(LIMIT + 1).collect();
let config2 = MessageContents { handles: handles2, ..Default::default() };
let result = Message::build(config2);
match result {
Err(ProcessargsError::TooManyHandles(_)) => {}
Err(err) => {
panic!("Unexpected error type: {}", err);
}
Ok(_) => {
panic!("build message unexpectedly succeeded with too many handles");
}
}
Ok(())
}
}