summary refs log blame commit diff
path: root/devices/src/jailed.rs
blob: d3fa2cf176f9afaef729548702090b38f2a75443 (plain) (tree)


























































































                                                                                             




































































                                                                                                





                         



                                                                                                 

                                                                                                    




                                                           



























                                                                                   
// 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<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;

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, 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<Command, CommandResult>,
    debug_label: String,
}

impl JailInfo {
    pub fn pid(&self) -> pid_t {
        self.pid
    }

    pub fn sock(&self) -> &MsgSocket<Command, CommandResult> {
        &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<D: BusDevice>(
    mut device: D,
    jail: &Minijail,
    mut keep_fds: Vec<RawFd>,
) -> Result<JailInfo> {
    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::<Command, CommandResult>::new(parent_sock),
        pid,
        debug_label,
    })
}

pub struct JailedDevice {
    proxy: ProxyDevice,
    pid: pid_t,
}

impl JailedDevice {
    pub fn jail<D: BusDevice>(device: D, jail: &Minijail, keep_fds: Vec<RawFd>) -> Result<Self> {
        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)
    }
}