summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--rng_device.policy19
-rw-r--r--src/hw/virtio/mod.rs4
-rw-r--r--src/hw/virtio/queue.rs1
-rw-r--r--src/hw/virtio/rng.rs187
-rw-r--r--src/main.rs20
5 files changed, 231 insertions, 0 deletions
diff --git a/rng_device.policy b/rng_device.policy
new file mode 100644
index 0000000..f270509
--- /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 e2cd4a2..cd1b6b7 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 894b9b9..b288427 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 0000000..c735b9a
--- /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<T> = std::result::Result<T, RngError>;
+
+struct Worker {
+    queue: Queue,
+    mem: GuestMemory,
+    random_file: File,
+    interrupt_status: Arc<AtomicUsize>,
+    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<EventFd>,
+    random_file: Option<File>,
+}
+
+impl Rng {
+    /// Create a new virtio rng device that gets random data from /dev/random.
+    pub fn new() -> Result<Rng> {
+        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<RawFd> {
+        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<AtomicUsize>,
+                mut queues: Vec<Queue>,
+                mut queue_evts: Vec<EventFd>) {
+        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 19f02b2..6449792 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)?;