// 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. // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::fs::File; use std::os::unix::io::AsRawFd; use std::sync::atomic::AtomicBool; use std::sync::{Arc, Barrier}; use std::{io, result}; use anyhow::anyhow; use event_monitor::event; use log::{error, info, warn}; use seccompiler::SeccompAction; use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_queue::{Queue, QueueT}; use vm_memory::{Bytes, GuestAddressSpace, GuestMemoryAtomic}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_virtio::AccessPlatform; use vm_virtio::checked_descriptor::DescriptorChainExt; use vmm_sys_util::eventfd::EventFd; use super::{ ActivateError, ActivateResult, EPOLL_HELPER_EVENT_LAST, EpollHelper, EpollHelperError, EpollHelperHandler, Error as DeviceError, VIRTIO_F_ACCESS_PLATFORM, VIRTIO_F_VERSION_1, VirtioCommon, VirtioDevice, VirtioDeviceType, }; use crate::device::ActivationContext; use crate::seccomp_filters::Thread; use crate::{GuestMemoryMmap, VirtioInterrupt, VirtioInterruptType}; 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("Failed adding used index")] QueueAddUsed(#[source] 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 mut total_len: usize = 0; // Validate the entire descriptor chain upfront so we never // partially fill buffers for a chain that turns out to be // invalid. let descs: Vec<_> = desc_chain .checked_iter(self.access_platform.as_deref()) .collect::>() .unwrap_or_default(); for desc in &descs { if !desc.is_write_only() { warn!("Skipping device-readable descriptor"); continue; } if desc.is_empty() { continue; } match desc_chain.memory().read_volatile_from( desc.addr(), &mut self.random_file, desc.len() as usize, ) { Ok(written) => total_len += written, Err(e) => { warn!("Failed to read entropy into descriptor: {e}"); break; } } } queue .add_used( desc_chain.memory(), desc_chain.head_index(), total_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: &AtomicBool, paused_sync: &Barrier, ) -> 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(Deserialize, Serialize)] pub struct RngState { pub avail_features: u64, pub acked_features: u64, } impl Rng { /// Create a new virtio rng device that gets random data from /dev/urandom. pub fn new( id: String, path: &str, access_platform_enabled: 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 access_platform_enabled { avail_features |= 1u64 << VIRTIO_F_ACCESS_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 VirtioDevice for Rng { fn config_size(&self) -> Option { Some(0) } 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, context: ActivationContext) -> ActivateResult { let ActivationContext { mem, interrupt_cb, mut queues, device_status, } = context; self.common.activate(&queues, interrupt_cb.clone())?; 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: interrupt_cb.clone(), queue_evt, kill_evt, pause_evt, access_platform: self.common.access_platform(), }; let paused = self.common.paused.clone(); let paused_sync = self.common.paused_sync.clone(); self.common.spawn_worker( &self.id, &self.seccomp_action, Thread::VirtioRng, &self.exit_evt, device_status.clone(), interrupt_cb.clone(), move || handler.run(&paused, paused_sync.as_ref().unwrap()), )?; event!("virtio-device", "activated", "id", &self.id); return Ok(()); } Err(ActivateError::BadActivate) } fn reset(&mut self) { self.common.reset(); event!("virtio-device", "reset", "id", &self.id); } fn set_access_platform(&mut self, access_platform: Arc) { self.common.set_access_platform(access_platform); } fn access_platform(&self) -> Option> { self.common.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) -> result::Result { Snapshot::new_from_state(&self.state()) } } impl Transportable for Rng {} impl Migratable for Rng {}