devices/virtio/
rng.rs

1// Copyright 2017 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::collections::BTreeMap;
6use std::io::Write;
7
8use anyhow::anyhow;
9use anyhow::Context;
10use base::error;
11use base::warn;
12use base::Event;
13use base::EventToken;
14use base::RawDescriptor;
15use base::WaitContext;
16use base::WorkerThread;
17#[cfg(any(target_os = "android", target_os = "linux"))]
18use jail::JailConfig;
19#[cfg(any(target_os = "android", target_os = "linux"))]
20use minijail::Minijail;
21use snapshot::AnySnapshot;
22use vm_memory::GuestMemory;
23
24use super::DeviceType;
25use super::Interrupt;
26use super::Queue;
27use super::VirtioDevice;
28use crate::virtio;
29use crate::VirtioDeviceArgs;
30use crate::VirtioDeviceModule;
31
32const QUEUE_SIZE: u16 = 256;
33const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
34
35// Chosen to match the Linux guest driver RNG buffer refill size.
36const CHUNK_SIZE: usize = 64;
37
38struct Worker {
39    queue: Queue,
40}
41
42impl Worker {
43    fn process_queue(&mut self) {
44        let mut needs_interrupt = false;
45
46        while let Some(mut avail_desc) = self.queue.pop() {
47            let writer = &mut avail_desc.writer;
48            while writer.available_bytes() > 0 {
49                let chunk_size = writer.available_bytes().min(CHUNK_SIZE);
50                let rand_bytes: [u8; CHUNK_SIZE] = rand::random();
51                let chunk = &rand_bytes[..chunk_size];
52                if let Err(e) = writer.write_all(chunk) {
53                    warn!("Failed to write random data to the guest: {}", e);
54                    break;
55                }
56            }
57
58            self.queue.add_used(avail_desc);
59            needs_interrupt = true;
60        }
61
62        if needs_interrupt {
63            self.queue.trigger_interrupt();
64        }
65    }
66
67    fn run(&mut self, kill_evt: Event) -> anyhow::Result<()> {
68        #[derive(EventToken)]
69        enum Token {
70            QueueAvailable,
71            Kill,
72        }
73
74        let wait_ctx = WaitContext::build_with(&[
75            (self.queue.event(), Token::QueueAvailable),
76            (&kill_evt, Token::Kill),
77        ])
78        .context("failed creating WaitContext")?;
79
80        let mut exiting = false;
81        while !exiting {
82            let events = wait_ctx.wait().context("failed polling for events")?;
83            for event in events.iter().filter(|e| e.is_readable) {
84                match event.token {
85                    Token::QueueAvailable => {
86                        self.queue
87                            .event()
88                            .wait()
89                            .context("failed reading queue Event")?;
90                        self.process_queue();
91                    }
92                    Token::Kill => exiting = true,
93                }
94            }
95        }
96
97        Ok(())
98    }
99}
100
101/// Virtio device for exposing entropy to the guest OS through virtio.
102struct Rng {
103    worker_thread: Option<WorkerThread<Worker>>,
104    virtio_features: u64,
105}
106
107impl VirtioDevice for Rng {
108    fn keep_rds(&self) -> Vec<RawDescriptor> {
109        Vec::new()
110    }
111
112    fn device_type(&self) -> DeviceType {
113        DeviceType::Rng
114    }
115
116    fn queue_max_sizes(&self) -> &[u16] {
117        QUEUE_SIZES
118    }
119
120    fn features(&self) -> u64 {
121        self.virtio_features
122    }
123
124    fn activate(
125        &mut self,
126        _mem: GuestMemory,
127        _interrupt: Interrupt,
128        mut queues: BTreeMap<usize, Queue>,
129    ) -> anyhow::Result<()> {
130        if queues.len() != 1 {
131            return Err(anyhow!("expected 1 queue, got {}", queues.len()));
132        }
133
134        let queue = queues.remove(&0).unwrap();
135
136        self.worker_thread = Some(WorkerThread::start("v_rng", move |kill_evt| {
137            let mut worker = Worker { queue };
138            if let Err(e) = worker.run(kill_evt) {
139                error!("rng worker thread failed: {:#}", e);
140            }
141            worker
142        }));
143
144        Ok(())
145    }
146
147    fn reset(&mut self) -> anyhow::Result<()> {
148        if let Some(worker_thread) = self.worker_thread.take() {
149            let _worker = worker_thread.stop();
150        }
151        Ok(())
152    }
153
154    fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
155        if let Some(worker_thread) = self.worker_thread.take() {
156            let worker = worker_thread.stop();
157            return Ok(Some(BTreeMap::from([(0, worker.queue)])));
158        }
159        Ok(None)
160    }
161
162    fn virtio_wake(
163        &mut self,
164        queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
165    ) -> anyhow::Result<()> {
166        if let Some((mem, interrupt, queues)) = queues_state {
167            self.activate(mem, interrupt, queues)?;
168        }
169        Ok(())
170    }
171
172    fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
173        // `virtio_sleep` ensures there is no pending state, except for the `Queue`s, which are
174        // handled at a higher layer.
175        AnySnapshot::to_any(())
176    }
177
178    fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
179        let () = AnySnapshot::from_any(data)?;
180        Ok(())
181    }
182}
183
184/// Virtio rng device that gets random data from /dev/urandom.
185#[derive(serde::Serialize, serde::Deserialize)]
186pub struct VirtioRngModule;
187
188impl VirtioDeviceModule for VirtioRngModule {
189    fn sort_name(&self) -> &'static str {
190        "rng"
191    }
192
193    fn create(&self, args: &mut VirtioDeviceArgs<'_>) -> anyhow::Result<Box<dyn VirtioDevice>> {
194        Ok(Box::new(Rng {
195            worker_thread: None,
196            virtio_features: virtio::base_features(args.protection_type),
197        }))
198    }
199
200    #[cfg(any(target_os = "android", target_os = "linux"))]
201    fn create_jail(&self, jail_config: &JailConfig) -> anyhow::Result<Option<Minijail>> {
202        jail::simple_jail(Some(jail_config), "rng_device")
203    }
204}