diff options
-rw-r--r-- | 9s/Cargo.toml | 10 | ||||
-rw-r--r-- | 9s/src/main.rs | 208 | ||||
-rw-r--r-- | 9s/src/vsock.rs | 197 | ||||
-rw-r--r-- | Cargo.lock | 16 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | seccomp/aarch64/9s.policy | 61 | ||||
-rw-r--r-- | seccomp/x86_64/9s.policy | 60 |
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 |