// 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 super::Error as DeviceError; use super::{ ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, VirtioCommon, VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1, }; use crate::seccomp_filters::Thread; use crate::thread_helper::spawn_virtio_thread; use crate::GuestMemoryMmap; use crate::{VirtioInterrupt, VirtioInterruptType}; use anyhow::anyhow; use seccompiler::SeccompAction; use std::fs::File; use std::io; use std::os::unix::io::AsRawFd; use std::result; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier}; use thiserror::Error; use versionize::{VersionMap, Versionize, VersionizeResult}; use versionize_derive::Versionize; use virtio_queue::{Queue, QueueT}; use vm_memory::{GuestAddressSpace, GuestMemory, GuestMemoryAtomic}; use vm_migration::VersionMapped; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_virtio::{AccessPlatform, Translatable}; use vmm_sys_util::eventfd::EventFd; const QUEUE_SIZE: u16 = 256; const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE]; // New descriptors are pending on the virtio queue. const QUEUE_AVAIL_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1; #[derive(Error, Debug)] enum Error { #[error("Descriptor chain too short")] DescriptorChainTooShort, #[error("Invalid descriptor")] InvalidDescriptor, #[error("Failed to write to guest memory: {0}")] GuestMemoryWrite(vm_memory::guest_memory::Error), #[error("Failed adding used index: {0}")] QueueAddUsed(virtio_queue::Error), } struct RngEpollHandler { mem: GuestMemoryAtomic, queue: Queue, random_file: File, interrupt_cb: Arc, queue_evt: EventFd, kill_evt: EventFd, pause_evt: EventFd, access_platform: Option>, } impl RngEpollHandler { fn process_queue(&mut self) -> result::Result { let queue = &mut self.queue; let mut used_descs = false; while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) { let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?; // The descriptor must be write-only and non-zero length if !(desc.is_write_only() && desc.len() > 0) { return Err(Error::InvalidDescriptor); } // Fill the read with data from the random device on the host. let len = desc_chain .memory() .read_volatile_from( desc.addr() .translate_gva(self.access_platform.as_ref(), desc.len() as usize), &mut self.random_file, desc.len() as usize, ) .map_err(Error::GuestMemoryWrite)?; queue .add_used(desc_chain.memory(), desc_chain.head_index(), len as u32) .map_err(Error::QueueAddUsed)?; used_descs = true; } Ok(used_descs) } fn signal_used_queue(&self) -> result::Result<(), DeviceError> { self.interrupt_cb .trigger(VirtioInterruptType::Queue(0)) .map_err(|e| { error!("Failed to signal used queue: {:?}", e); DeviceError::FailedSignalingUsedQueue(e) }) } fn run( &mut self, paused: Arc, paused_sync: Arc, ) -> result::Result<(), EpollHelperError> { let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?; helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?; helper.run(paused, paused_sync, self)?; Ok(()) } } impl EpollHelperHandler for RngEpollHandler { fn handle_event( &mut self, _helper: &mut EpollHelper, event: &epoll::Event, ) -> result::Result<(), EpollHelperError> { let ev_type = event.data as u16; match ev_type { QUEUE_AVAIL_EVENT => { self.queue_evt.read().map_err(|e| { EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e)) })?; let needs_notification = self.process_queue().map_err(|e| { EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {:?}", e)) })?; if needs_notification { self.signal_used_queue().map_err(|e| { EpollHelperError::HandleEvent(anyhow!( "Failed to signal used queue: {:?}", e )) })?; } } _ => { return Err(EpollHelperError::HandleEvent(anyhow!( "Unexpected event: {}", ev_type ))); } } Ok(()) } } /// Virtio device for exposing entropy to the guest OS through virtio. pub struct Rng { common: VirtioCommon, id: String, random_file: Option, seccomp_action: SeccompAction, exit_evt: EventFd, } #[derive(Versionize)] pub struct RngState { pub avail_features: u64, pub acked_features: u64, } impl VersionMapped for RngState {} impl Rng { /// Create a new virtio rng device that gets random data from /dev/urandom. pub fn new( id: String, path: &str, iommu: bool, seccomp_action: SeccompAction, exit_evt: EventFd, state: Option, ) -> io::Result { let random_file = File::open(path)?; let (avail_features, acked_features, paused) = if let Some(state) = state { info!("Restoring virtio-rng {}", id); (state.avail_features, state.acked_features, true) } else { let mut avail_features = 1u64 << VIRTIO_F_VERSION_1; if iommu { avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM; } (avail_features, 0, false) }; Ok(Rng { common: VirtioCommon { device_type: VirtioDeviceType::Rng as u32, queue_sizes: QUEUE_SIZES.to_vec(), paused_sync: Some(Arc::new(Barrier::new(2))), avail_features, acked_features, min_queues: 1, paused: Arc::new(AtomicBool::new(paused)), ..Default::default() }, id, random_file: Some(random_file), seccomp_action, exit_evt, }) } fn state(&self) -> RngState { RngState { avail_features: self.common.avail_features, acked_features: self.common.acked_features, } } #[cfg(fuzzing)] pub fn wait_for_epoll_threads(&mut self) { self.common.wait_for_epoll_threads(); } } impl Drop for Rng { fn drop(&mut self) { if let Some(kill_evt) = self.common.kill_evt.take() { // Ignore the result because there is nothing we can do about it. let _ = kill_evt.write(1); } self.common.wait_for_epoll_threads(); } } impl VirtioDevice for Rng { fn device_type(&self) -> u32 { self.common.device_type } fn queue_max_sizes(&self) -> &[u16] { &self.common.queue_sizes } fn features(&self) -> u64 { self.common.avail_features } fn ack_features(&mut self, value: u64) { self.common.ack_features(value) } fn activate( &mut self, mem: GuestMemoryAtomic, interrupt_cb: Arc, mut queues: Vec<(usize, Queue, EventFd)>, ) -> ActivateResult { self.common.activate(&queues, &interrupt_cb)?; let (kill_evt, pause_evt) = self.common.dup_eventfds(); if let Some(file) = self.random_file.as_ref() { let random_file = file.try_clone().map_err(|e| { error!("failed cloning rng source: {}", e); ActivateError::BadActivate })?; let (_, queue, queue_evt) = queues.remove(0); let mut handler = RngEpollHandler { mem, queue, random_file, interrupt_cb, queue_evt, kill_evt, pause_evt, access_platform: self.common.access_platform.clone(), }; let paused = self.common.paused.clone(); let paused_sync = self.common.paused_sync.clone(); let mut epoll_threads = Vec::new(); spawn_virtio_thread( &self.id, &self.seccomp_action, Thread::VirtioRng, &mut epoll_threads, &self.exit_evt, move || handler.run(paused, paused_sync.unwrap()), )?; self.common.epoll_threads = Some(epoll_threads); event!("virtio-device", "activated", "id", &self.id); return Ok(()); } Err(ActivateError::BadActivate) } fn reset(&mut self) -> Option> { let result = self.common.reset(); event!("virtio-device", "reset", "id", &self.id); result } fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform) } } impl Pausable for Rng { fn pause(&mut self) -> result::Result<(), MigratableError> { self.common.pause() } fn resume(&mut self) -> result::Result<(), MigratableError> { self.common.resume() } } impl Snapshottable for Rng { fn id(&self) -> String { self.id.clone() } fn snapshot(&mut self) -> std::result::Result { Snapshot::new_from_versioned_state(&self.state()) } } impl Transportable for Rng {} impl Migratable for Rng {}