mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-28 17:44:10 +00:00
crosvm: Add virtio random
Provide the guest OS with /dev/random. Change-Id: I1323836392f3f1d59a6be276ce495e0d78ea9669 Signed-off-by: Dylan Reid <dgreid@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/603531 Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
parent
d579e3cd49
commit
c28f6a67b1
5 changed files with 231 additions and 0 deletions
19
rng_device.policy
Normal file
19
rng_device.policy
Normal file
|
@ -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
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
187
src/hw/virtio/rng.rs
Normal file
187
src/hw/virtio/rng.rs
Normal file
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
20
src/main.rs
20
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)?;
|
||||
|
|
Loading…
Reference in a new issue