blob: 383eee45065beddf71c2b64340dbe2b80d161671 [file] [log] [blame]
// 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::time::Duration;
use std::{self, io};
use base::{error, net::UnixSeqpacket, AsRawDescriptor, RawDescriptor};
use libc::{self, pid_t};
use minijail::{self, Minijail};
use msg_socket::{MsgOnSocket, MsgReceiver, MsgSender, MsgSocket};
use crate::{BusAccessInfo, BusDevice};
/// Errors for proxy devices.
#[derive(Debug)]
pub enum Error {
ForkingJail(minijail::Error),
Io(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
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;
#[derive(Debug, MsgOnSocket)]
enum Command {
Read {
len: u32,
info: BusAccessInfo,
},
Write {
len: u32,
info: BusAccessInfo,
data: [u8; 8],
},
ReadConfig(u32),
WriteConfig {
reg_idx: u32,
offset: u32,
len: u32,
data: [u8; 4],
},
Shutdown,
}
#[derive(MsgOnSocket)]
enum CommandResult {
Ok,
ReadResult([u8; 8]),
ReadConfigResult(u32),
}
fn child_proc<D: BusDevice>(sock: UnixSeqpacket, device: &mut D) {
let mut running = true;
let sock = MsgSocket::<CommandResult, Command>::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, info } => {
let mut buffer = [0u8; 8];
device.read(info, &mut buffer[0..len as usize]);
sock.send(&CommandResult::ReadResult(buffer))
}
Command::Write { len, info, data } => {
let len = len as usize;
device.write(info, &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);
}
}
}
/// Wraps an inner `BusDevice` that is run inside a child process via fork.
///
/// Because forks are very unfriendly to destructors and all memory mappings and file descriptors
/// are inherited, this should be used as early as possible in the main process.
pub struct ProxyDevice {
sock: MsgSocket<Command, CommandResult>,
pid: pid_t,
debug_label: String,
}
impl ProxyDevice {
/// Takes the given device and isolates it into another process via fork before returning.
///
/// The forked process will automatically be terminated when this is dropped, so be sure to keep
/// a reference.
///
/// # Arguments
/// * `device` - The device to isolate to another process.
/// * `jail` - The jail to use for isolating the given device.
/// * `keep_rds` - File descriptors that will be kept open in the child.
pub fn new<D: BusDevice>(
mut device: D,
jail: &Minijail,
mut keep_rds: Vec<RawDescriptor>,
) -> Result<ProxyDevice> {
let debug_label = device.debug_label();
let (child_sock, parent_sock) = UnixSeqpacket::pair().map_err(Error::Io)?;
keep_rds.push(child_sock.as_raw_descriptor());
// Forking here is safe as long as the program is still single threaded.
let pid = unsafe {
match jail.fork(Some(&keep_rds)).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(ProxyDevice {
sock: MsgSocket::<Command, CommandResult>::new(parent_sock),
pid,
debug_label,
})
}
pub fn pid(&self) -> pid_t {
self.pid
}
/// Send a command that does not expect a response from the child device process.
fn send_no_result(&self, cmd: &Command) {
let res = self.sock.send(cmd);
if let Err(e) = res {
error!(
"failed write to child device process {}: {}",
self.debug_label, e,
);
}
}
/// Send a command and read its response from the child device process.
fn sync_send(&self, cmd: &Command) -> Option<CommandResult> {
self.send_no_result(cmd);
match self.sock.recv() {
Err(e) => {
error!(
"failed to read result of {:?} from child device process {}: {}",
cmd, self.debug_label, e,
);
None
}
Ok(r) => Some(r),
}
}
}
impl BusDevice for ProxyDevice {
fn debug_label(&self) -> String {
self.debug_label.clone()
}
fn config_register_write(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
let len = data.len() as u32;
let mut buffer = [0u8; 4];
buffer[0..data.len()].clone_from_slice(data);
let reg_idx = reg_idx as u32;
let offset = offset as u32;
self.send_no_result(&Command::WriteConfig {
reg_idx,
offset,
len,
data: buffer,
});
}
fn config_register_read(&self, reg_idx: usize) -> u32 {
let res = self.sync_send(&Command::ReadConfig(reg_idx as u32));
if let Some(CommandResult::ReadConfigResult(val)) = res {
val
} else {
0
}
}
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
let len = data.len() as u32;
if let Some(CommandResult::ReadResult(buffer)) =
self.sync_send(&Command::Read { len, info })
{
let len = data.len();
data.clone_from_slice(&buffer[0..len]);
}
}
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
let mut buffer = [0u8; 8];
let len = data.len() as u32;
buffer[0..data.len()].clone_from_slice(data);
self.send_no_result(&Command::Write {
len,
info,
data: buffer,
});
}
}
impl Drop for ProxyDevice {
fn drop(&mut self) {
self.sync_send(&Command::Shutdown);
}
}
/// Note: These tests must be run with --test-threads=1 to allow minijail to fork
/// the process.
#[cfg(test)]
mod tests {
use super::*;
/// A simple test echo device that outputs the same u8 that was written to it.
struct EchoDevice {
data: u8,
config: u8,
}
impl EchoDevice {
fn new() -> EchoDevice {
EchoDevice { data: 0, config: 0 }
}
}
impl BusDevice for EchoDevice {
fn debug_label(&self) -> String {
"EchoDevice".to_owned()
}
fn write(&mut self, _info: BusAccessInfo, data: &[u8]) {
assert!(data.len() == 1);
self.data = data[0];
}
fn read(&mut self, _info: BusAccessInfo, data: &mut [u8]) {
assert!(data.len() == 1);
data[0] = self.data;
}
fn config_register_write(&mut self, _reg_idx: usize, _offset: u64, data: &[u8]) {
assert!(data.len() == 1);
self.config = data[0];
}
fn config_register_read(&self, _reg_idx: usize) -> u32 {
self.config as u32
}
}
fn new_proxied_echo_device() -> ProxyDevice {
let device = EchoDevice::new();
let keep_fds: Vec<RawDescriptor> = Vec::new();
let minijail = Minijail::new().unwrap();
ProxyDevice::new(device, &minijail, keep_fds).unwrap()
}
// TODO(b/173833661): Find a way to ensure these tests are run single-threaded.
#[test]
#[ignore]
fn test_debug_label() {
let proxy_device = new_proxied_echo_device();
assert_eq!(proxy_device.debug_label(), "EchoDevice");
}
#[test]
#[ignore]
fn test_proxied_read_write() {
let mut proxy_device = new_proxied_echo_device();
let address = BusAccessInfo {
offset: 0,
address: 0,
id: 0,
};
proxy_device.write(address, &[42]);
let mut read_buffer = [0];
proxy_device.read(address, &mut read_buffer);
assert_eq!(read_buffer, [42]);
}
#[test]
#[ignore]
fn test_proxied_config() {
let mut proxy_device = new_proxied_echo_device();
proxy_device.config_register_write(0, 0, &[42]);
assert_eq!(proxy_device.config_register_read(0), 42);
}
}