From c28f6a67b16d2bb8e113bfd09ec8238069fa14c0 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 4 Aug 2017 00:00:14 -0700 Subject: [PATCH] crosvm: Add virtio random Provide the guest OS with /dev/random. Change-Id: I1323836392f3f1d59a6be276ce495e0d78ea9669 Signed-off-by: Dylan Reid Reviewed-on: https://chromium-review.googlesource.com/603531 Reviewed-by: Zach Reizner --- rng_device.policy | 19 +++++ src/hw/virtio/mod.rs | 4 + src/hw/virtio/queue.rs | 1 + src/hw/virtio/rng.rs | 187 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 20 +++++ 5 files changed, 231 insertions(+) create mode 100644 rng_device.policy create mode 100644 src/hw/virtio/rng.rs diff --git a/rng_device.policy b/rng_device.policy new file mode 100644 index 0000000000..f270509d67 --- /dev/null +++ b/rng_device.policy @@ -0,0 +1,19 @@ +close: 1 +exit_group: 1 +futex: 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 +munmap: 1 +read: 1 +recvfrom: 1 +sched_getaffinity: 1 +set_robust_list: 1 +sigaltstack: 1 +# Disallow clone's other than new threads. +clone: arg0 & 0x00010000 +write: 1 +eventfd2: 1 +dup: 1 +poll: 1 diff --git a/src/hw/virtio/mod.rs b/src/hw/virtio/mod.rs index e2cd4a2094..cd1b6b73b6 100644 --- a/src/hw/virtio/mod.rs +++ b/src/hw/virtio/mod.rs @@ -7,11 +7,13 @@ mod queue; mod mmio; mod block; +mod rng; mod vhost_net; pub use self::queue::*; pub use self::mmio::*; pub use self::block::*; +pub use self::rng::*; pub use self::vhost_net::*; const DEVICE_ACKNOWLEDGE: u32 = 0x01; @@ -20,8 +22,10 @@ const DEVICE_DRIVER_OK: u32 = 0x04; const DEVICE_FEATURES_OK: u32 = 0x08; const DEVICE_FAILED: u32 = 0x80; +// Types taken from linux/virtio_ids.h const TYPE_NET: u32 = 1; const TYPE_BLOCK: u32 = 2; +const TYPE_RNG: u32 = 4; const INTERRUPT_STATUS_USED_RING: u32 = 0x1; diff --git a/src/hw/virtio/queue.rs b/src/hw/virtio/queue.rs index 894b9b9281..b2884270e8 100644 --- a/src/hw/virtio/queue.rs +++ b/src/hw/virtio/queue.rs @@ -96,6 +96,7 @@ impl<'a> DescriptorChain<'a> { /// If the driver designated this as a write only descriptor. /// /// If this is false, this descriptor is read only. + /// Write only means the the emulated device can write and the driver can read. pub fn is_write_only(&self) -> bool { self.flags & VIRTQ_DESC_F_WRITE != 0 } diff --git a/src/hw/virtio/rng.rs b/src/hw/virtio/rng.rs new file mode 100644 index 0000000000..c735b9a2ee --- /dev/null +++ b/src/hw/virtio/rng.rs @@ -0,0 +1,187 @@ +// 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; +use std::fs::File; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread::spawn; + +use sys_util::{EventFd, GuestMemory, Poller}; + +use super::{VirtioDevice, Queue, INTERRUPT_STATUS_USED_RING, TYPE_RNG}; + +const QUEUE_SIZE: u16 = 256; +const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE]; + +#[derive(Debug)] +pub enum RngError { + /// Can't access /dev/random + AccessingRandomDev(io::Error), +} +pub type Result = std::result::Result; + +struct Worker { + queue: Queue, + mem: GuestMemory, + random_file: File, + interrupt_status: Arc, + interrupt_evt: EventFd, +} + +impl Worker { + fn process_queue(&mut self) -> bool { + let queue = &mut self.queue; + + let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize]; + let mut used_count = 0; + for avail_desc in queue.iter(&self.mem) { + let mut len = 0; + + // Drivers can only read from the random device. + if avail_desc.is_write_only() { + // Fill the read with data from the random device on the host. + if self.mem.read_to_memory(avail_desc.addr, + &mut self.random_file, + avail_desc.len as usize) + .is_ok() { + len = avail_desc.len; + } + } + + used_desc_heads[used_count] = (avail_desc.index, len); + used_count += 1; + } + + for &(desc_index, len) in &used_desc_heads[..used_count] { + queue.add_used(&self.mem, desc_index, len); + } + used_count > 0 + } + + fn signal_used_queue(&self) { + self.interrupt_status + .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst); + self.interrupt_evt.write(1).unwrap(); + } + + fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) { + const Q_AVAIL: u32 = 0; + const KILL: u32 = 1; + + let mut poller = Poller::new(2); + 'poll: loop { + let tokens = match poller.poll(&[(Q_AVAIL, &queue_evt), (KILL, &kill_evt)]) { + Ok(v) => v, + Err(e) => { + println!("rng: error polling for events: {:?}", e); + break; + } + }; + + let mut needs_interrupt = false; + for &token in tokens { + match token { + Q_AVAIL => { + if let Err(e) = queue_evt.read() { + println!("rng: error reading queue EventFd: {:?}", e); + break 'poll; + } + needs_interrupt |= self.process_queue(); + } + KILL => break 'poll, + _ => unreachable!(), + } + } + if needs_interrupt { + self.signal_used_queue(); + } + } + } +} + +/// Virtio device for exposing entropy to the guest OS through virtio. +pub struct Rng { + kill_evt: Option, + random_file: Option, +} + +impl Rng { + /// Create a new virtio rng device that gets random data from /dev/random. + pub fn new() -> Result { + let random_file = File::open("/dev/random") + .map_err(RngError::AccessingRandomDev)?; + Ok(Rng { + kill_evt: None, + random_file: Some(random_file), + }) + } +} + +impl Drop for Rng { + 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); + } + } +} + +impl VirtioDevice for Rng { + fn keep_fds(&self) -> Vec { + let mut keep_fds = Vec::new(); + + if let Some(ref random_file) = self.random_file { + keep_fds.push(random_file.as_raw_fd()); + } + + keep_fds + } + + fn device_type(&self) -> u32 { + TYPE_RNG + } + + fn queue_max_sizes(&self) -> &[u16] { + QUEUE_SIZES + } + + fn activate(&mut self, + mem: GuestMemory, + interrupt_evt: EventFd, + status: Arc, + mut queues: Vec, + mut queue_evts: Vec) { + if queues.len() != 1 || queue_evts.len() != 1 { + return; + } + + let (self_kill_evt, kill_evt) = + match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) { + Ok(v) => v, + Err(e) => { + println!("rng: error creating kill EventFd pair: {:?}", e); + return; + } + }; + self.kill_evt = Some(self_kill_evt); + + let queue = queues.remove(0); + + if let Some(random_file) = self.random_file.take() { + spawn(move || { + let mut worker = Worker { + queue: queue, + mem: mem, + random_file: random_file, + interrupt_status: status, + interrupt_evt: interrupt_evt, + }; + worker.run(queue_evts.remove(0), kill_evt); + }); + } + } +} diff --git a/src/main.rs b/src/main.rs index 19f02b2ac2..64497925f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,6 +62,9 @@ enum Error { Cmdline(kernel_cmdline::Error), RegisterIoevent(sys_util::Error), RegisterIrqfd(sys_util::Error), + RegisterRng(device_manager::Error), + RngDeviceNew(hw::virtio::RngError), + RngDeviceRootSetup(sys_util::Error), KernelLoader(kernel_loader::Error), ConfigureSystem(x86_64::Error), EventFd(sys_util::Error), @@ -109,6 +112,11 @@ impl fmt::Display for Error { &Error::DeviceJail(ref e) => write!(f, "failed to jail device: {:?}", e), &Error::DevicePivotRoot(ref e) => write!(f, "failed to pivot root device: {:?}", e), &Error::RegisterNet(ref e) => write!(f, "error registering net device: {:?}", e), + &Error::RegisterRng(ref e) => write!(f, "error registering rng device: {:?}", e), + &Error::RngDeviceNew(ref e) => write!(f, "failed to set up rng: {:?}", e), + &Error::RngDeviceRootSetup(ref e) => { + write!(f, "failed to create root directory for a rng device: {:?}", e) + } &Error::Cmdline(ref e) => write!(f, "the given kernel command line was invalid: {}", e), &Error::RegisterIoevent(ref e) => write!(f, "error registering ioevent: {:?}", e), &Error::RegisterIrqfd(ref e) => write!(f, "error registering irqfd: {:?}", e), @@ -261,6 +269,18 @@ fn run_config(cfg: Config) -> Result<()> { .map_err(Error::RegisterBlock)?; } + let rng_root = TempDir::new(&PathBuf::from("/tmp/rng_root")) + .map_err(Error::RngDeviceRootSetup)?; + let rng_box = Box::new(hw::virtio::Rng::new().map_err(Error::RngDeviceNew)?); + let rng_jail = if cfg.multiprocess { + let rng_root_path = rng_root.as_path().unwrap(); // Won't fail if new succeeded. + Some(create_base_minijail(rng_root_path, Path::new("rng_device.policy"))?) + } else { + None + }; + device_manager.register_mmio(rng_box, rng_jail, &mut cmdline) + .map_err(Error::RegisterRng)?; + // We checked above that if the IP is defined, then the netmask is, too. let net_root = TempDir::new(&PathBuf::from("/tmp/net_root")) .map_err(Error::NetDeviceRootSetup)?;