// Copyright 2017 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Runs hardware devices in child processes. use std::fmt::{self, Display}; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; use std::time::Duration; use io_jail::Minijail; use libc::pid_t; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; use sys_util::{error, net::UnixSeqpacket}; use crate::proxy::*; use crate::BusDevice; /// Errors for proxy devices. #[derive(Debug)] pub enum Error { ForkingJail(io_jail::Error), Io(io::Error), } pub type Result = std::result::Result; impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; match self { ForkingJail(e) => write!(f, "Failed to fork jail process: {}", e), Io(e) => write!(f, "IO error configuring proxy device {}.", e), } } } const SOCKET_TIMEOUT_MS: u64 = 2000; fn child_proc(sock: UnixSeqpacket, device: &mut D) { let mut running = true; let sock = MsgSocket::::new(sock); while running { let cmd = match sock.recv() { Ok(cmd) => cmd, Err(err) => { error!("child device process failed recv: {}", err); break; } }; let res = match cmd { Command::Read { len, offset } => { let mut buffer = [0u8; 8]; device.read(offset, &mut buffer[0..len as usize]); sock.send(&CommandResult::ReadResult(buffer)) } Command::Write { len, offset, data } => { let len = len as usize; device.write(offset, &data[0..len]); // Command::Write does not have a result. Ok(()) } Command::ReadConfig(idx) => { let val = device.config_register_read(idx as usize); sock.send(&CommandResult::ReadConfigResult(val)) } Command::WriteConfig { reg_idx, offset, len, data, } => { let len = len as usize; device.config_register_write(reg_idx as usize, offset as u64, &data[0..len]); // Command::WriteConfig does not have a result. Ok(()) } Command::Shutdown => { running = false; sock.send(&CommandResult::Ok) } }; if let Err(e) = res { error!("child device process failed send: {}", e); } } } pub struct JailInfo { pid: pid_t, sock: MsgSocket, debug_label: String, } impl JailInfo { pub fn pid(&self) -> pid_t { self.pid } pub fn sock(&self) -> &MsgSocket { &self.sock } pub fn debug_label(&self) -> &str { self.debug_label.as_ref() } } /// Takes the given device and isolates it into another process via fork before returning. /// /// # Arguments /// * `device` - The device to isolate to another process. /// * `jail` - The jail to use for isolating the given device. /// * `keep_fds` - File descriptors that will be kept open in the child. pub fn jail_device( mut device: D, jail: &Minijail, mut keep_fds: Vec, ) -> Result { let debug_label = device.debug_label(); let (child_sock, parent_sock) = UnixSeqpacket::pair().map_err(Error::Io)?; keep_fds.push(child_sock.as_raw_fd()); // Forking here is safe as long as the program is still single threaded. let pid = unsafe { match jail.fork(Some(&keep_fds)).map_err(Error::ForkingJail)? { 0 => { device.on_sandboxed(); child_proc(child_sock, &mut device); // We're explicitly not using std::process::exit here to avoid the cleanup of // stdout/stderr globals. This can cause cascading panics and SIGILL if a worker // thread attempts to log to stderr after at_exit handlers have been run. // TODO(crbug.com/992494): Remove this once device shutdown ordering is clearly // defined. // // exit() is trivially safe. // ! Never returns libc::exit(0); } p => p, } }; parent_sock .set_write_timeout(Some(Duration::from_millis(SOCKET_TIMEOUT_MS))) .map_err(Error::Io)?; parent_sock .set_read_timeout(Some(Duration::from_millis(SOCKET_TIMEOUT_MS))) .map_err(Error::Io)?; Ok(JailInfo { sock: MsgSocket::::new(parent_sock), pid, debug_label, }) } pub struct JailedDevice { proxy: ProxyDevice, pid: pid_t, } impl JailedDevice { pub fn jail(device: D, jail: &Minijail, keep_fds: Vec) -> Result { jail_device(device, jail, keep_fds).map(Self::new) } /// The forked process will automatically be terminated when this is dropped, so be sure to keep /// a reference. pub fn new(j: JailInfo) -> JailedDevice { Self { proxy: ProxyDevice::new(j.sock, j.debug_label), pid: j.pid, } } pub fn pid(&self) -> pid_t { self.pid } } impl BusDevice for JailedDevice { fn debug_label(&self) -> String { self.proxy.debug_label() } fn config_register_write(&mut self, reg_idx: usize, offset: u64, data: &[u8]) { self.proxy.config_register_write(reg_idx, offset, data) } fn config_register_read(&self, reg_idx: usize) -> u32 { self.proxy.config_register_read(reg_idx) } fn read(&mut self, offset: u64, data: &mut [u8]) { self.proxy.read(offset, data) } fn write(&mut self, offset: u64, data: &[u8]) { self.proxy.write(offset, data) } }