From 54ab55f99cd593f71a9b572d23199101a48206ec Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Fri, 22 May 2020 00:34:09 +0000 Subject: crosvm: add memfd server --- servers/Cargo.toml | 11 ++++ servers/src/jailed.rs | 142 +++++++++++++++++++++++++++++++++++++++++ servers/src/lib.rs | 32 ++++++++++ servers/src/memfd.rs | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 356 insertions(+) create mode 100644 servers/Cargo.toml create mode 100644 servers/src/jailed.rs create mode 100644 servers/src/lib.rs create mode 100644 servers/src/memfd.rs (limited to 'servers') diff --git a/servers/Cargo.toml b/servers/Cargo.toml new file mode 100644 index 0000000..c42c57f --- /dev/null +++ b/servers/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "servers" +version = "0.1.0" +authors = ["Alyssa Ross"] +edition = "2018" + +[dependencies] +io_jail = { path = "../io_jail" } +libc = "*" +msg_socket = { path = "../msg_socket" } +sys_util = { path = "../sys_util" } diff --git a/servers/src/jailed.rs b/servers/src/jailed.rs new file mode 100644 index 0000000..c633efb --- /dev/null +++ b/servers/src/jailed.rs @@ -0,0 +1,142 @@ +// Copyright 2020 Alyssa Ross. All rights reserved. +// 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. + +use std::fmt::{self, Display, Formatter}; +use std::io; +use std::os::unix::prelude::*; +use std::time::Duration; + +use io_jail::Minijail; +use libc; +use msg_socket::{MsgError, MsgOnSocket, MsgReceiver, MsgSender, MsgSocket}; +use sys_util::net::UnixSeqpacket; +use sys_util::{error, warn}; + +use crate::Server; + +#[derive(Debug)] +pub enum Error { + ForkingJail(io_jail::Error), + Io(io::Error), +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use Error::*; + match self { + ForkingJail(e) => write!(f, "failed to fork jail process: {}", e), + Io(e) => write!(f, "IO error configuring proxy server: {}", e), + } + } +} + +impl std::error::Error for Error {} + +const SOCKET_TIMEOUT_MS: u64 = 2000; + +#[derive(Copy, Clone, Debug, Eq, MsgOnSocket, PartialEq)] +enum Event { + Activate, + Shutdown, +} + +#[derive(Debug)] +pub struct JailedServer { + sock: MsgSocket, + server: S, +} + +fn recv_ack(sock: &MsgSocket<(), Event>) -> Result { + let event = sock.recv()?; + sock.send(&())?; + Ok(event) +} + +fn child_proc(sock: UnixSeqpacket, server: &mut S) -> Result<(), MsgError> { + let sock = MsgSocket::new(sock); + + if recv_ack(&sock)? == Event::Activate { + server.activate(); + + while recv_ack(&sock)? == Event::Activate { + warn!("server already activated"); + } + } + + eprintln!("Received shutdown"); + + // Event::Shutdown received. Time to stop. + Ok(()) +} + +impl JailedServer { + pub fn jail(mut server: S, jail: &Minijail, mut keep_fds: Vec) -> Result { + let (child_sock, parent_sock) = UnixSeqpacket::pair().map_err(Error::Io)?; + + keep_fds.push(child_sock.as_raw_fd()); + let pid = unsafe { jail.fork(Some(&keep_fds)) }.map_err(Error::ForkingJail)?; + + if pid == 0 { + server.on_sandboxed(); + if let Err(e) = child_proc(child_sock, &mut server) { + error!("child server process: {}", e); + } + + // 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 (or when corresponding code for devices has been removed). + // + // exit() is trivially safe. + // ! Never returns + unsafe { libc::exit(0) }; + } + + 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(Self { + server, + sock: MsgSocket::new(parent_sock), + }) + } +} + +impl JailedServer { + fn sync_send(&self, event: &Event) { + if let Err(e) = self.sock.send(event) { + error!("failed to send message to server: {}", e); + } + + if let Err(e) = self.sock.recv() { + error!("failed to receive ack from server: {}", e); + } + } +} + +impl Drop for JailedServer { + fn drop(&mut self) { + self.sync_send(&Event::Shutdown); + } +} + +impl Server for JailedServer { + fn keep_fds(&self) -> Vec { + panic!("Don't double jail servers!"); + } + + fn on_sandboxed(&mut self) { + panic!("Don't double jail servers!"); + } + + fn activate(&mut self) { + self.sync_send(&Event::Activate); + } +} diff --git a/servers/src/lib.rs b/servers/src/lib.rs new file mode 100644 index 0000000..5c174f9 --- /dev/null +++ b/servers/src/lib.rs @@ -0,0 +1,32 @@ +// Copyright 2020 Alyssa Ross. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +mod jailed; +mod memfd; + +use std::os::unix::prelude::*; + +pub use jailed::Error as ProxyError; +pub use jailed::JailedServer; +pub use memfd::MemfdServer; + +pub trait Server { + fn keep_fds(&self) -> Vec; + fn on_sandboxed(&mut self) {} + fn activate(&mut self); +} + +impl Server for Box { + fn keep_fds(&self) -> Vec { + (**self).keep_fds() + } + + fn on_sandboxed(&mut self) { + (**self).on_sandboxed() + } + + fn activate(&mut self) { + (**self).activate() + } +} diff --git a/servers/src/memfd.rs b/servers/src/memfd.rs new file mode 100644 index 0000000..ea6e148 --- /dev/null +++ b/servers/src/memfd.rs @@ -0,0 +1,171 @@ +// Copyright 2020 Alyssa Ross. All rights reserved. +// 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. + +use std::convert::TryInto; +use std::error::Error; +use std::io::prelude::*; +use std::io::IoSlice; +use std::net::Shutdown; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::os::unix::prelude::*; +use std::thread::{self, JoinHandle}; + +use sys_util::{error, EventFd, MemfdSeals, PollContext, PollToken, ScmSocket, SharedMemory}; + +use crate::Server; + +fn create_memfd(name: [u8; 224], size: u64) -> Result> { + let mut full_name: Vec = b"crosvm-guest-memfd-".to_vec(); + full_name.extend( + name.iter() + .map(|x| *x) + .take_while(|b| *b != 0 && *b != b'/'), + ); + + let size: usize = size.try_into()?; + + let mut seals = MemfdSeals::new(); + seals.set_grow_seal(); + seals.set_shrink_seal(); + seals.set_seal_seal(); + + let mut memfd = SharedMemory::named(full_name)?; + memfd.set_size(size as u64)?; + memfd.add_seals(seals)?; + + Ok(memfd) +} + +fn do_request(mut conn: UnixStream) -> Result<(), Box> { + let mut name = [0; 224]; + conn.read_exact(&mut name)?; + + let mut size = [0; 8]; + conn.read_exact(&mut size)?; + let size = u64::from_le_bytes(size); + + let _ = conn.shutdown(Shutdown::Read); + + match create_memfd(name, size) { + Ok(memfd) => conn.send_with_fd(&[IoSlice::new(&[0x00])], memfd.as_raw_fd())?, + Err(_) => conn.write(&[0x01])?, + }; + + Ok(()) +} + +fn run(wl_socket: UnixListener, kill_evt: EventFd) { + #[derive(Debug, PollToken)] + enum Token { + Socket, + Kill, + } + + let poll_ctx = + match PollContext::build_with(&[(&wl_socket, Token::Socket), (&kill_evt, Token::Kill)]) { + Ok(pc) => pc, + Err(e) => { + error!("failed creating PollContext: {}", e); + return; + } + }; + + 'poll: loop { + let events = match poll_ctx.wait() { + Ok(v) => v, + Err(e) => { + error!("failed polling for events: {}", e); + break; + } + }; + + for event in &events { + match dbg!(event.token()) { + Token::Socket => { + let conn = match wl_socket.accept() { + Ok((conn, _)) => conn, + Err(e) => { + error!("Failed to accept memfd connection: {}", e); + break 'poll; + } + }; + + if let Err(e) = do_request(conn) { + error!("Failed to service memfd request: {}", e); + break 'poll; + } + } + + Token::Kill => break 'poll, + } + } + } +} + +#[derive(Debug)] +pub struct MemfdServer { + kill_evt: Option, + worker_thread: Option>, + wl_socket: Option, +} + +impl MemfdServer { + pub fn new(wl_socket: UnixListener) -> Self { + Self { + kill_evt: None, + worker_thread: None, + wl_socket: Some(wl_socket), + } + } +} + +impl Drop for MemfdServer { + fn drop(&mut self) { + if let Some(kill_evt) = self.kill_evt.take() { + // Ignore the result because there is nothing we can do about it. + let _ = kill_evt.write(1); + } + + if let Some(worker_thread) = self.worker_thread.take() { + let _ = worker_thread.join(); + } + } +} + +impl Server for MemfdServer { + fn keep_fds(&self) -> Vec { + self.wl_socket.iter().map(AsRawFd::as_raw_fd).collect() + } + + fn activate(&mut self) { + let wl_socket = match self.wl_socket.take() { + Some(wl) => wl, + None => return, + }; + + let (self_kill_evt, kill_evt) = match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + error!("failed creating kill EventFd pair: {}", e); + return; + } + }; + self.kill_evt = Some(self_kill_evt); + + let worker_result = thread::Builder::new() + .name("memfd-server".to_string()) + .spawn(move || run(wl_socket, kill_evt)); + + match worker_result { + Err(e) => { + error!("failed to spawn memfd server worker: {}", e); + return; + } + Ok(join_handle) => { + self.worker_thread = Some(join_handle); + } + } + } +} -- cgit 1.4.1