| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // The constant and type definitions in this file must all match |
| // //zircon/system/public/zircon/processargs.h |
| // |
| // TODO: Figure out a more foolproof way to keep these in sync, e.g. bindgen from the header |
| // directly, or maybe a new Tricium linter like IfThisThenThat. |
| |
| use { |
| fuchsia_runtime::HandleInfo, |
| fuchsia_zircon as zx, |
| std::convert::TryFrom, |
| std::ffi::CString, |
| std::fmt, |
| std::mem, |
| std::num, |
| thiserror::Error, |
| zerocopy::{AsBytes, FromBytes}, |
| }; |
| |
| /// Possible errors that can occur during processargs startup message construction |
| #[allow(missing_docs)] // No docs on individual error variants. |
| #[derive(Error, Debug)] |
| pub enum ProcessargsError { |
| TryFromInt(num::TryFromIntError), |
| SizeTooLarge(usize), |
| TooManyHandles(usize), |
| } |
| |
| impl ProcessargsError { |
| /// Returns an appropriate zx::Status code for the given error. |
| pub fn as_zx_status(&self) -> zx::Status { |
| match self { |
| ProcessargsError::TryFromInt(_) |
| | ProcessargsError::SizeTooLarge(_) |
| | ProcessargsError::TooManyHandles(_) => zx::Status::INVALID_ARGS, |
| } |
| } |
| } |
| |
| // Can't use macro-based failure Display derive with the _MAX_MSG_BYTES argument below |
| 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; |
| |
| /// Header for bootstrap message following the processargs protocol. |
| #[derive(FromBytes, AsBytes, Default)] |
| #[repr(C)] |
| pub(crate) struct MessageHeader { |
| // Protocol and version identifiers to allow for different process start message protocols and |
| // versioning of the same. |
| pub protocol: u32, |
| pub version: u32, |
| |
| // Offset from start of message to handle info array, which contains one HandleInfo as a u32 |
| // per handle passed along with the message. |
| pub handle_info_off: u32, |
| |
| // Offset from start of message to arguments and count of arguments. Arguments are provided as |
| // a set of null-terminated UTF-8 strings, one after the other. |
| pub args_off: u32, |
| pub args_num: u32, |
| |
| // Offset from start of message to environment strings and count of them. Environment entries |
| // are provided as a set of null-terminated UTF-8 strings, one after the other. Canonically |
| // each string has the form "NAME=VALUE", but nothing enforces this. |
| pub environ_off: u32, |
| pub environ_num: u32, |
| |
| // Offset from start of message to namespace path strings and count of them. These strings are |
| // packed similar to the argument strings, but are referenced by NamespaceDirectory (PA_NS_DIR) |
| // handle table entries and used to set up namespaces. |
| // |
| // Specifically: In a handle table entry with HandleType of NamespaceDirectory (PA_NS_DIR), the |
| // u16 handle info argument is an index into this name table. |
| names_off: u32, |
| names_num: u32, |
| } |
| |
| /// A container for a single startup handle, containing a handle and metadata. Used as an input to |
| /// [ProcessBuilder::add_handles()]. |
| /// |
| /// [ProcessBuilder::add_handles()]: crate::ProcessBuilder::add_handles() |
| pub struct StartupHandle { |
| /// A handle. |
| pub handle: zx::Handle, |
| |
| /// Handle metadata. See [fuchsia_runtime::HandleInfo]. |
| 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>, |
| } |
| |
| /// A bootstrap message following the processargs protocol. |
| /// |
| /// See [//docs/zircon/program_loading.md#The-processargs-protocol][program_loading.md] or |
| /// [//zircon/system/public/zircon/processargs.h][processargs] for more details. |
| /// |
| /// [program_loading.md]: https://fuchsia.dev/fuchsia-src/concepts/booting/program_loading#The-processargs-protocol |
| /// [processargs]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/public/zircon/processargs.h |
| pub struct Message { |
| bytes: Vec<u8>, |
| handles: Vec<zx::Handle>, |
| } |
| |
| // Return type of fuchsia_runtime::HandleInfo::as_raw(), checked with static assert below. |
| type HandleInfoRaw = u32; |
| |
| impl Message { |
| /// Create a new bootstrap message using the given contents. |
| 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()); |
| |
| // Sanity check length against the offsets in the header as we go. Failures are bugs in |
| // this code and serious enough to panic rather than continue, hence the asserts rather |
| // than returning an Err(). |
| 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()); |
| } |
| |
| // Sanity check final message size. |
| assert!(data.len() == size); |
| Ok(Message { bytes: data, handles }) |
| } |
| |
| /// Calculate the size that a bootstrap message will be if created using the given contents. |
| /// |
| /// Note that the size returned is only for the message data and does not include the size of |
| /// the handles themselves, only the handle info in the message. |
| pub fn calculate_size(contents: &MessageContents) -> Result<usize, ProcessargsError> { |
| let (_, size) = Self::build_header(contents)?; |
| Ok(size) |
| } |
| |
| /// Builds the processargs message header for the given config, as well as calculates the total |
| /// message 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)) |
| } |
| |
| /// Write the processargs message to the provided channel. |
| 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::*, |
| anyhow::Error, |
| fuchsia_runtime::HandleType, |
| fuchsia_zircon::{AsHandleRef, HandleBased}, |
| std::iter, |
| }; |
| |
| #[test] |
| fn build_and_write_message() -> Result<(), Error> { |
| // We need some dummy handles to use in the message below, since you can't send an invalid |
| // handle in a channel. We just use VMOs since they're easy to create, even though they're |
| // not semantically valid for a processargs handle type like PA_NS_DIR. |
| 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()); |
| |
| // Write the message into a channel, read it back from the other end. |
| 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(); |
| |
| // concat! doesn't work for byte literals and there's no concat_bytes! (yet), so we just |
| // build this in a Vec instead since it's a test. |
| let mut correct = Vec::new(); |
| correct.extend_from_slice(b"\x5d\x58\x50\x41"); // protocol |
| correct.extend_from_slice(b"\x00\x10\x00\x00"); // version |
| correct.extend_from_slice(b"\x24\x00\x00\x00"); // handle_info_off |
| correct.extend_from_slice(b"\x30\x00\x00\x00"); // args_off |
| correct.extend_from_slice(b"\x03\x00\x00\x00"); // args_num |
| correct.extend_from_slice(b"\x3F\x00\x00\x00"); // environ_off |
| correct.extend_from_slice(b"\x01\x00\x00\x00"); // environ_num |
| correct.extend_from_slice(b"\x47\x00\x00\x00"); // names_off |
| correct.extend_from_slice(b"\x02\x00\x00\x00"); // names_num |
| correct.extend_from_slice(b"\xF1\x00\x34\x12"); // handle info |
| 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"); // args |
| correct.extend_from_slice(b"arg2\0"); // |
| correct.extend_from_slice(b"arg3\0"); // |
| correct.extend_from_slice(b"FOO=BAR\0"); // environ |
| correct.extend_from_slice(b"/data\0"); // namespace paths |
| 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() }; |
| |
| // Should succeed at limit. |
| Message::build(config)?.write(&chan_wr)?; |
| chan_rd.read(&mut read_buf)?; |
| assert_eq!(read_buf.bytes().len(), LIMIT); |
| |
| // Should fail to build just over 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() }; |
| |
| // Should succeed at limit. |
| 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); |
| |
| // Should fail to build with one more handle. |
| 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(()) |
| } |
| } |