| // 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. |
| |
| //! Native process creation and program loading library. |
| //! |
| //! # Restrictions |
| //! |
| //! Most Fuchsia processes are not able to use this library. |
| //! |
| //! This library uses the [zx_process_create] syscall to create a new process in a job. Use of that |
| //! syscall requires that the job of the process using the syscall (not the job that the process is |
| //! being created in) be allowed to create processes. In concrete terms, the process using this |
| //! library must be in a job whose [ZX_POL_NEW_PROCESS job policy is |
| //! ZX_POL_ACTION_ALLOW][zx_job_set_policy]. |
| //! |
| //! Most processes on Fuchsia run in jobs where this job policy is set to DENY and thus will not |
| //! be able to use this library. Those processes should instead use the [fuchsia.process.Launcher] |
| //! FIDL service, which is itself implemented using this library. [fdio::spawn()], |
| //! [fdio::spawn_vmo()], and [fdio::spawn_etc()] provide simple interfaces to this service. |
| //! |
| //! # Example |
| //! |
| //! ``` |
| //! let process_name = CString::new("my_process")?; |
| //! let job = /* job to create new process in */; |
| //! let executable = /* VMO with execute rights containing ELF executable */; |
| //! let pkg_directory = /* fidl::endpoints::ClientEnd for fuchsia.io.Directory */; |
| //! let out_directory = /* server end of zx::Channel */; |
| //! let other_handle = /* some arbitrary zx::Handle */; |
| //! |
| //! let builder = ProcessBuilder::new(&process_name, &job, executable)?; |
| //! builder.add_arguments(vec![process_name, CString::new("arg0")?]); |
| //! builder.add_environment_variables(vec![CString::new("VAR=VALUE")?]); |
| //! builder.add_namespace_entries(vec![NamespaceEntry{ |
| //! path: CString::new("/pkg")?, |
| //! directory: package_directory, |
| //! }])?; |
| //! builder.add_handles(vec![ |
| //! StartupHandle{ |
| //! handle: out_directory.into_handle(), |
| //! info: HandleInfo::new(HandleType::DirectoryRequest, 0), |
| //! }, |
| //! StartupHandle{ |
| //! handle: other_handle, |
| //! info: HandleInfo::new(HandleType::User0, 1), |
| //! }, |
| //! ])?; |
| //! |
| //! let built_process: BuiltProcess = builder.build()?; |
| //! let process: zx::Process = builder.start()?; |
| //! ``` |
| //! |
| //! [zx_process_create]: https://fuchsia.dev/fuchsia-src/reference/syscalls/process_create.md |
| //! [zx_job_set_policy]: https://fuchsia.dev/fuchsia-src/reference/syscalls/job_set_policy.md |
| //! [fuchsia.process.Launcher]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/fidl/fuchsia-process/launcher.fidl |
| // |
| // TODO: Consider supporting this for processes that do not meet the above requirements (nearly |
| // all), where it can optionally build the process directly if able or delegate to a remote |
| // fuchsia.process.Launcher (possibly through fdio::spawn_etc, in which case it would just be an |
| // alternative front-end for that, similar to our C++ ProcessBuilder library, though that pulls in |
| // a dependency on fdio). |
| |
| #![deny(missing_docs)] |
| |
| pub use self::elf_load::ElfLoadError; |
| pub use self::elf_parse::ElfParseError; |
| pub use self::processargs::{ProcessargsError, StartupHandle}; |
| |
| mod elf_load; |
| mod elf_parse; |
| mod processargs; |
| mod util; |
| |
| use { |
| anyhow::{anyhow, Context}, |
| fidl::endpoints::{ClientEnd, Proxy}, |
| fidl_fuchsia_io as fio, fidl_fuchsia_ldsvc as fldsvc, |
| fuchsia_async::{self as fasync, TimeoutExt}, |
| fuchsia_runtime::{HandleInfo, HandleType}, |
| fuchsia_zircon::{self as zx, AsHandleRef, DurationNum, HandleBased}, |
| futures::prelude::*, |
| lazy_static::lazy_static, |
| log::warn, |
| std::convert::TryFrom, |
| std::default::Default, |
| std::ffi::{CStr, CString}, |
| std::iter, |
| std::mem, |
| thiserror::Error, |
| }; |
| |
| /// A container for a single namespace entry, containing a path and a directory handle. Used as an |
| /// input to [ProcessBuilder::add_namespace_entries()]. |
| pub struct NamespaceEntry { |
| /// Namespace path. |
| pub path: CString, |
| |
| /// Namespace directory handle. |
| pub directory: ClientEnd<fio::DirectoryMarker>, |
| } |
| |
| /// The main builder type for this crate. Collects inputs and creates a new process. |
| /// |
| /// See top-level crate documentation for a usage example. |
| pub struct ProcessBuilder { |
| /// The ELF binary for the new process. |
| executable: zx::Vmo, |
| /// The fuchsia.ldsvc.Loader service to use for the new process, if dynamically linked. |
| ldsvc: Option<fldsvc::LoaderProxy>, |
| /// A non-default vDSO to use for the new process, if any. |
| non_default_vdso: Option<zx::Vmo>, |
| /// The contents of the main processargs message to be sent to the new process. |
| msg_contents: processargs::MessageContents, |
| /// Handles that are common to both the linker and main processargs messages, wrapped in an |
| /// inner struct for code organization and clarity around borrows. |
| common: CommonMessageHandles, |
| } |
| |
| struct CommonMessageHandles { |
| process: zx::Process, |
| thread: zx::Thread, |
| root_vmar: zx::Vmar, |
| } |
| |
| /// A container for a fully built but not yet started (as in, its initial thread is not yet |
| /// running) process, with all related handles and metadata. Output of [ProcessBuilder::build()]. |
| /// |
| /// You can use this struct to start the process with [BuiltProcess::start()], which is a simple |
| /// wrapper around the [zx_process_start] syscall. You can optionally use the handles and |
| /// information in this struct to manipulate the process or its address space before starting it, |
| /// such as when creating a process in a debugger. |
| /// |
| /// [zx_process_start]: https://fuchsia.dev/fuchsia-src/reference/syscalls/process_start.md |
| pub struct BuiltProcess { |
| /// The newly created process. |
| pub process: zx::Process, |
| |
| /// The root VMAR for the created process. |
| pub root_vmar: zx::Vmar, |
| |
| /// The process's initial thread. |
| pub thread: zx::Thread, |
| |
| /// The process's entry point. |
| pub entry: usize, |
| |
| /// The initial thread's stack pointer. |
| pub stack: usize, |
| |
| /// The bootstrap channel, to be passed to the process on start as arg1 in zx_process_start / |
| /// zx::Process::start. |
| pub bootstrap: zx::Channel, |
| |
| /// The base address of the VDSO in the process's VMAR, to be passed to the process on start as |
| /// arg2 in zx_process_start / zx::Process::start. |
| pub vdso_base: usize, |
| |
| /// The base address where the ELF executable, or the dynamic linker if the ELF was dynamically |
| /// linked, was loaded in the process's VMAR. |
| pub elf_base: usize, |
| } |
| |
| impl ProcessBuilder { |
| /// Create a new ProcessBuilder that can be used to create a new process under the given job |
| /// with the given name and ELF64 executable (as a VMO). |
| /// |
| /// This job is only used to create the process and thus is not taken ownership of. To provide |
| /// a default job handle to be passed to the new process, use [ProcessBuilder::add_handles()] |
| /// with [HandleType::DefaultJob]. |
| /// |
| /// The provided VMO must have the [zx::Rights::EXECUTE] right. |
| /// |
| /// # Errors |
| /// |
| /// Returns Err([ProcessBuilderError::CreateProcess]) if process creation fails, such as if the |
| /// process using this is disallowed direct process creation rights through job policy. See |
| /// top-level crate documentation for more details. |
| pub fn new( |
| name: &CStr, |
| job: &zx::Job, |
| executable: zx::Vmo, |
| ) -> Result<ProcessBuilder, ProcessBuilderError> { |
| if job.is_invalid_handle() { |
| return Err(ProcessBuilderError::BadHandle("Invalid job handle")); |
| } |
| if executable.is_invalid_handle() { |
| return Err(ProcessBuilderError::BadHandle("Invalid executable handle")); |
| } |
| |
| // Creating the process immediately has the benefit that we fail fast if the calling |
| // process does not have permission to create processes directly. |
| let (process, root_vmar) = job |
| .create_child_process(name.to_bytes()) |
| .map_err(ProcessBuilderError::CreateProcess)?; |
| |
| // Create the initial thread. |
| let thread = |
| process.create_thread(b"initial-thread").map_err(ProcessBuilderError::CreateThread)?; |
| |
| // Add duplicates of the process, VMAR, and thread handles to the bootstrap message. |
| let msg_contents = processargs::MessageContents::default(); |
| let mut pb = ProcessBuilder { |
| executable, |
| ldsvc: None, |
| non_default_vdso: None, |
| msg_contents, |
| common: CommonMessageHandles { process, thread, root_vmar }, |
| }; |
| pb.common.add_to_message(&mut pb.msg_contents)?; |
| Ok(pb) |
| } |
| |
| /// Sets the fuchsia.ldsvc.Loader service for the process. |
| /// |
| /// The loader service is used to load dynamic libraries if the executable is a dynamically |
| /// linked ELF file (i.e. if it contains a PT_INTERP header), and is required for such |
| /// executables. It will only be provided to the new process in this case. Otherwise, it is |
| /// unused and has no effect. |
| /// |
| /// If no loader service has been provided and it is needed, process creation will fail. Note |
| /// that this differs from the automatic fallback behavior of previous process creation |
| /// libraries, which would clone the loader of the current process. This fallback is likely to |
| /// fail in subtle and confusing ways. An appropriate loader service that has access to the |
| /// libraries or interpreter must be provided. |
| /// |
| /// Note that [ProcessBuilder::add_handles()] will automatically pass a handle with type |
| /// [HandleType::LdsvcLoader] to this function. |
| /// |
| /// If called multiple times (e.g. if a loader was initially provided through |
| /// [ProcessBuilder::add_handles()] and you want to replace it), the new loader replaces the |
| /// previous and the handle to the previous loader is dropped. |
| pub fn set_loader_service( |
| &mut self, |
| ldsvc: ClientEnd<fldsvc::LoaderMarker>, |
| ) -> Result<(), ProcessBuilderError> { |
| if ldsvc.is_invalid_handle() { |
| return Err(ProcessBuilderError::BadHandle("Invalid loader service handle")); |
| } |
| self.ldsvc = |
| Some(ldsvc.into_proxy().map_err(|e| { |
| ProcessBuilderError::Internal("Failed to get LoaderProxy", e.into()) |
| })?); |
| Ok(()) |
| } |
| |
| /// Sets the vDSO VMO for the process. |
| pub fn set_vdso_vmo(&mut self, vdso: zx::Vmo) { |
| self.non_default_vdso = Some(vdso); |
| } |
| |
| /// Add arguments to the process's bootstrap message. Successive calls append (not replace) |
| /// arguments. |
| pub fn add_arguments(&mut self, mut args: Vec<CString>) { |
| self.msg_contents.args.append(&mut args); |
| } |
| |
| /// Add environment variables to the process's bootstrap message. Successive calls append (not |
| /// replace) environment variables. |
| pub fn add_environment_variables(&mut self, mut vars: Vec<CString>) { |
| self.msg_contents.environment_vars.append(&mut vars); |
| } |
| |
| /// Add handles to the process's bootstrap message. Successive calls append (not replace) |
| /// handles. |
| /// |
| /// Each [StartupHandle] contains a [zx::Handle] object accompanied by a [HandleInfo] object |
| /// that includes the handle type and a type/context-dependent argument. |
| /// |
| /// A [HandleType::LdsvcLoader] handle will automatically be passed along to |
| /// [ProcessBuilder::set_loader_service()] if provided through this function. |
| /// |
| /// # Errors |
| /// |
| /// [HandleType::NamespaceDirectory] handles should not be added through this function since |
| /// they must be accompanied with a path. Use [ProcessBuilder::add_namespace_entries()] for |
| /// that instead. |
| /// |
| /// The following handle types cannot be added through this, as they are added automatically by |
| /// the ProcessBuilder: |
| /// * [HandleType::ProcessSelf] |
| /// * [HandleType::ThreadSelf] |
| /// * [HandleType::RootVmar] |
| /// * [HandleType::LoadedVmar] |
| /// * [HandleType::StackVmo] |
| /// * [HandleType::ExecutableVmo] |
| pub fn add_handles( |
| &mut self, |
| mut startup_handles: Vec<StartupHandle>, |
| ) -> Result<(), ProcessBuilderError> { |
| // Do a bit of validation before adding to the bootstrap handles. |
| for h in &startup_handles { |
| if h.handle.is_invalid() { |
| return Err(ProcessBuilderError::BadHandle("Invalid handle in startup handles")); |
| } |
| |
| let t = h.info.handle_type(); |
| match t { |
| HandleType::NamespaceDirectory => { |
| return Err(ProcessBuilderError::InvalidArg( |
| "Cannot add NamespaceDirectory handles directly, use add_namespace_entries" |
| .into(), |
| )); |
| } |
| HandleType::ProcessSelf |
| | HandleType::ThreadSelf |
| | HandleType::RootVmar |
| | HandleType::LoadedVmar |
| | HandleType::StackVmo |
| | HandleType::ExecutableVmo => { |
| return Err(ProcessBuilderError::InvalidArg(format!( |
| "Cannot add a {:?} handle directly, it will be automatically added", |
| t, |
| ))); |
| } |
| _ => {} |
| } |
| } |
| |
| // Intentionally separate from validation so that we don't partially add namespace entries. |
| for h in startup_handles.drain(..) { |
| match h.info.handle_type() { |
| HandleType::LdsvcLoader => { |
| // Automatically pass this to |set_loader_service| instead. |
| self.set_loader_service(ClientEnd::from(h.handle))?; |
| } |
| HandleType::VdsoVmo => { |
| self.set_vdso_vmo(h.handle.into()); |
| } |
| _ => { |
| self.msg_contents.handles.push(h); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Add directories to the process's namespace. |
| /// |
| /// Successive calls append new namespace entries, not replace previous entries. |
| /// |
| /// Each [NamespaceEntry] contains a client connection to a fuchsia.io.Directory FIDL service |
| /// and a path to add that directory to the process's namespace as. |
| /// |
| /// # Errors |
| /// |
| /// Returns Err([ProcessBuilderError::InvalidArg]) if the maximum number of namespace entries |
| /// (2^16) was reached and the entry could not be added. This is exceedingly unlikely, and most |
| /// likely if you are anywhere near this limit [ProcessBuilder::build()] will fail because the |
| /// process's processargs startup messsage is over its own length limit. |
| pub fn add_namespace_entries( |
| &mut self, |
| mut entries: Vec<NamespaceEntry>, |
| ) -> Result<(), ProcessBuilderError> { |
| // Namespace entries are split into a namespace path, that is included in the bootstrap |
| // message (as the so-called "namespace table"), plus a NamespaceDirectory handle, where the arg |
| // value is the index of the path in the namespace table. |
| // |
| // Check that the namespace table doesn't exceed 2^16 entries, since the HandleInfo arg is |
| // only 16-bits. Realistically this will never matter - if you're anywhere near this |
| // many entries, you're going to exceed the bootstrap message length limit - but Rust |
| // encourages us (and makes it easy) to be safe about the edge case here. |
| let mut idx = u16::try_from(self.msg_contents.namespace_paths.len()) |
| .expect("namespace_paths.len should never be larger than a u16"); |
| let num_entries = u16::try_from(entries.len()) |
| .map_err(|_| ProcessBuilderError::InvalidArg("Too many namespace entries".into()))?; |
| if idx.checked_add(num_entries).is_none() { |
| return Err(ProcessBuilderError::InvalidArg( |
| "Can't add namespace entries, limit reached".into(), |
| )); |
| } |
| |
| for entry in &entries { |
| if entry.directory.is_invalid_handle() { |
| return Err(ProcessBuilderError::BadHandle("Invalid handle in namespace entry")); |
| } |
| } |
| |
| // Intentionally separate from validation so that we don't partially add namespace entries= |
| for entry in entries.drain(..) { |
| self.msg_contents.namespace_paths.push(entry.path); |
| self.msg_contents.handles.push(StartupHandle { |
| handle: zx::Handle::from(entry.directory), |
| info: HandleInfo::new(HandleType::NamespaceDirectory, idx), |
| }); |
| idx += 1; |
| } |
| Ok(()) |
| } |
| |
| /// Build the new process using the data and handles provided to the ProcessBuilder. |
| /// |
| /// The return value of this function is a [BuiltProcess] struct which contains the new process |
| /// and all the handles and data needed to start it, but the process' initial thread is not yet |
| /// started. Use [BuiltProcess::start()] or the [zx_process_start] syscall to actually start |
| /// it. |
| /// |
| /// # Errors |
| /// |
| /// There are many errors that could result during process loading and only some are listed |
| /// here. See [ProcessBuilderError] for the various error types that can be returned. |
| /// |
| /// Returns Err([ProcessBuilderError::LoaderMissing]) if the ELF executable is dynamically |
| /// linked (has a PT_INTERP program header) but no loader service has been provided through |
| /// [ProcessBuilder::set_loader_service()] or [ProcessBuilder::add_handles()]. |
| /// |
| /// [zx_process_start]: https://fuchsia.dev/fuchsia-src/reference/syscalls/process_start.md |
| pub async fn build(mut self) -> Result<BuiltProcess, ProcessBuilderError> { |
| // Parse the executable as an ELF64 file, reading in the headers we need. Done first since |
| // this is most likely to be invalid and error out. |
| let elf_headers = elf_parse::Elf64Headers::from_vmo(&self.executable)?; |
| |
| // Create bootstrap message channel. |
| let (bootstrap_rd, bootstrap_wr) = zx::Channel::create() |
| .map_err(|s| ProcessBuilderError::GenericStatus("Failed to create channel", s))?; |
| |
| // Check if main executable is dynamically linked, and handle appropriately. |
| let loaded_elf; |
| let mut reserve_vmar = None; |
| let dynamic; |
| if let Some(interp_hdr) = |
| elf_headers.program_header_with_type(elf_parse::SegmentType::Interp)? |
| { |
| // Dynamically linked so defer loading the main executable to the dynamic |
| // linker/loader, which we load here instead. |
| dynamic = true; |
| |
| // Check that a ldsvc.Loader service was provided. |
| let ldsvc = self.ldsvc.take().ok_or(ProcessBuilderError::LoaderMissing())?; |
| |
| // A process using PT_INTERP might be loading a libc.so that supports sanitizers; |
| // reserve the low address region for sanitizers to allocate shadow memory. |
| // |
| // The reservation VMAR ensures that the initial allocations & mappings made in this |
| // function stay out of this area. It is destroyed below before returning and the |
| // process's own allocations can use the full address space. |
| // |
| // !! WARNING: This makes a specific address VMAR allocation, so it must come before |
| // any elf_load::load_elf calls. !! |
| reserve_vmar = |
| Some(ReservationVmar::reserve_low_address_space(&self.common.root_vmar)?); |
| |
| // Get the dynamic linker and map it into the process's address space. |
| let ld_vmo = get_dynamic_linker(&ldsvc, &self.executable, interp_hdr).await?; |
| let ld_headers = elf_parse::Elf64Headers::from_vmo(&ld_vmo)?; |
| loaded_elf = elf_load::load_elf(&ld_vmo, &self.common.root_vmar, &ld_headers)?; |
| |
| // Build the dynamic linker bootstrap message and write it to the bootstrap channel. |
| // This message is written before the primary bootstrap message since it is consumed |
| // first in the dynamic linker. |
| let executable = mem::replace(&mut self.executable, zx::Handle::invalid().into()); |
| let msg = self.build_linker_message(ldsvc, executable, loaded_elf.vmar)?; |
| msg.write(&bootstrap_wr).map_err(ProcessBuilderError::WriteBootstrapMessage)?; |
| } else { |
| // Statically linked but still position-independent (ET_DYN) ELF, load directly. |
| dynamic = false; |
| |
| loaded_elf = |
| elf_load::load_elf(&self.executable, &self.common.root_vmar, &elf_headers)?; |
| self.msg_contents.handles.push(StartupHandle { |
| handle: loaded_elf.vmar.into_handle(), |
| info: HandleInfo::new(HandleType::LoadedVmar, 0), |
| }); |
| } |
| |
| // Load the vDSO - either the default system vDSO, or the user-provided one - into the |
| // process's address space and a handle to it to the bootstrap message. |
| let vdso_base = self.load_vdso()?; |
| |
| // Calculate initial stack size. |
| let stack_size; |
| let stack_vmo_name; |
| if dynamic { |
| // Calculate the initial stack size for the dynamic linker. This factors in the size of |
| // an extra handle for the stac) that hasn't yet been added to the message contents, |
| // since creating the stack requires this size. |
| stack_size = calculate_initial_linker_stack_size(&mut self.msg_contents, 1)?; |
| stack_vmo_name = format!("stack: msg of {:#x?}", stack_size); |
| } else { |
| // Set stack size from PT_GNU_STACK header, if present, or use the default. The dynamic |
| // linker handles this for dynamically linked ELFs (above case). |
| const ZIRCON_DEFAULT_STACK_SIZE: usize = 256 << 10; // 256KiB |
| let mut ss = ("default", ZIRCON_DEFAULT_STACK_SIZE); |
| if let Some(stack_hdr) = |
| elf_headers.program_header_with_type(elf_parse::SegmentType::GnuStack)? |
| { |
| if stack_hdr.memsz > 0 { |
| ss = ("explicit", stack_hdr.memsz as usize); |
| } |
| } |
| |
| // Stack size must be page aligned. |
| stack_size = util::page_end(ss.1); |
| stack_vmo_name = format!("stack: {} {:#x?}", ss.0, stack_size); |
| } |
| |
| // Allocate the initial thread's stack, map it, and add a handle to the bootstrap message. |
| let stack_vmo_name = |
| CString::new(stack_vmo_name).expect("Stack VMO name must not contain interior nul's"); |
| let stack_ptr = self.create_stack(stack_size, &stack_vmo_name)?; |
| |
| // Build and send the primary bootstrap message. |
| let msg = processargs::Message::build(self.msg_contents)?; |
| msg.write(&bootstrap_wr).map_err(ProcessBuilderError::WriteBootstrapMessage)?; |
| |
| // Explicitly destroy the reservation VMAR before returning so that we can be sure it is |
| // gone (so we don't end up with a process with half its address space gone). |
| if let Some(mut r) = reserve_vmar { |
| r.destroy().map_err(ProcessBuilderError::DestroyReservationVMAR)?; |
| } |
| |
| Ok(BuiltProcess { |
| process: self.common.process, |
| root_vmar: self.common.root_vmar, |
| thread: self.common.thread, |
| entry: loaded_elf.entry, |
| stack: stack_ptr, |
| bootstrap: bootstrap_rd, |
| vdso_base: vdso_base, |
| elf_base: loaded_elf.vmar_base, |
| }) |
| } |
| |
| /// Build the bootstrap message for the dynamic linker, which uses the same processargs |
| /// protocol as the message for the main process but somewhat different contents. |
| /// |
| /// The LoaderProxy provided must be ready to be converted to a Handle with into_channel(). In |
| /// other words, there must be no other active clones of the proxy, no open requests, etc. The |
| /// intention is that the user provides a handle only (perhaps wrapped in a ClientEnd) through |
| /// [ProcessBuilder::set_loader_service()], not a Proxy, so the library can be sure this |
| /// invariant is maintained and a failure is a library bug. |
| fn build_linker_message( |
| &self, |
| ldsvc: fldsvc::LoaderProxy, |
| executable: zx::Vmo, |
| loaded_vmar: zx::Vmar, |
| ) -> Result<processargs::Message, ProcessBuilderError> { |
| // Don't need to use the ldsvc.Loader anymore; turn it back into into a raw handle so |
| // we can pass it along in the dynamic linker bootstrap message. |
| let ldsvc_hnd = |
| ldsvc.into_channel().expect("Failed to get channel from LoaderProxy").into_zx_channel(); |
| |
| // The linker message only needs a subset of argv and envvars. |
| let args = extract_ld_arguments(&self.msg_contents.args); |
| let environment_vars = |
| extract_ld_environment_variables(&self.msg_contents.environment_vars); |
| |
| let mut linker_msg_contents = processargs::MessageContents { |
| // Argument strings are sent to the linker so that it can use argv[0] in messages it |
| // prints. |
| args, |
| // Environment variables are sent to the linker so that it can see vars like LD_DEBUG. |
| environment_vars, |
| // Process namespace is not set up or used in the linker. |
| namespace_paths: vec![], |
| // Loader message includes a few special handles needed to do its job, plus a set of |
| // handles common to both messages which are generated by this library. |
| handles: vec![ |
| StartupHandle { |
| handle: ldsvc_hnd.into_handle(), |
| info: HandleInfo::new(HandleType::LdsvcLoader, 0), |
| }, |
| StartupHandle { |
| handle: executable.into_handle(), |
| info: HandleInfo::new(HandleType::ExecutableVmo, 0), |
| }, |
| StartupHandle { |
| handle: loaded_vmar.into_handle(), |
| info: HandleInfo::new(HandleType::LoadedVmar, 0), |
| }, |
| ], |
| }; |
| self.common.add_to_message(&mut linker_msg_contents)?; |
| Ok(processargs::Message::build(linker_msg_contents)?) |
| } |
| |
| /// Load the vDSO VMO into the process's address space and a handle to it to the bootstrap |
| /// message. If a vDSO VMO is provided, loads that one, otherwise loads the default system |
| /// vDSO. Returns the base address that the vDSO was mapped into. |
| fn load_vdso(&mut self) -> Result<usize, ProcessBuilderError> { |
| let vdso = match self.non_default_vdso.take() { |
| Some(vmo) => Ok(vmo), |
| None => get_system_vdso_vmo(), |
| }?; |
| let vdso_headers = elf_parse::Elf64Headers::from_vmo(&vdso)?; |
| let loaded_vdso = elf_load::load_elf(&vdso, &self.common.root_vmar, &vdso_headers)?; |
| |
| self.msg_contents.handles.push(StartupHandle { |
| handle: vdso.into_handle(), |
| info: HandleInfo::new(HandleType::VdsoVmo, 0), |
| }); |
| |
| Ok(loaded_vdso.vmar_base) |
| } |
| |
| /// Allocate the initial thread's stack, map it, and add a handle to the bootstrap message. |
| /// Returns the initial stack pointer for the process. |
| /// |
| /// Note that launchpad supported not allocating a stack at all, but that only happened if an |
| /// explicit stack size of 0 is set. ProcessBuilder does not support overriding the stack size |
| /// so a stack is always created. |
| fn create_stack( |
| &mut self, |
| stack_size: usize, |
| vmo_name: &CStr, |
| ) -> Result<usize, ProcessBuilderError> { |
| let stack_vmo = zx::Vmo::create(stack_size as u64).map_err(|s| { |
| ProcessBuilderError::GenericStatus("Failed to create VMO for initial thread stack", s) |
| })?; |
| stack_vmo |
| .set_name(&vmo_name) |
| .map_err(|s| ProcessBuilderError::GenericStatus("Failed to set stack VMO name", s))?; |
| let stack_flags = zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE; |
| let stack_base = |
| self.common.root_vmar.map(0, &stack_vmo, 0, stack_size, stack_flags).map_err(|s| { |
| ProcessBuilderError::GenericStatus("Failed to map initial stack", s) |
| })?; |
| let stack_ptr = compute_initial_stack_pointer(stack_base, stack_size); |
| |
| // Pass the stack VMO to the process. Our protocol with the new process is that we warrant |
| // that this is the VMO from which the initial stack is mapped and that we've exactly |
| // mapped the entire thing, so vm_object_get_size on this in concert with the initial SP |
| // value tells it the exact bounds of its stack. |
| self.msg_contents.handles.push(StartupHandle { |
| handle: stack_vmo.into_handle(), |
| info: HandleInfo::new(HandleType::StackVmo, 0), |
| }); |
| |
| Ok(stack_ptr) |
| } |
| } |
| |
| /// Calculate the size of the initial stack to allocate for the dynamic linker, based on the given |
| /// processargs message contents. |
| /// |
| /// The initial stack is used just for startup work in the dynamic linker and to hold the bootstrap |
| /// message, so we only attempt to make it only as big as needed. The size returned is based on the |
| /// stack space needed to read the bootstrap message with zx_channel_read, and thus includes the |
| /// message data itself plus the size of the handles (i.e. the size of N zx_handle_t's). |
| /// |
| /// This also allows the caller to specify an number of "extra handles" to factor into the size |
| /// calculation. This allows the size to be calculated before all the real handles have been added |
| /// to the contents, for example if the size is needed to create those handles. |
| fn calculate_initial_linker_stack_size( |
| msg_contents: &mut processargs::MessageContents, |
| extra_handles: usize, |
| ) -> Result<usize, ProcessBuilderError> { |
| // Add N placeholder handles temporarily to factor in the size of handles that are not yet |
| // added to the message contents. |
| msg_contents.handles.extend( |
| iter::repeat_with(|| StartupHandle { |
| handle: zx::Handle::invalid(), |
| info: HandleInfo::new(HandleType::User0, 0), |
| }) |
| .take(extra_handles), |
| ); |
| |
| // Include both the message data size and the size of the handles since we're calculating the |
| // stack space required to read the message. |
| let num_handles = msg_contents.handles.len(); |
| let msg_stack_size = processargs::Message::calculate_size(msg_contents)? |
| + num_handles * mem::size_of::<zx::sys::zx_handle_t>(); |
| msg_contents.handles.truncate(num_handles - extra_handles); |
| |
| // PTHREAD_STACK_MIN is defined by the C library in |
| // //zircon/third_party/ulib/musl/include/limits.h. It is tuned enough to cover the dynamic |
| // linker and C library startup code's stack usage (up until the point it switches to its own |
| // stack in __libc_start_main), but leave a little space so for small bootstrap message sizes |
| // the stack needs only one page. |
| const PTHREAD_STACK_MIN: usize = 3072; |
| Ok(util::page_end(msg_stack_size + PTHREAD_STACK_MIN)) |
| } |
| |
| /// Extract only the arguments that are needed for a linker message. |
| fn extract_ld_arguments(arguments: &[CString]) -> Vec<CString> { |
| let mut extracted = vec![]; |
| |
| if let Some(argument) = arguments.get(0) { |
| extracted.push(argument.clone()) |
| } |
| |
| extracted |
| } |
| |
| /// Extract only the environment variables that are needed for a linker message. |
| fn extract_ld_environment_variables(envvars: &[CString]) -> Vec<CString> { |
| let prefixes = ["LD_DEBUG=", "LD_TRACE="]; |
| |
| let mut extracted = vec![]; |
| for envvar in envvars { |
| for prefix in &prefixes { |
| let envvar_bytes: &[u8] = envvar.to_bytes(); |
| let prefix_bytes: &[u8] = prefix.as_bytes(); |
| if envvar_bytes.starts_with(prefix_bytes) { |
| extracted.push(envvar.clone()); |
| continue; |
| } |
| } |
| } |
| |
| extracted |
| } |
| |
| impl CommonMessageHandles { |
| /// Returns a vector of processargs message handles created by this library which are common to |
| /// both the linker and main messages, duplicating handles as needed. |
| fn add_to_message( |
| &self, |
| msg: &mut processargs::MessageContents, |
| ) -> Result<(), ProcessBuilderError> { |
| let handles: &[(zx::HandleRef<'_>, &str, HandleType)] = &[ |
| (self.process.as_handle_ref(), "Failed to dup process handle", HandleType::ProcessSelf), |
| (self.root_vmar.as_handle_ref(), "Failed to dup VMAR handle", HandleType::RootVmar), |
| (self.thread.as_handle_ref(), "Failed to dup thread handle", HandleType::ThreadSelf), |
| ]; |
| |
| for (handle, err_str, handle_type) in handles { |
| let dup = handle |
| .duplicate(zx::Rights::SAME_RIGHTS) |
| .map_err(|s| ProcessBuilderError::GenericStatus(err_str, s))?; |
| msg.handles.push(StartupHandle { handle: dup, info: HandleInfo::new(*handle_type, 0) }); |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Returns an owned VMO handle to the system vDSO ELF image, duplicated from the handle provided |
| /// to this process through its own processargs bootstrap message. |
| fn get_system_vdso_vmo() -> Result<zx::Vmo, ProcessBuilderError> { |
| lazy_static! { |
| static ref VDSO_VMO: zx::Vmo = { |
| zx::Vmo::from( |
| fuchsia_runtime::take_startup_handle(HandleInfo::new(HandleType::VdsoVmo, 0)) |
| .expect("Failed to take VDSO VMO startup handle"), |
| ) |
| }; |
| } |
| |
| let vdso_dup = VDSO_VMO |
| .duplicate_handle(zx::Rights::SAME_RIGHTS) |
| .map_err(|s| ProcessBuilderError::GenericStatus("Failed to dup vDSO VMO handle", s))?; |
| Ok(vdso_dup) |
| } |
| |
| // Copied from //zircon/system/ulib/elf-psabi/include/lib/elf-psabi/sp.h, must be kept in sync with |
| // that. |
| fn compute_initial_stack_pointer(base: usize, size: usize) -> usize { |
| // Assume stack grows down. |
| let mut sp = base.checked_add(size).expect("Overflow in stack pointer calculation"); |
| |
| // The x86-64 and AArch64 ABIs require 16-byte alignment. |
| // The 32-bit ARM ABI only requires 8-byte alignment, but 16-byte alignment is preferable for |
| // NEON so use it there too. |
| sp &= 16usize.wrapping_neg(); |
| |
| // The x86-64 ABI requires %rsp % 16 = 8 on entry. The zero word at (%rsp) serves as the |
| // return address for the outermost frame. |
| #[cfg(target_arch = "x86_64")] |
| { |
| sp -= 8; |
| } |
| |
| // The ARMv7 and ARMv8 ABIs both just require that SP be aligned, so just catch unknown archs. |
| #[cfg(not(any(target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))] |
| { |
| compile_error!("Unknown target_arch"); |
| } |
| |
| sp |
| } |
| |
| /// Load the dynamic linker/loader specified in the PT_INTERP header via the fuchsia.ldsvc.Loader |
| /// handle. |
| async fn get_dynamic_linker<'a>( |
| ldsvc: &'a fldsvc::LoaderProxy, |
| executable: &'a zx::Vmo, |
| interp_hdr: &'a elf_parse::Elf64ProgramHeader, |
| ) -> Result<zx::Vmo, ProcessBuilderError> { |
| // Read the dynamic linker name from the main VMO, based on the PT_INTERP header. |
| let mut interp = vec![0u8; interp_hdr.filesz as usize]; |
| executable |
| .read(&mut interp[..], interp_hdr.offset as u64) |
| .map_err(|s| ProcessBuilderError::GenericStatus("Failed to read from VMO", s))?; |
| // Trim null terminator included in filesz. |
| match interp.pop() { |
| Some(b'\0') => Ok(()), |
| _ => Err(ProcessBuilderError::InvalidInterpHeader(anyhow!("Missing null terminator"))), |
| }?; |
| let interp_str = std::str::from_utf8(&interp) |
| .context("Invalid UTF8") |
| .map_err(ProcessBuilderError::InvalidInterpHeader)?; |
| |
| // Retrieve the dynamic linker as a VMO from fuchsia.ldsvc.Loader |
| const LDSO_LOAD_TIMEOUT_SEC: i64 = 30; |
| let load_fut = ldsvc |
| .load_object(interp_str) |
| .map_err(ProcessBuilderError::LoadDynamicLinker) |
| .on_timeout(fasync::Time::after(LDSO_LOAD_TIMEOUT_SEC.seconds()), || { |
| Err(ProcessBuilderError::LoadDynamicLinkerTimeout()) |
| }); |
| let (status, ld_vmo) = load_fut.await?; |
| zx::Status::ok(status).map_err(|s| { |
| ProcessBuilderError::GenericStatus( |
| "Failed to load dynamic linker from fuchsia.ldsvc.Loader", |
| s, |
| ) |
| })?; |
| Ok(ld_vmo.ok_or(ProcessBuilderError::GenericStatus( |
| "load_object status was OK but no VMO", |
| zx::Status::INTERNAL, |
| ))?) |
| } |
| |
| impl BuiltProcess { |
| /// Start an already built process. |
| /// |
| /// This is a simple wrapper around the [zx_process_start] syscall that consumes the handles |
| /// and data in the BuiltProcess struct as needed. |
| /// |
| /// [zx_process_start]: https://fuchsia.dev/fuchsia-src/reference/syscalls/process_start.md |
| pub fn start(self) -> Result<zx::Process, ProcessBuilderError> { |
| self.process |
| .start( |
| &self.thread, |
| self.entry, |
| self.stack, |
| self.bootstrap.into_handle(), |
| self.vdso_base, |
| ) |
| .map_err(ProcessBuilderError::ProcessStart)?; |
| Ok(self.process) |
| } |
| } |
| |
| struct ReservationVmar(Option<zx::Vmar>); |
| |
| impl ReservationVmar { |
| /// Reserve the lower half of the address space of the given VMAR by allocating another VMAR. |
| /// |
| /// The VMAR wrapped by this reservation is automatically destroyed when the reservation |
| /// is dropped. |
| fn reserve_low_address_space(vmar: &zx::Vmar) -> Result<ReservationVmar, ProcessBuilderError> { |
| let info = vmar |
| .info() |
| .map_err(|s| ProcessBuilderError::GenericStatus("Failed to get VMAR info", s))?; |
| |
| // Reserve the lower half of the full address space, not just half of the VMAR length. |
| // (base+len) represents the full address space, assuming this is used with a root VMAR and |
| // length extends to the end of the address space, including a region the kernel reserves |
| // at the start of the space. |
| let reserve_size = util::page_end((info.base + info.len) / 2) - info.base; |
| let (reserve_vmar, reserve_base) = |
| vmar.allocate(0, reserve_size, zx::VmarFlags::SPECIFIC).map_err(|s| { |
| ProcessBuilderError::GenericStatus("Failed to allocate reservation VMAR", s) |
| })?; |
| assert_eq!(reserve_base, info.base, "Reservation VMAR allocated at wrong address"); |
| |
| Ok(ReservationVmar(Some(reserve_vmar))) |
| } |
| |
| /// Destroy the reservation. The reservation is also automatically destroyed when |
| /// ReservationVmar is dropped. |
| /// |
| /// VMARs are not destroyed when the handle is closed (by dropping), so we must explicit destroy |
| /// it to release the reservation and allow the created process to use the full address space. |
| fn destroy(&mut self) -> Result<(), zx::Status> { |
| match self.0.take() { |
| Some(vmar) => { |
| // This is safe because there are no mappings in the region and it is not a region |
| // in the current process. |
| unsafe { vmar.destroy() } |
| } |
| None => Ok(()), |
| } |
| } |
| } |
| |
| // This is probably unnecessary, but it feels wrong to rely on the side effect of the process's |
| // root VMAR going away. We explicitly call destroy if ProcessBuilder.build() succeeds and returns |
| // a BuiltProcess, in which case this will do nothing, and if build() fails then the new process |
| // and its root VMAR will get cleaned up along with this sub-VMAR. |
| impl Drop for ReservationVmar { |
| fn drop(&mut self) { |
| self.destroy().unwrap_or_else(|e| warn!("Failed to destroy reservation VMAR: {}", e)); |
| } |
| } |
| |
| /// Error type returned by ProcessBuilder methods. |
| #[allow(missing_docs)] // No docs on individual error variants. |
| #[derive(Error, Debug)] |
| pub enum ProcessBuilderError { |
| #[error("{}", _0)] |
| InvalidArg(String), |
| #[error("{}", _0)] |
| BadHandle(&'static str), |
| #[error("Failed to create process: {}", _0)] |
| CreateProcess(zx::Status), |
| #[error("Failed to create thread: {}", _0)] |
| CreateThread(zx::Status), |
| #[error("Failed to start process: {}", _0)] |
| ProcessStart(zx::Status), |
| #[error("Failed to parse ELF: {}", _0)] |
| ElfParse(#[from] elf_parse::ElfParseError), |
| #[error("Failed to load ELF: {}", _0)] |
| ElfLoad(#[from] elf_load::ElfLoadError), |
| #[error("{}", _0)] |
| Processargs(#[from] processargs::ProcessargsError), |
| #[error("{}: {}", _0, _1)] |
| GenericStatus(&'static str, zx::Status), |
| #[error("{}: {}", _0, _1)] |
| Internal(&'static str, #[source] anyhow::Error), |
| #[error("Invalid PT_INTERP header: {}", _0)] |
| InvalidInterpHeader(#[source] anyhow::Error), |
| #[error("Failed to build process with dynamic ELF, missing fuchsia.ldsvc.Loader handle")] |
| LoaderMissing(), |
| #[error("Failed to load dynamic linker from fuchsia.ldsvc.Loader: {}", _0)] |
| LoadDynamicLinker(#[source] fidl::Error), |
| #[error("Timed out loading dynamic linker from fuchsia.ldsvc.Loader")] |
| LoadDynamicLinkerTimeout(), |
| #[error("Failed to write bootstrap message to channel: {}", _0)] |
| WriteBootstrapMessage(zx::Status), |
| #[error("Failed to destroy reservation VMAR: {}", _0)] |
| DestroyReservationVMAR(zx::Status), |
| } |
| |
| impl ProcessBuilderError { |
| /// Returns an appropriate zx::Status code for the given error. |
| pub fn as_zx_status(&self) -> zx::Status { |
| match self { |
| ProcessBuilderError::InvalidArg(_) |
| | ProcessBuilderError::InvalidInterpHeader(_) |
| | ProcessBuilderError::LoaderMissing() => zx::Status::INVALID_ARGS, |
| ProcessBuilderError::BadHandle(_) => zx::Status::BAD_HANDLE, |
| ProcessBuilderError::CreateProcess(s) |
| | ProcessBuilderError::CreateThread(s) |
| | ProcessBuilderError::ProcessStart(s) |
| | ProcessBuilderError::GenericStatus(_, s) |
| | ProcessBuilderError::WriteBootstrapMessage(s) |
| | ProcessBuilderError::DestroyReservationVMAR(s) => *s, |
| ProcessBuilderError::ElfParse(e) => e.as_zx_status(), |
| ProcessBuilderError::ElfLoad(e) => e.as_zx_status(), |
| ProcessBuilderError::Processargs(e) => e.as_zx_status(), |
| ProcessBuilderError::Internal(_, _) => zx::Status::INTERNAL, |
| ProcessBuilderError::LoadDynamicLinker(_) => zx::Status::NOT_FOUND, |
| ProcessBuilderError::LoadDynamicLinkerTimeout() => zx::Status::TIMED_OUT, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| anyhow::Error, |
| fidl::endpoints::{Proxy, ServerEnd, ServiceMarker}, |
| fidl_fuchsia_io as fio, |
| fidl_test_processbuilder::{UtilMarker, UtilProxy}, |
| fuchsia_async as fasync, |
| fuchsia_vfs_pseudo_fs::{ |
| directory::entry::DirectoryEntry, file::simple::read_only, pseudo_directory, |
| }, |
| std::iter, |
| std::mem, |
| zerocopy::LayoutVerified, |
| }; |
| |
| extern "C" { |
| fn dl_clone_loader_service(handle: *mut zx::sys::zx_handle_t) -> zx::sys::zx_status_t; |
| } |
| |
| // Clone the current loader service to provide to the new test processes. |
| fn clone_loader_service() -> Result<ClientEnd<fldsvc::LoaderMarker>, zx::Status> { |
| let mut raw = 0; |
| let status = unsafe { dl_clone_loader_service(&mut raw) }; |
| zx::Status::ok(status)?; |
| |
| let handle = unsafe { zx::Handle::from_raw(raw) }; |
| Ok(ClientEnd::new(zx::Channel::from(handle))) |
| } |
| |
| fn connect_util(client: &zx::Channel) -> Result<UtilProxy, Error> { |
| let (proxy, server) = zx::Channel::create()?; |
| fdio::service_connect_at(&client, UtilMarker::NAME, server) |
| .context("failed to connect to util service")?; |
| Ok(UtilProxy::from_channel(fasync::Channel::from_channel(proxy)?)) |
| } |
| |
| fn create_test_util_builder() -> Result<ProcessBuilder, Error> { |
| const TEST_UTIL_BIN: &'static str = "/pkg/bin/process_builder_test_util"; |
| let file = |
| fdio::open_fd(TEST_UTIL_BIN, fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE)?; |
| let vmo = fdio::get_vmo_exec_from_file(&file)?; |
| let job = fuchsia_runtime::job_default(); |
| |
| let procname = CString::new(TEST_UTIL_BIN.to_owned())?; |
| Ok(ProcessBuilder::new(&procname, &job, vmo)?) |
| } |
| |
| // Common builder setup for all tests that start a test util process. |
| fn setup_test_util_builder(set_loader: bool) -> Result<(ProcessBuilder, UtilProxy), Error> { |
| let mut builder = create_test_util_builder()?; |
| if set_loader { |
| builder.add_handles(vec![StartupHandle { |
| handle: clone_loader_service()?.into_handle(), |
| info: HandleInfo::new(HandleType::LdsvcLoader, 0), |
| }])?; |
| } |
| |
| let (dir_client, dir_server) = zx::Channel::create()?; |
| builder.add_handles(vec![StartupHandle { |
| handle: dir_server.into_handle(), |
| info: HandleInfo::new(HandleType::DirectoryRequest, 0), |
| }])?; |
| |
| let proxy = connect_util(&dir_client)?; |
| Ok((builder, proxy)) |
| } |
| |
| fn check_process_running(process: &zx::Process) -> Result<(), Error> { |
| let info = process.info()?; |
| assert_eq!( |
| info, |
| zx::ProcessInfo { |
| return_code: 0, |
| started: true, |
| exited: false, |
| debugger_attached: false |
| } |
| ); |
| Ok(()) |
| } |
| |
| async fn check_process_exited_ok(process: &zx::Process) -> Result<(), Error> { |
| fasync::OnSignals::new(process, zx::Signals::PROCESS_TERMINATED).await?; |
| |
| let info = process.info()?; |
| assert_eq!( |
| info, |
| zx::ProcessInfo { |
| return_code: 0, |
| started: true, |
| exited: true, |
| debugger_attached: false |
| } |
| ); |
| Ok(()) |
| } |
| |
| // These start_util_with_* tests cover the most common paths through ProcessBuilder and |
| // exercise most of its functionality. They verify that we can create a new process for a |
| // "standard" dynamically linked executable and that we can provide arguments, environment |
| // variables, namespace entries, and other handles to it through the startup processargs |
| // message. The test communicates with the test util process it creates over a test-only FIDL |
| // API to verify that arguments and whatnot were passed correctly. |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_args() -> Result<(), Error> { |
| let test_args = vec!["arg0", "arg1", "arg2"]; |
| let test_args_cstr = |
| test_args.iter().map(|s| CString::new(s.clone())).collect::<Result<_, _>>()?; |
| |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| builder.add_arguments(test_args_cstr); |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // Use the util protocol to confirm that the new process was set up correctly. A successful |
| // connection to the util validates that handles are passed correctly to the new process, |
| // since the DirectoryRequest handle made it. |
| let proc_args = proxy.get_arguments().await.context("failed to get args from util")?; |
| assert_eq!(proc_args, test_args); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_huge_args() -> Result<(), Error> { |
| // This test is partially designed to probe the stack usage of |
| // code processing the initial loader message. Such processing |
| // is on a stack of limited size, a few pages, and well |
| // smaller than a maximally large channel packet. Each |
| // instance of "arg" takes 4 bytes (counting the separating |
| // '\0' byte), so let's send 10k of them to be well larger |
| // than the initial stack but well within the 64k channel size. |
| let test_args = vec!["arg"; 10 * 1000]; |
| let test_args_cstr = |
| test_args.iter().map(|s| CString::new(s.clone())).collect::<Result<_, _>>()?; |
| |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| builder.add_arguments(test_args_cstr); |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // Use the util protocol to confirm that the new process was set up correctly. A successful |
| // connection to the util validates that handles are passed correctly to the new process, |
| // since the DirectoryRequest handle made it. |
| // We can't use get_arguments() here because the FIDL response will be bigger than the |
| // maximum message size[1] and cause the process to crash. Instead, we just check the number |
| // of environment variables and assume that if that's correct we're good to go. |
| // Size of each vector entry: (length = 8, pointer = 8) = 16 + (string size = 8) = 24 |
| // Message size = (10k * vector entry size) = 240,000 > 65,536 |
| let proc_args = |
| proxy.get_argument_count().await.context("failed to get arg count from util")?; |
| |
| assert_eq!(proc_args, test_args.len() as u64); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| // Verify that the lifecycle channel can be passed through the bootstrap |
| // channel. This test checks by creating a channel, passing it through, |
| // asking the remote process for the lifecycle channel's koid, and then |
| // comparing that koid to the one the test recorded. |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_lifecycle_channel() -> Result<(), Error> { |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| let (lifecycle_server, _lifecycle_client) = zx::Channel::create()?; |
| let koid = lifecycle_server |
| .as_handle_ref() |
| .basic_info() |
| .expect("error getting server handle info") |
| .koid |
| .raw_koid(); |
| builder.add_handles(vec![StartupHandle { |
| handle: lifecycle_server.into_handle(), |
| info: HandleInfo::new(HandleType::Lifecycle, 0), |
| }])?; |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // Use the util protocol to confirm that the new process received the |
| // lifecycle channel |
| let reported_koid = |
| proxy.get_lifecycle_koid().await.context("failed getting koid from util")?; |
| assert_eq!(koid, reported_koid); |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| // Verify that if no lifecycle channel is sent via the bootstrap channel |
| // that the remote process reports ZX_KOID_INVALID for the channel koid. |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_no_lifecycle_channel() -> Result<(), Error> { |
| let (builder, proxy) = setup_test_util_builder(true)?; |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // Use the util protocol to confirm that the new process received the |
| // lifecycle channel |
| let reported_koid = |
| proxy.get_lifecycle_koid().await.context("failed getting koid from util")?; |
| assert_eq!(zx::sys::ZX_KOID_INVALID, reported_koid); |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| // Verify that a loader service handle is properly handled if passed directly to |
| // set_loader_service instead of through add_handles. Otherwise this test is identical to |
| // start_util_with_args. |
| #[fasync::run_singlethreaded(test)] |
| async fn set_loader_directly() -> Result<(), Error> { |
| let test_args = vec!["arg0", "arg1", "arg2"]; |
| let test_args_cstr = |
| test_args.iter().map(|s| CString::new(s.clone())).collect::<Result<_, _>>()?; |
| |
| let (mut builder, proxy) = setup_test_util_builder(false)?; |
| builder.set_loader_service(clone_loader_service()?)?; |
| builder.add_arguments(test_args_cstr); |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // Use the util protocol to confirm that the new process was set up correctly. A successful |
| // connection to the util validates that handles are passed correctly to the new process, |
| // since the DirectoryRequest handle made it. |
| let proc_args = proxy.get_arguments().await.context("failed to get args from util")?; |
| assert_eq!(proc_args, test_args); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| // Verify that a vDSO handle is properly handled if passed directly to set_vdso_vmo instead of |
| // relying on the default value. |
| // Note: There isn't a great way to tell here whether the vDSO VMO we passed in was used |
| // instead of the default (because the kernel only allows use of vDSOs that it created for |
| // security, so we can't make a fake vDSO with a different name or something), so that isn't |
| // checked explicitly. The failure tests below make sure we don't ignore the provided vDSO VMO |
| // completely. |
| #[fasync::run_singlethreaded(test)] |
| async fn set_vdso_directly() -> Result<(), Error> { |
| let test_args = vec!["arg0", "arg1", "arg2"]; |
| let test_args_cstr = |
| test_args.iter().map(|s| CString::new(s.clone())).collect::<Result<_, _>>()?; |
| |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| builder.set_vdso_vmo(get_system_vdso_vmo()?); |
| builder.add_arguments(test_args_cstr); |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // Use the util protocol to confirm that the new process was set up correctly. |
| let proc_args = proxy.get_arguments().await.context("failed to get args from util")?; |
| assert_eq!(proc_args, test_args); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| // Verify that a vDSO handle is properly handled if passed directly to set_vdso_vmo instead of |
| // relying on the default value, this time by providing an invalid VMO (something that isn't |
| // ELF and will fail to parse). This also indirectly tests that the reservation VMAR cleanup |
| // happens properly by testing a failure after it has been created. |
| #[fasync::run_singlethreaded(test)] |
| async fn set_invalid_vdso_directly_fails() -> Result<(), Error> { |
| let bad_vdso = zx::Vmo::create(1)?; |
| |
| let (mut builder, _) = setup_test_util_builder(true)?; |
| builder.set_vdso_vmo(bad_vdso); |
| |
| let result = builder.build().await; |
| match result { |
| Err(ProcessBuilderError::ElfParse(ElfParseError::InvalidFileHeader(_))) => {} |
| Err(err) => { |
| panic!("Unexpected error type: {}", err); |
| } |
| Ok(_) => { |
| panic!("Unexpectedly succeeded to build process with invalid vDSO"); |
| } |
| } |
| Ok(()) |
| } |
| |
| // Verify that a vDSO handle is properly handled if passed through add_handles instead of |
| // relying on the default value, this time by providing an invalid VMO (something that isn't |
| // ELF and will fail to parse). This also indirectly tests that the reservation VMAR cleanup |
| // happens properly by testing a failure after it has been created. |
| #[fasync::run_singlethreaded(test)] |
| async fn set_invalid_vdso_fails() -> Result<(), Error> { |
| let bad_vdso = zx::Vmo::create(1)?; |
| |
| let (mut builder, _) = setup_test_util_builder(true)?; |
| builder.add_handles(vec![StartupHandle { |
| handle: bad_vdso.into_handle(), |
| info: HandleInfo::new(HandleType::VdsoVmo, 0), |
| }])?; |
| |
| let result = builder.build().await; |
| match result { |
| Err(ProcessBuilderError::ElfParse(ElfParseError::InvalidFileHeader(_))) => {} |
| Err(err) => { |
| panic!("Unexpected error type: {}", err); |
| } |
| Ok(_) => { |
| panic!("Unexpectedly succeeded to build process with invalid vDSO"); |
| } |
| } |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_env() -> Result<(), Error> { |
| let test_env = vec![("VAR1", "value2"), ("VAR2", "value2")]; |
| let test_env_cstr = test_env |
| .iter() |
| .map(|v| CString::new(format!("{}={}", v.0, v.1))) |
| .collect::<Result<_, _>>()?; |
| |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| builder.add_environment_variables(test_env_cstr); |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| let proc_env = proxy.get_environment().await.context("failed to get env from util")?; |
| let proc_env_tuple: Vec<(&str, &str)> = |
| proc_env.iter().map(|v| (&*v.key, &*v.value)).collect(); |
| assert_eq!(proc_env_tuple, test_env); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_huge_env() -> Result<(), Error> { |
| // This test is partially designed to probe the stack usage of |
| // code processing the initial loader message. Such processing |
| // is on a stack of limited size, a few pages, and well |
| // smaller than a maximally large channel packet. Each |
| // instance of "a=b" takes 4 bytes (counting the separating |
| // '\0' byte), so let's send 10k of them to be well larger |
| // than the initial stack but well within the 64k channel size. |
| let test_env = vec!["a=b"; 10 * 1000]; |
| let test_env_cstr = |
| test_env.iter().map(|s| CString::new(s.clone())).collect::<Result<_, _>>()?; |
| |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| builder.add_environment_variables(test_env_cstr); |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| // We can't use get_environment() here because the FIDL response will be bigger than the |
| // maximum message size and cause the process to crash. Instead, we just check the number |
| // of environment variables and assume that if that's correct we're good to go. |
| let proc_env = |
| proxy.get_environment_count().await.context("failed to get env from util")?; |
| assert_eq!(proc_env, test_env.len() as u64); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_namespace_entries() -> Result<(), Error> { |
| let mut randbuf = [0; 8]; |
| zx::cprng_draw(&mut randbuf)?; |
| let test_content1 = format!("test content 1 {}", u64::from_le_bytes(randbuf)); |
| zx::cprng_draw(&mut randbuf)?; |
| let test_content2 = format!("test content 2 {}", u64::from_le_bytes(randbuf)); |
| |
| let test_content1_bytes = test_content1.clone().into_bytes(); |
| let (dir1_server, dir1_client) = zx::Channel::create()?; |
| fasync::Task::spawn(async move { |
| let mut dir1 = pseudo_directory! { |
| "test_file1" => read_only(|| Ok(test_content1_bytes.clone())), |
| }; |
| dir1.open( |
| fio::OPEN_RIGHT_READABLE, |
| fio::MODE_TYPE_DIRECTORY, |
| &mut iter::empty(), |
| ServerEnd::new(dir1_server), |
| ); |
| dir1.await; |
| panic!("Psuedo dir stopped serving!"); |
| }) |
| .detach(); |
| |
| let test_content2_bytes = test_content2.clone().into_bytes(); |
| let (dir2_server, dir2_client) = zx::Channel::create()?; |
| fasync::Task::spawn(async move { |
| let mut dir2 = pseudo_directory! { |
| "test_file2" => read_only(|| Ok(test_content2_bytes.clone())), |
| }; |
| dir2.open( |
| fio::OPEN_RIGHT_READABLE, |
| fio::MODE_TYPE_DIRECTORY, |
| &mut iter::empty(), |
| ServerEnd::new(dir2_server), |
| ); |
| dir2.await; |
| panic!("Psuedo dir stopped serving!"); |
| }) |
| .detach(); |
| |
| let (mut builder, proxy) = setup_test_util_builder(true)?; |
| builder.add_namespace_entries(vec![ |
| NamespaceEntry { path: CString::new("/dir1")?, directory: ClientEnd::new(dir1_client) }, |
| NamespaceEntry { path: CString::new("/dir2")?, directory: ClientEnd::new(dir2_client) }, |
| ])?; |
| let process = builder.build().await?.start()?; |
| check_process_running(&process)?; |
| |
| let namespace_dump = proxy.dump_namespace().await.context("failed to dump namespace")?; |
| assert_eq!(namespace_dump, "/dir1, /dir1/test_file1, /dir2, /dir2/test_file2"); |
| |
| let dir1_contents = |
| proxy.read_file("/dir1/test_file1").await.context("failed to read file via util")?; |
| assert_eq!(dir1_contents, test_content1); |
| let dir2_contents = |
| proxy.read_file("/dir2/test_file2").await.context("failed to read file via util")?; |
| assert_eq!(dir2_contents, test_content2); |
| |
| mem::drop(proxy); |
| check_process_exited_ok(&process).await?; |
| Ok(()) |
| } |
| |
| // Trying to start a dynamically linked process without providing a loader service should |
| // fail. This verifies that nothing is automatically cloning a loader. |
| #[fasync::run_singlethreaded(test)] |
| async fn start_util_with_no_loader_fails() -> Result<(), Error> { |
| let (builder, _) = setup_test_util_builder(false)?; |
| |
| let result = builder.build().await; |
| match result { |
| Err(ProcessBuilderError::LoaderMissing()) => {} |
| Err(err) => { |
| panic!("Unexpected error type: {}", err); |
| } |
| Ok(_) => { |
| panic!("Unexpectedly succeeded to build process without loader"); |
| } |
| } |
| Ok(()) |
| } |
| |
| // Checks that, for dynamically linked binaries, the lower half of the address space has been |
| // reserved for sanitizers. |
| #[fasync::run_singlethreaded(test)] |
| async fn verify_low_address_range_reserved() -> Result<(), Error> { |
| let (builder, _) = setup_test_util_builder(true)?; |
| let built = builder.build().await?; |
| |
| // This ends up being the same thing ReservationVmar does, but it's not reused here so that |
| // this catches bugs or bad changes to ReservationVmar itself. |
| let info = built.root_vmar.info()?; |
| let lower_half_len = util::page_end((info.base + info.len) / 2) - info.base; |
| built |
| .root_vmar |
| .allocate(0, lower_half_len, zx::VmarFlags::SPECIFIC) |
| .context("Unable to allocate lower address range of new process")?; |
| Ok(()) |
| } |
| |
| // Parses the given channel message as a processargs message and returns the HandleInfo's |
| // contained in it. |
| fn parse_handle_info_from_message(message: &zx::MessageBuf) -> Result<Vec<HandleInfo>, Error> { |
| let bytes = message.bytes(); |
| let header = LayoutVerified::<&[u8], processargs::MessageHeader>::new_from_prefix(bytes) |
| .ok_or(anyhow!("Failed to parse processargs header"))? |
| .0; |
| |
| let offset = header.handle_info_off as usize; |
| let len = mem::size_of::<u32>() * message.n_handles(); |
| let info_bytes = &bytes[offset..offset + len]; |
| let raw_info = LayoutVerified::<&[u8], [u32]>::new_slice(info_bytes) |
| .ok_or(anyhow!("Failed to parse raw handle info"))?; |
| |
| Ok(raw_info.iter().map(|raw| HandleInfo::try_from(*raw)).collect::<Result<_, _>>()?) |
| } |
| |
| const LINKER_MESSAGE_HANDLES: &[HandleType] = &[ |
| HandleType::ProcessSelf, |
| HandleType::ThreadSelf, |
| HandleType::RootVmar, |
| HandleType::LdsvcLoader, |
| HandleType::LoadedVmar, |
| HandleType::ExecutableVmo, |
| ]; |
| |
| const MAIN_MESSAGE_HANDLES: &[HandleType] = &[ |
| HandleType::ProcessSelf, |
| HandleType::ThreadSelf, |
| HandleType::RootVmar, |
| HandleType::VdsoVmo, |
| HandleType::StackVmo, |
| ]; |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn correct_handles_present() -> Result<(), Error> { |
| let mut builder = create_test_util_builder()?; |
| builder.set_loader_service(clone_loader_service()?)?; |
| let built = builder.build().await?; |
| |
| for correct in &[LINKER_MESSAGE_HANDLES, MAIN_MESSAGE_HANDLES] { |
| let mut msg_buf = zx::MessageBuf::new(); |
| built.bootstrap.read(&mut msg_buf)?; |
| let handle_info = parse_handle_info_from_message(&msg_buf)?; |
| |
| assert_eq!(handle_info.len(), correct.len()); |
| for correct_type in *correct { |
| // Should only be one of each of these handles present. |
| assert_eq!( |
| 1, |
| handle_info.iter().filter(|info| &info.handle_type() == correct_type).count() |
| ); |
| } |
| } |
| Ok(()) |
| } |
| |
| // Verify that [ProcessBuilder::add_handles()] rejects handle types that are added |
| // automatically by the builder. |
| #[fasync::run_singlethreaded(test)] |
| async fn add_handles_rejects_automatic_handle_types() -> Result<(), Error> { |
| // The VMO doesn't need to be valid since we're not calling build. |
| let vmo = zx::Vmo::create(1)?; |
| let job = fuchsia_runtime::job_default(); |
| let procname = CString::new("dummy_name")?; |
| let mut builder = ProcessBuilder::new(&procname, &job, vmo)?; |
| |
| // There's some duplicates between these slices but just checking twice is easier than |
| // deduping these. |
| for handle_type in LINKER_MESSAGE_HANDLES.iter().chain(MAIN_MESSAGE_HANDLES) { |
| if *handle_type == HandleType::LdsvcLoader { |
| // Skip LdsvcLoader, which is required in the linker message but is not added |
| // automatically. The user must supply it. |
| continue; |
| } |
| |
| if *handle_type == HandleType::VdsoVmo { |
| // Skip VdsoVmo, which may be supplied by the user. |
| continue; |
| } |
| |
| // Another dummy VMO, just to have a valid handle. |
| let dummy_vmo = zx::Vmo::create(1)?; |
| let result = builder.add_handles(vec![StartupHandle { |
| handle: dummy_vmo.into_handle(), |
| info: HandleInfo::new(*handle_type, 0), |
| }]); |
| match result { |
| Err(ProcessBuilderError::InvalidArg(_)) => {} |
| Err(err) => { |
| panic!("Unexpected error type, should be invalid arg: {}", err); |
| } |
| Ok(_) => { |
| panic!("add_handle unexpectedly succeeded for type {:?}", handle_type); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| // Verify that invalid handles are correctly rejected. |
| #[fasync::run_singlethreaded(test)] |
| async fn rejects_invalid_handles() -> Result<(), Error> { |
| let invalid = || zx::Handle::invalid(); |
| let assert_invalid_arg = |result| match result { |
| Err(ProcessBuilderError::BadHandle(_)) => {} |
| Err(err) => { |
| panic!("Unexpected error type, should be BadHandle: {}", err); |
| } |
| Ok(_) => { |
| panic!("API unexpectedly accepted invalid handle"); |
| } |
| }; |
| |
| // The VMO doesn't need to be valid since we're not calling build with this. |
| let vmo = zx::Vmo::create(1)?; |
| let job = fuchsia_runtime::job_default(); |
| let procname = CString::new("dummy_name")?; |
| |
| assert_invalid_arg(ProcessBuilder::new(&procname, &invalid().into(), vmo).map(|_| ())); |
| assert_invalid_arg(ProcessBuilder::new(&procname, &job, invalid().into()).map(|_| ())); |
| |
| let (mut builder, _) = setup_test_util_builder(true)?; |
| |
| assert_invalid_arg(builder.set_loader_service(invalid().into())); |
| assert_invalid_arg(builder.add_handles(vec![StartupHandle { |
| handle: invalid().into(), |
| info: HandleInfo::new(HandleType::User0, 0), |
| }])); |
| assert_invalid_arg(builder.add_handles(vec![StartupHandle { |
| handle: invalid().into(), |
| info: HandleInfo::new(HandleType::User0, 0), |
| }])); |
| assert_invalid_arg(builder.add_namespace_entries(vec![NamespaceEntry { |
| path: CString::new("/dir")?, |
| directory: invalid().into(), |
| }])); |
| |
| Ok(()) |
| } |
| |
| #[fasync::run_singlethreaded] |
| #[test] |
| async fn start_static_pie_binary() -> Result<(), Error> { |
| const TEST_BIN: &'static str = "/pkg/bin/static_pie_test_util"; |
| let file = fdio::open_fd(TEST_BIN, fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE)?; |
| let vmo = fdio::get_vmo_exec_from_file(&file)?; |
| let job = fuchsia_runtime::job_default(); |
| |
| let procname = CString::new(TEST_BIN.to_owned())?; |
| let mut builder = ProcessBuilder::new(&procname, &job, vmo)?; |
| |
| // We pass the program a channel with handle type User0 which we send a message on and |
| // expect it to echo back the message on the same channel. |
| let (local, remote) = zx::Channel::create()?; |
| builder.add_handles(vec![StartupHandle { |
| handle: remote.into_handle(), |
| info: HandleInfo::new(HandleType::User0, 0), |
| }])?; |
| |
| let mut randbuf = [0; 8]; |
| zx::cprng_draw(&mut randbuf)?; |
| let test_message = format!("test content 1 {}", u64::from_le_bytes(randbuf)).into_bytes(); |
| local.write(&test_message, &mut vec![])?; |
| |
| // Start process and wait for channel to have a message to read or be closed. |
| builder.build().await?.start()?; |
| let signals = fasync::OnSignals::new( |
| &local, |
| zx::Signals::CHANNEL_READABLE | zx::Signals::CHANNEL_PEER_CLOSED, |
| ) |
| .await?; |
| assert!(signals.contains(zx::Signals::CHANNEL_READABLE)); |
| |
| let mut echoed = zx::MessageBuf::new(); |
| local.read(&mut echoed)?; |
| assert_eq!(echoed.bytes(), test_message.as_slice()); |
| assert_eq!(echoed.n_handles(), 0); |
| |
| Ok(()) |
| } |
| } |