xref: /cloud-hypervisor/virtio-devices/src/rng.rs (revision 88a9f799449c04180c6b9a21d3b9c0c4b57e2bd6)
1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
6 
7 use std::fs::File;
8 use std::io;
9 use std::os::unix::io::AsRawFd;
10 use std::result;
11 use std::sync::atomic::AtomicBool;
12 use std::sync::{Arc, Barrier};
13 
14 use anyhow::anyhow;
15 use seccompiler::SeccompAction;
16 use serde::{Deserialize, Serialize};
17 use thiserror::Error;
18 use virtio_queue::{Queue, QueueT};
19 use vm_memory::{GuestAddressSpace, GuestMemory, GuestMemoryAtomic};
20 use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
21 use vm_virtio::{AccessPlatform, Translatable};
22 use vmm_sys_util::eventfd::EventFd;
23 
24 use super::Error as DeviceError;
25 use super::{
26     ActivateError, ActivateResult, EpollHelper, EpollHelperError, EpollHelperHandler, VirtioCommon,
27     VirtioDevice, VirtioDeviceType, EPOLL_HELPER_EVENT_LAST, VIRTIO_F_IOMMU_PLATFORM,
28     VIRTIO_F_VERSION_1,
29 };
30 use crate::seccomp_filters::Thread;
31 use crate::thread_helper::spawn_virtio_thread;
32 use crate::GuestMemoryMmap;
33 use crate::{VirtioInterrupt, VirtioInterruptType};
34 
35 const QUEUE_SIZE: u16 = 256;
36 const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
37 
38 // New descriptors are pending on the virtio queue.
39 const QUEUE_AVAIL_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
40 
41 #[derive(Error, Debug)]
42 enum Error {
43     #[error("Descriptor chain too short")]
44     DescriptorChainTooShort,
45     #[error("Invalid descriptor")]
46     InvalidDescriptor,
47     #[error("Failed to write to guest memory: {0}")]
48     GuestMemoryWrite(vm_memory::guest_memory::Error),
49     #[error("Failed adding used index: {0}")]
50     QueueAddUsed(virtio_queue::Error),
51 }
52 
53 struct RngEpollHandler {
54     mem: GuestMemoryAtomic<GuestMemoryMmap>,
55     queue: Queue,
56     random_file: File,
57     interrupt_cb: Arc<dyn VirtioInterrupt>,
58     queue_evt: EventFd,
59     kill_evt: EventFd,
60     pause_evt: EventFd,
61     access_platform: Option<Arc<dyn AccessPlatform>>,
62 }
63 
64 impl RngEpollHandler {
65     fn process_queue(&mut self) -> result::Result<bool, Error> {
66         let queue = &mut self.queue;
67 
68         let mut used_descs = false;
69         while let Some(mut desc_chain) = queue.pop_descriptor_chain(self.mem.memory()) {
70             let desc = desc_chain.next().ok_or(Error::DescriptorChainTooShort)?;
71 
72             // The descriptor must be write-only and non-zero length
73             if !(desc.is_write_only() && desc.len() > 0) {
74                 return Err(Error::InvalidDescriptor);
75             }
76 
77             // Fill the read with data from the random device on the host.
78             let len = desc_chain
79                 .memory()
80                 .read_volatile_from(
81                     desc.addr()
82                         .translate_gva(self.access_platform.as_ref(), desc.len() as usize),
83                     &mut self.random_file,
84                     desc.len() as usize,
85                 )
86                 .map_err(Error::GuestMemoryWrite)?;
87 
88             queue
89                 .add_used(desc_chain.memory(), desc_chain.head_index(), len as u32)
90                 .map_err(Error::QueueAddUsed)?;
91             used_descs = true;
92         }
93 
94         Ok(used_descs)
95     }
96 
97     fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
98         self.interrupt_cb
99             .trigger(VirtioInterruptType::Queue(0))
100             .map_err(|e| {
101                 error!("Failed to signal used queue: {:?}", e);
102                 DeviceError::FailedSignalingUsedQueue(e)
103             })
104     }
105 
106     fn run(
107         &mut self,
108         paused: Arc<AtomicBool>,
109         paused_sync: Arc<Barrier>,
110     ) -> result::Result<(), EpollHelperError> {
111         let mut helper = EpollHelper::new(&self.kill_evt, &self.pause_evt)?;
112         helper.add_event(self.queue_evt.as_raw_fd(), QUEUE_AVAIL_EVENT)?;
113         helper.run(paused, paused_sync, self)?;
114 
115         Ok(())
116     }
117 }
118 
119 impl EpollHelperHandler for RngEpollHandler {
120     fn handle_event(
121         &mut self,
122         _helper: &mut EpollHelper,
123         event: &epoll::Event,
124     ) -> result::Result<(), EpollHelperError> {
125         let ev_type = event.data as u16;
126         match ev_type {
127             QUEUE_AVAIL_EVENT => {
128                 self.queue_evt.read().map_err(|e| {
129                     EpollHelperError::HandleEvent(anyhow!("Failed to get queue event: {:?}", e))
130                 })?;
131                 let needs_notification = self.process_queue().map_err(|e| {
132                     EpollHelperError::HandleEvent(anyhow!("Failed to process queue : {:?}", e))
133                 })?;
134                 if needs_notification {
135                     self.signal_used_queue().map_err(|e| {
136                         EpollHelperError::HandleEvent(anyhow!(
137                             "Failed to signal used queue: {:?}",
138                             e
139                         ))
140                     })?;
141                 }
142             }
143             _ => {
144                 return Err(EpollHelperError::HandleEvent(anyhow!(
145                     "Unexpected event: {}",
146                     ev_type
147                 )));
148             }
149         }
150         Ok(())
151     }
152 }
153 
154 /// Virtio device for exposing entropy to the guest OS through virtio.
155 pub struct Rng {
156     common: VirtioCommon,
157     id: String,
158     random_file: Option<File>,
159     seccomp_action: SeccompAction,
160     exit_evt: EventFd,
161 }
162 
163 #[derive(Deserialize, Serialize)]
164 pub struct RngState {
165     pub avail_features: u64,
166     pub acked_features: u64,
167 }
168 
169 impl Rng {
170     /// Create a new virtio rng device that gets random data from /dev/urandom.
171     pub fn new(
172         id: String,
173         path: &str,
174         iommu: bool,
175         seccomp_action: SeccompAction,
176         exit_evt: EventFd,
177         state: Option<RngState>,
178     ) -> io::Result<Rng> {
179         let random_file = File::open(path)?;
180 
181         let (avail_features, acked_features, paused) = if let Some(state) = state {
182             info!("Restoring virtio-rng {}", id);
183             (state.avail_features, state.acked_features, true)
184         } else {
185             let mut avail_features = 1u64 << VIRTIO_F_VERSION_1;
186 
187             if iommu {
188                 avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
189             }
190 
191             (avail_features, 0, false)
192         };
193 
194         Ok(Rng {
195             common: VirtioCommon {
196                 device_type: VirtioDeviceType::Rng as u32,
197                 queue_sizes: QUEUE_SIZES.to_vec(),
198                 paused_sync: Some(Arc::new(Barrier::new(2))),
199                 avail_features,
200                 acked_features,
201                 min_queues: 1,
202                 paused: Arc::new(AtomicBool::new(paused)),
203                 ..Default::default()
204             },
205             id,
206             random_file: Some(random_file),
207             seccomp_action,
208             exit_evt,
209         })
210     }
211 
212     fn state(&self) -> RngState {
213         RngState {
214             avail_features: self.common.avail_features,
215             acked_features: self.common.acked_features,
216         }
217     }
218 
219     #[cfg(fuzzing)]
220     pub fn wait_for_epoll_threads(&mut self) {
221         self.common.wait_for_epoll_threads();
222     }
223 }
224 
225 impl Drop for Rng {
226     fn drop(&mut self) {
227         if let Some(kill_evt) = self.common.kill_evt.take() {
228             // Ignore the result because there is nothing we can do about it.
229             let _ = kill_evt.write(1);
230         }
231         self.common.wait_for_epoll_threads();
232     }
233 }
234 
235 impl VirtioDevice for Rng {
236     fn device_type(&self) -> u32 {
237         self.common.device_type
238     }
239 
240     fn queue_max_sizes(&self) -> &[u16] {
241         &self.common.queue_sizes
242     }
243 
244     fn features(&self) -> u64 {
245         self.common.avail_features
246     }
247 
248     fn ack_features(&mut self, value: u64) {
249         self.common.ack_features(value)
250     }
251 
252     fn activate(
253         &mut self,
254         mem: GuestMemoryAtomic<GuestMemoryMmap>,
255         interrupt_cb: Arc<dyn VirtioInterrupt>,
256         mut queues: Vec<(usize, Queue, EventFd)>,
257     ) -> ActivateResult {
258         self.common.activate(&queues, &interrupt_cb)?;
259         let (kill_evt, pause_evt) = self.common.dup_eventfds();
260 
261         if let Some(file) = self.random_file.as_ref() {
262             let random_file = file.try_clone().map_err(|e| {
263                 error!("failed cloning rng source: {}", e);
264                 ActivateError::BadActivate
265             })?;
266 
267             let (_, queue, queue_evt) = queues.remove(0);
268 
269             let mut handler = RngEpollHandler {
270                 mem,
271                 queue,
272                 random_file,
273                 interrupt_cb,
274                 queue_evt,
275                 kill_evt,
276                 pause_evt,
277                 access_platform: self.common.access_platform.clone(),
278             };
279 
280             let paused = self.common.paused.clone();
281             let paused_sync = self.common.paused_sync.clone();
282             let mut epoll_threads = Vec::new();
283             spawn_virtio_thread(
284                 &self.id,
285                 &self.seccomp_action,
286                 Thread::VirtioRng,
287                 &mut epoll_threads,
288                 &self.exit_evt,
289                 move || handler.run(paused, paused_sync.unwrap()),
290             )?;
291 
292             self.common.epoll_threads = Some(epoll_threads);
293 
294             event!("virtio-device", "activated", "id", &self.id);
295             return Ok(());
296         }
297         Err(ActivateError::BadActivate)
298     }
299 
300     fn reset(&mut self) -> Option<Arc<dyn VirtioInterrupt>> {
301         let result = self.common.reset();
302         event!("virtio-device", "reset", "id", &self.id);
303         result
304     }
305 
306     fn set_access_platform(&mut self, access_platform: Arc<dyn AccessPlatform>) {
307         self.common.set_access_platform(access_platform)
308     }
309 }
310 
311 impl Pausable for Rng {
312     fn pause(&mut self) -> result::Result<(), MigratableError> {
313         self.common.pause()
314     }
315 
316     fn resume(&mut self) -> result::Result<(), MigratableError> {
317         self.common.resume()
318     }
319 }
320 
321 impl Snapshottable for Rng {
322     fn id(&self) -> String {
323         self.id.clone()
324     }
325 
326     fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
327         Snapshot::new_from_state(&self.state())
328     }
329 }
330 
331 impl Transportable for Rng {}
332 impl Migratable for Rng {}
333