summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--9s/Cargo.toml10
-rw-r--r--9s/src/main.rs208
-rw-r--r--9s/src/vsock.rs197
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml1
-rw-r--r--seccomp/aarch64/9s.policy61
-rw-r--r--seccomp/x86_64/9s.policy60
7 files changed, 553 insertions, 0 deletions
diff --git a/9s/Cargo.toml b/9s/Cargo.toml
new file mode 100644
index 0000000..151dbb6
--- /dev/null
+++ b/9s/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "9s"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[dependencies]
+getopts = "=0.2.17"
+libc = "=0.2.40"
+p9 = { path = "../p9" }
+sys_util = { path = "../sys_util" }
diff --git a/9s/src/main.rs b/9s/src/main.rs
new file mode 100644
index 0000000..711e07f
--- /dev/null
+++ b/9s/src/main.rs
@@ -0,0 +1,208 @@
+// Copyright 2018 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 a [9P] server.
+///
+/// [9P]: http://man.cat-v.org/plan_9/5/0intro
+extern crate getopts;
+extern crate libc;
+extern crate p9;
+#[macro_use]
+extern crate sys_util;
+
+mod vsock;
+
+use std::fmt;
+use std::io::{self, BufReader, BufWriter};
+use std::net;
+use std::num::ParseIntError;
+use std::os::raw::c_uint;
+use std::result;
+use std::str::FromStr;
+use std::string;
+use std::sync::Arc;
+use std::thread;
+
+use sys_util::syslog;
+
+use vsock::*;
+
+const DEFAULT_BUFFER_SIZE: usize = 8192;
+
+// Address family identifiers.
+const VSOCK: &'static str = "vsock:";
+const UNIX: &'static str = "unix:";
+
+// Usage for this program.
+const USAGE: &'static str = "9s [options] {vsock:<port>|unix:<path>|<ip>:<port>}";
+
+enum ListenAddress {
+    Net(net::SocketAddr),
+    Unix(String),
+    Vsock(c_uint),
+}
+
+#[derive(Debug)]
+enum ParseAddressError {
+    MissingUnixPath,
+    MissingVsockPort,
+    Net(net::AddrParseError),
+    Unix(string::ParseError),
+    Vsock(ParseIntError),
+}
+
+impl fmt::Display for ParseAddressError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            &ParseAddressError::MissingUnixPath => write!(f, "missing unix path"),
+            &ParseAddressError::MissingVsockPort => write!(f, "missing vsock port number"),
+            &ParseAddressError::Net(ref e) => e.fmt(f),
+            &ParseAddressError::Unix(ref e) => write!(f, "invalid unix path: {}", e),
+            &ParseAddressError::Vsock(ref e) => write!(f, "invalid vsock port number: {}", e),
+        }
+    }
+}
+
+impl FromStr for ListenAddress {
+    type Err = ParseAddressError;
+
+    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+        if s.starts_with(VSOCK) {
+            if s.len() > VSOCK.len() {
+                Ok(ListenAddress::Vsock(s[VSOCK.len()..]
+                    .parse()
+                    .map_err(ParseAddressError::Vsock)?))
+            } else {
+                Err(ParseAddressError::MissingVsockPort)
+            }
+        } else if s.starts_with(UNIX) {
+            if s.len() > UNIX.len() {
+                Ok(ListenAddress::Unix(s[UNIX.len()..]
+                    .parse()
+                    .map_err(ParseAddressError::Unix)?))
+            } else {
+                Err(ParseAddressError::MissingUnixPath)
+            }
+        } else {
+            Ok(ListenAddress::Net(
+                s.parse().map_err(ParseAddressError::Net)?
+            ))
+        }
+    }
+}
+
+#[derive(Debug)]
+enum Error {
+    Address(ParseAddressError),
+    Argument(getopts::Fail),
+    Cid(ParseIntError),
+    IO(io::Error),
+    MissingAcceptCid,
+    Syslog(syslog::Error),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            &Error::Address(ref e) => e.fmt(f),
+            &Error::Argument(ref e) => e.fmt(f),
+            &Error::Cid(ref e) => write!(f, "invalid cid value: {}", e),
+            &Error::IO(ref e) => e.fmt(f),
+            &Error::MissingAcceptCid => write!(f, "`accept_cid` is required for vsock servers"),
+            &Error::Syslog(ref e) => write!(f, "failed to initialize syslog: {:?}", e),
+        }
+    }
+}
+
+type Result<T> = result::Result<T, Error>;
+
+fn handle_client<R: io::Read, W: io::Write>(
+    root: Arc<str>,
+    mut reader: R,
+    mut writer: W,
+) -> io::Result<()> {
+    let mut server = p9::Server::new(&*root);
+
+    loop {
+        server.handle_message(&mut reader, &mut writer)?;
+    }
+}
+
+fn run_vsock_server(root: Arc<str>, port: c_uint, accept_cid: c_uint) -> io::Result<()> {
+    let listener = VsockListener::bind(port)?;
+
+    loop {
+        let (stream, peer) = listener.accept()?;
+
+        if accept_cid != peer.cid {
+            warn!("ignoring connection from {}:{}", peer.cid, peer.port);
+            continue;
+        }
+
+        info!("accepted connection from {}:{}", peer.cid, peer.port);
+        let reader = BufReader::with_capacity(DEFAULT_BUFFER_SIZE, stream.try_clone()?);
+        let writer = BufWriter::with_capacity(DEFAULT_BUFFER_SIZE, stream);
+        let server_root = root.clone();
+        thread::spawn(move || {
+            if let Err(e) = handle_client(server_root, reader, writer) {
+                error!(
+                    "error while handling client {}:{}: {}",
+                    peer.cid, peer.port, e
+                );
+            }
+        });
+    }
+}
+
+fn main() -> Result<()> {
+    let mut opts = getopts::Options::new();
+    opts.optopt(
+        "",
+        "accept_cid",
+        "only accept connections from this vsock context id",
+        "CID",
+    );
+    opts.optopt(
+        "r",
+        "root",
+        "root directory for clients (default is \"/\")",
+        "PATH",
+    );
+    opts.optflag("h", "help", "print this help menu");
+
+    let matches = opts.parse(std::env::args_os().skip(1))
+        .map_err(Error::Argument)?;
+
+    if matches.opt_present("h") || matches.free.len() == 0 {
+        print!("{}", opts.usage(USAGE));
+        return Ok(());
+    }
+
+    syslog::init().map_err(Error::Syslog)?;
+
+    let root: Arc<str> = Arc::from(matches.opt_str("r").unwrap_or_else(|| "/".into()));
+
+    // We already checked that |matches.free| has at least one item.
+    match matches.free[0]
+        .parse::<ListenAddress>()
+        .map_err(Error::Address)?
+    {
+        ListenAddress::Vsock(port) => {
+            let accept_cid = if let Some(cid) = matches.opt_str("accept_cid") {
+                cid.parse::<c_uint>().map_err(Error::Cid)
+            } else {
+                Err(Error::MissingAcceptCid)
+            }?;
+            run_vsock_server(root, port, accept_cid).map_err(Error::IO)?;
+        }
+        ListenAddress::Net(_) => {
+            error!("Network server unimplemented");
+        }
+        ListenAddress::Unix(_) => {
+            error!("Unix server unimplemented");
+        }
+    }
+
+    Ok(())
+}
diff --git a/9s/src/vsock.rs b/9s/src/vsock.rs
new file mode 100644
index 0000000..7957860
--- /dev/null
+++ b/9s/src/vsock.rs
@@ -0,0 +1,197 @@
+// Copyright 2018 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.
+
+/// Support for virtual sockets.
+use std::io;
+use std::mem::{self, size_of};
+use std::os::raw::{c_int, c_uchar, c_uint, c_ushort};
+use std::os::unix::io::RawFd;
+
+use libc::{self, c_void, sa_family_t, size_t, sockaddr, socklen_t};
+
+// The domain for vsock sockets.
+const AF_VSOCK: sa_family_t = 40;
+
+// Vsock equivalent of INADDR_ANY.  Indicates the context id of the current endpoint.
+const VMADDR_CID_ANY: c_uint = c_uint::max_value();
+
+// The number of bytes of padding to be added to the sockaddr_vm struct.  Taken directly
+// from linux/vm_sockets.h.
+const PADDING: usize = size_of::<sockaddr>()
+    - size_of::<sa_family_t>()
+    - size_of::<c_ushort>()
+    - (2 * size_of::<c_uint>());
+
+#[repr(C)]
+struct sockaddr_vm {
+    svm_family: sa_family_t,
+    svm_reserved1: c_ushort,
+    svm_port: c_uint,
+    svm_cid: c_uint,
+    svm_zero: [c_uchar; PADDING],
+}
+
+/// An address associated with a virtual socket.
+pub struct SocketAddr {
+    pub cid: c_uint,
+    pub port: c_uint,
+}
+
+/// A virtual stream socket.
+pub struct VsockStream {
+    fd: RawFd,
+}
+
+impl VsockStream {
+    pub fn try_clone(&self) -> io::Result<VsockStream> {
+        // Safe because this doesn't modify any memory and we check the return value.
+        let dup_fd = unsafe { libc::fcntl(self.fd, libc::F_DUPFD_CLOEXEC, 0) };
+        if dup_fd < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(VsockStream { fd: dup_fd })
+    }
+}
+
+impl io::Read for VsockStream {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        // Safe because this will only modify the contents of |buf| and we check the return value.
+        let ret = unsafe {
+            handle_eintr_errno!(libc::read(
+                self.fd,
+                buf as *mut [u8] as *mut c_void,
+                buf.len() as size_t
+            ))
+        };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(ret as usize)
+    }
+}
+
+impl io::Write for VsockStream {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        // Safe because this doesn't modify any memory and we check the return value.
+        let ret = unsafe {
+            handle_eintr_errno!(libc::write(
+                self.fd,
+                buf as *const [u8] as *const c_void,
+                buf.len() as size_t,
+            ))
+        };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(ret as usize)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        // No buffered data so nothing to do.
+        Ok(())
+    }
+}
+
+impl Drop for VsockStream {
+    fn drop(&mut self) {
+        // Safe because this doesn't modify any memory and we are the only
+        // owner of the file descriptor.
+        unsafe { libc::close(self.fd) };
+    }
+}
+
+/// Represents a virtual socket server.
+pub struct VsockListener {
+    fd: RawFd,
+}
+
+impl VsockListener {
+    /// Creates a new `VsockListener` bound to the specified port on the current virtual socket
+    /// endpoint.
+    pub fn bind(port: c_uint) -> io::Result<VsockListener> {
+        // The compiler should optimize this out since these are both compile-time constants.
+        assert_eq!(size_of::<sockaddr_vm>(), size_of::<sockaddr>());
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let fd: RawFd =
+            unsafe { libc::socket(AF_VSOCK as c_int, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
+        if fd < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        // Safe because we are zero-initializing a struct with only integer fields.
+        let mut svm: sockaddr_vm = unsafe { mem::zeroed() };
+        svm.svm_family = AF_VSOCK;
+        svm.svm_cid = VMADDR_CID_ANY;
+        svm.svm_port = port;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let ret = unsafe {
+            libc::bind(
+                fd,
+                &svm as *const sockaddr_vm as *const sockaddr,
+                size_of::<sockaddr_vm>() as socklen_t,
+            )
+        };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let ret = unsafe { libc::listen(fd, 1) };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(VsockListener { fd: fd })
+    }
+
+    /// Accepts a new incoming connection on this listener.  Blocks the calling thread until a
+    /// new connection is established.  When established, returns the corresponding `VsockStream`
+    /// and the remote peer's address.
+    pub fn accept(&self) -> io::Result<(VsockStream, SocketAddr)> {
+        // Safe because we are zero-initializing a struct with only integer fields.
+        let mut svm: sockaddr_vm = unsafe { mem::zeroed() };
+
+        // Safe because this will only modify |svm| and we check the return value.
+        let mut socklen: socklen_t = size_of::<sockaddr_vm>() as socklen_t;
+        let fd = unsafe {
+            libc::accept4(
+                self.fd,
+                &mut svm as *mut sockaddr_vm as *mut sockaddr,
+                &mut socklen as *mut socklen_t,
+                libc::SOCK_CLOEXEC,
+            )
+        };
+        if fd < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        if svm.svm_family != AF_VSOCK {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                format!("unexpected address family: {}", svm.svm_family),
+            ));
+        }
+
+        Ok((
+            VsockStream { fd: fd },
+            SocketAddr {
+                cid: svm.svm_cid,
+                port: svm.svm_port,
+            },
+        ))
+    }
+}
+
+impl Drop for VsockListener {
+    fn drop(&mut self) {
+        // Safe because this doesn't modify any memory and we are the only
+        // owner of the file descriptor.
+        unsafe { libc::close(self.fd) };
+    }
+}
diff --git a/Cargo.lock b/Cargo.lock
index 73d1eb4..b8d822b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,4 +1,14 @@
 [[package]]
+name = "9s"
+version = "0.1.0"
+dependencies = [
+ "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "p9 0.1.0",
+ "sys_util 0.1.0",
+]
+
+[[package]]
 name = "aarch64"
 version = "0.1.0"
 dependencies = [
@@ -145,6 +155,11 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "getopts"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "gpu_buffer"
 version = "0.1.0"
 dependencies = [
@@ -437,6 +452,7 @@ dependencies = [
 "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05"
 "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
 "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
 "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
diff --git a/Cargo.toml b/Cargo.toml
index c3a5c2e..edeb1bb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ lto = true
 panic = 'abort'
 
 [workspace]
+members = ["9s"]
 
 [features]
 plugin = ["plugin_proto", "crosvm_plugin", "protobuf"]
diff --git a/seccomp/aarch64/9s.policy b/seccomp/aarch64/9s.policy
new file mode 100644
index 0000000..37325d3
--- /dev/null
+++ b/seccomp/aarch64/9s.policy
@@ -0,0 +1,61 @@
+# Copyright 2018 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.
+
+read: 1
+write: 1
+stat64: 1
+open: 1
+close: 1
+fstat64: 1
+lstat64: 1
+getdents64: 1
+ioctl: arg1 == FIOCLEX
+pread64: 1
+pwrite64: 1
+# Disallow mmap with PROT_EXEC set.  The syntax here doesn't allow bit
+# negation, thus the manually negated mask constant.
+mmap2: arg2 in 0xfffffffb
+mprotect: arg2 in 0xfffffffb
+rt_sigaction: 1
+sigaltstack: 1
+munmap: 1
+utimensat: 1
+brk: 1
+uname: 1
+accept4: 1
+mkdir: 1
+sched_getaffinity: 1
+getpid: 1
+ugetrlimit: 1
+set_robust_list: 1
+fcntl64: 1
+socket: arg0 == AF_UNIX || arg0 == AF_VSOCK
+gettimeofday: 1
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+rename: 1
+ftruncate64: 1
+connect: 1
+madvise: 1
+rt_sigprocmask: 1
+access: 1
+ARM_set_tls: 1
+_llseek: 1
+exit: 1
+fdatasync: 1
+set_tid_address: 1
+listen: 1
+# Disallow clone's other than new threads.
+clone: arg0 & 0x00010000
+statfs64: 1
+link: 1
+unlink: 1
+fsync: 1
+futex: 1
+bind: 1
+rmdir: 1
+# Calling fchown with -1 as the uid/gid will change the ctime but do nothing else.
+fchown: arg1 == 0xffffffff && arg2 == 0xffffffff
+mremap: 1
diff --git a/seccomp/x86_64/9s.policy b/seccomp/x86_64/9s.policy
new file mode 100644
index 0000000..75cf032
--- /dev/null
+++ b/seccomp/x86_64/9s.policy
@@ -0,0 +1,60 @@
+# Copyright 2018 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.
+
+read: 1
+write: 1
+lstat: 1
+stat: 1
+open: 1
+close: 1
+fstat: 1
+getdents: 1
+ioctl: arg1 == FIOCLEX
+pwrite64: 1
+pread64: 1
+# Disallow mmap with PROT_EXEC set.  The syntax here doesn't allow bit
+# negation, thus the manually negated mask constant.
+mmap: arg2 in 0xfffffffb
+mprotect: arg2 in 0xfffffffb
+utimensat: 1
+rt_sigaction: 1
+statfs: 1
+sigaltstack: 1
+munmap: 1
+brk: 1
+accept4: 1
+sched_getaffinity: 1
+getpid: 1
+getrlimit: 1
+fcntl: 1
+set_robust_list: 1
+link: 1
+socket: arg0 == AF_UNIX || arg0 == AF_VSOCK
+restart_syscall: 1
+exit_group: 1
+rt_sigreturn: 1
+lseek: 1
+uname: 1
+connect: 1
+rt_sigprocmask: 1
+arch_prctl: 1
+access: 1
+exit: 1
+set_tid_address: 1
+listen: 1
+# Disallow clone's other than new threads.
+clone: arg0 & 0x00010000
+unlink: 1
+madvise: 1
+futex: 1
+bind: 1
+rmdir: 1
+# Calling fchown with -1 as the uid/gid will change the ctime but do nothing else.
+fchown: arg1 == 0xffffffff && arg2 == 0xffffffff
+fsync: 1
+fdatasync: 1
+ftruncate: 1
+mkdir: 1
+mremap: 1
+rename: 1