summary refs log tree commit diff
path: root/servers
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2020-05-22 00:34:09 +0000
committerAlyssa Ross <hi@alyssa.is>2020-06-15 09:37:34 +0000
commit54ab55f99cd593f71a9b572d23199101a48206ec (patch)
tree633e6f29a69e27db3947fa2b8a857a9ccc2ae638 /servers
parente0e035c065fdfbbea1e734d137d87a2635dbd0fa (diff)
downloadcrosvm-54ab55f99cd593f71a9b572d23199101a48206ec.tar
crosvm-54ab55f99cd593f71a9b572d23199101a48206ec.tar.gz
crosvm-54ab55f99cd593f71a9b572d23199101a48206ec.tar.bz2
crosvm-54ab55f99cd593f71a9b572d23199101a48206ec.tar.lz
crosvm-54ab55f99cd593f71a9b572d23199101a48206ec.tar.xz
crosvm-54ab55f99cd593f71a9b572d23199101a48206ec.tar.zst
crosvm-54ab55f99cd593f71a9b572d23199101a48206ec.zip
crosvm: add memfd server
Diffstat (limited to 'servers')
-rw-r--r--servers/Cargo.toml11
-rw-r--r--servers/src/jailed.rs142
-rw-r--r--servers/src/lib.rs32
-rw-r--r--servers/src/memfd.rs171
4 files changed, 356 insertions, 0 deletions
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<S> {
+    sock: MsgSocket<Event, ()>,
+    server: S,
+}
+
+fn recv_ack(sock: &MsgSocket<(), Event>) -> Result<Event, MsgError> {
+    let event = sock.recv()?;
+    sock.send(&())?;
+    Ok(event)
+}
+
+fn child_proc<S: Server>(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<S: Server> JailedServer<S> {
+    pub fn jail(mut server: S, jail: &Minijail, mut keep_fds: Vec<RawFd>) -> Result<Self, Error> {
+        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<S> JailedServer<S> {
+    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<S> Drop for JailedServer<S> {
+    fn drop(&mut self) {
+        self.sync_send(&Event::Shutdown);
+    }
+}
+
+impl<S> Server for JailedServer<S> {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        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<RawFd>;
+    fn on_sandboxed(&mut self) {}
+    fn activate(&mut self);
+}
+
+impl<T: Server + ?Sized> Server for Box<T> {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        (**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<SharedMemory, Box<dyn Error>> {
+    let mut full_name: Vec<u8> = 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<dyn Error>> {
+    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<EventFd>,
+    worker_thread: Option<JoinHandle<()>>,
+    wl_socket: Option<UnixListener>,
+}
+
+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<RawFd> {
+        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);
+            }
+        }
+    }
+}