// Copyright 2017 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. use std::collections::BTreeMap; use std::io::Write; use anyhow::anyhow; use anyhow::Context; use base::error; use base::warn; use base::Event; use base::EventToken; use base::RawDescriptor; use base::WaitContext; use base::WorkerThread; use rand::rngs::OsRng; use rand::RngCore; use vm_memory::GuestMemory; use super::DeviceType; use super::Interrupt; use super::Queue; use super::VirtioDevice; const QUEUE_SIZE: u16 = 256; const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE]; // Chosen to match the Linux guest driver RNG buffer refill size. const CHUNK_SIZE: usize = 64; struct Worker { interrupt: Interrupt, queue: Queue, } impl Worker { fn process_queue(&mut self) { let mut rand_bytes = [0u8; CHUNK_SIZE]; let mut needs_interrupt = false; while let Some(mut avail_desc) = self.queue.pop() { let writer = &mut avail_desc.writer; while writer.available_bytes() > 0 { let chunk_size = writer.available_bytes().min(CHUNK_SIZE); let chunk = &mut rand_bytes[..chunk_size]; OsRng.fill_bytes(chunk); if let Err(e) = writer.write_all(chunk) { warn!("Failed to write random data to the guest: {}", e); break; } } let written_size = writer.bytes_written(); self.queue.add_used(avail_desc, written_size as u32); needs_interrupt = true; } if needs_interrupt { self.queue.trigger_interrupt(); } } fn run(&mut self, kill_evt: Event) -> anyhow::Result<()> { #[derive(EventToken)] enum Token { QueueAvailable, InterruptResample, Kill, } let wait_ctx = WaitContext::build_with(&[ (self.queue.event(), Token::QueueAvailable), (&kill_evt, Token::Kill), ]) .context("failed creating WaitContext")?; if let Some(resample_evt) = self.interrupt.get_resample_evt() { wait_ctx .add(resample_evt, Token::InterruptResample) .context("failed adding resample event to WaitContext.")?; } let mut exiting = false; while !exiting { let events = wait_ctx.wait().context("failed polling for events")?; for event in events.iter().filter(|e| e.is_readable) { match event.token { Token::QueueAvailable => { self.queue .event() .wait() .context("failed reading queue Event")?; self.process_queue(); } Token::InterruptResample => { self.interrupt.interrupt_resample(); } Token::Kill => exiting = true, } } } Ok(()) } } /// Virtio device for exposing entropy to the guest OS through virtio. pub struct Rng { worker_thread: Option>, virtio_features: u64, } impl Rng { /// Create a new virtio rng device that gets random data from /dev/urandom. pub fn new(virtio_features: u64) -> anyhow::Result { Ok(Rng { worker_thread: None, virtio_features, }) } } impl VirtioDevice for Rng { fn keep_rds(&self) -> Vec { Vec::new() } fn device_type(&self) -> DeviceType { DeviceType::Rng } fn queue_max_sizes(&self) -> &[u16] { QUEUE_SIZES } fn features(&self) -> u64 { self.virtio_features } fn activate( &mut self, _mem: GuestMemory, interrupt: Interrupt, mut queues: BTreeMap, ) -> anyhow::Result<()> { if queues.len() != 1 { return Err(anyhow!("expected 1 queue, got {}", queues.len())); } let queue = queues.remove(&0).unwrap(); self.worker_thread = Some(WorkerThread::start("v_rng", move |kill_evt| { let mut worker = Worker { interrupt, queue }; if let Err(e) = worker.run(kill_evt) { error!("rng worker thread failed: {:#}", e); } worker })); Ok(()) } fn reset(&mut self) -> anyhow::Result<()> { if let Some(worker_thread) = self.worker_thread.take() { let _worker = worker_thread.stop(); } Ok(()) } fn virtio_sleep(&mut self) -> anyhow::Result>> { if let Some(worker_thread) = self.worker_thread.take() { let worker = worker_thread.stop(); return Ok(Some(BTreeMap::from([(0, worker.queue)]))); } Ok(None) } fn virtio_wake( &mut self, queues_state: Option<(GuestMemory, Interrupt, BTreeMap)>, ) -> anyhow::Result<()> { if let Some((mem, interrupt, queues)) = queues_state { self.activate(mem, interrupt, queues)?; } Ok(()) } fn virtio_snapshot(&mut self) -> anyhow::Result { // `virtio_sleep` ensures there is no pending state, except for the `Queue`s, which are // handled at a higher layer. Ok(serde_json::Value::Null) } fn virtio_restore(&mut self, data: serde_json::Value) -> anyhow::Result<()> { anyhow::ensure!( data == serde_json::Value::Null, "unexpected snapshot data: should be null, got {}", data, ); Ok(()) } }