• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 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 
5 //! Programmable flash device that supports the minimum interface that OVMF
6 //! requires. This is purpose-built to allow OVMF to store UEFI variables in
7 //! the same way that it stores them on QEMU.
8 //!
9 //! For that reason it's heavily based on [QEMU's pflash implementation], while
10 //! taking even more shortcuts, chief among them being the complete lack of CFI
11 //! tables, which systems would normally use to learn how to use the device.
12 //!
13 //! In addition to full-width reads, we only support single byte writes,
14 //! block erases, and status requests, which OVMF uses to probe the device to
15 //! determine if it is pflash.
16 //!
17 //! Note that without SMM support in crosvm (which it doesn't yet have) this
18 //! device is directly accessible to potentially malicious kernels. With SMM
19 //! and the appropriate changes to this device this could be made more secure
20 //! by ensuring only the BIOS is able to touch the pflash.
21 //!
22 //! [QEMU's pflash implementation]: https://github.com/qemu/qemu/blob/master/hw/block/pflash_cfi01.c
23 
24 use std::path::PathBuf;
25 
26 use anyhow::bail;
27 use base::error;
28 use base::VolatileSlice;
29 use disk::DiskFile;
30 use serde::Deserialize;
31 use serde::Serialize;
32 use snapshot::AnySnapshot;
33 
34 use crate::pci::CrosvmDeviceId;
35 use crate::BusAccessInfo;
36 use crate::BusDevice;
37 use crate::DeviceId;
38 use crate::Suspendable;
39 
40 const COMMAND_WRITE_BYTE: u8 = 0x10;
41 const COMMAND_BLOCK_ERASE: u8 = 0x20;
42 const COMMAND_CLEAR_STATUS: u8 = 0x50;
43 const COMMAND_READ_STATUS: u8 = 0x70;
44 const COMMAND_BLOCK_ERASE_CONFIRM: u8 = 0xd0;
45 const COMMAND_READ_ARRAY: u8 = 0xff;
46 
47 const STATUS_READY: u8 = 0x80;
48 
pflash_parameters_default_block_size() -> u3249 fn pflash_parameters_default_block_size() -> u32 {
50     // 4K
51     4 * (1 << 10)
52 }
53 
54 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
55 pub struct PflashParameters {
56     pub path: PathBuf,
57     #[serde(default = "pflash_parameters_default_block_size")]
58     pub block_size: u32,
59 }
60 
61 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
62 enum State {
63     ReadArray,
64     ReadStatus,
65     BlockErase(u64),
66     Write(u64),
67 }
68 
69 pub struct Pflash {
70     image: Box<dyn DiskFile>,
71     image_size: u64,
72     block_size: u32,
73 
74     state: State,
75     status: u8,
76 }
77 
78 impl Pflash {
new(image: Box<dyn DiskFile>, block_size: u32) -> anyhow::Result<Pflash>79     pub fn new(image: Box<dyn DiskFile>, block_size: u32) -> anyhow::Result<Pflash> {
80         if !block_size.is_power_of_two() {
81             bail!("Block size {} is not a power of 2", block_size);
82         }
83         let image_size = image.get_len()?;
84         if image_size % block_size as u64 != 0 {
85             bail!(
86                 "Disk size {} is not a multiple of block size {}",
87                 image_size,
88                 block_size
89             );
90         }
91 
92         Ok(Pflash {
93             image,
94             image_size,
95             block_size,
96             state: State::ReadArray,
97             status: STATUS_READY,
98         })
99     }
100 }
101 
102 impl BusDevice for Pflash {
device_id(&self) -> DeviceId103     fn device_id(&self) -> DeviceId {
104         CrosvmDeviceId::Pflash.into()
105     }
106 
debug_label(&self) -> String107     fn debug_label(&self) -> String {
108         "pflash".to_owned()
109     }
110 
read(&mut self, info: BusAccessInfo, data: &mut [u8])111     fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
112         let offset = info.offset;
113         match &self.state {
114             State::ReadArray => {
115                 if offset + data.len() as u64 >= self.image_size {
116                     error!("pflash read request beyond disk");
117                     return;
118                 }
119                 if let Err(e) = self
120                     .image
121                     .read_exact_at_volatile(VolatileSlice::new(data), offset)
122                 {
123                     error!("pflash failed to read: {}", e);
124                 }
125             }
126             State::ReadStatus => {
127                 self.state = State::ReadArray;
128                 for d in data {
129                     *d = self.status;
130                 }
131             }
132             _ => {
133                 error!(
134                     "pflash received unexpected read in state {:?}, recovering to ReadArray mode",
135                     self.state
136                 );
137                 self.state = State::ReadArray;
138             }
139         }
140     }
141 
write(&mut self, info: BusAccessInfo, data: &[u8])142     fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
143         if data.len() > 1 {
144             error!("pflash write request for >1 byte, ignoring");
145             return;
146         }
147         let data = data[0];
148         let offset = info.offset;
149 
150         match self.state {
151             State::Write(expected_offset) => {
152                 self.state = State::ReadArray;
153                 self.status = STATUS_READY;
154 
155                 if offset != expected_offset {
156                     error!("pflash received write for offset {} that doesn't match offset from WRITE_BYTE command {}", offset, expected_offset);
157                     return;
158                 }
159                 if offset >= self.image_size {
160                     error!(
161                         "pflash offset {} greater than image size {}",
162                         offset, self.image_size
163                     );
164                     return;
165                 }
166 
167                 if let Err(e) = self
168                     .image
169                     .write_all_at_volatile(VolatileSlice::new(&mut [data]), offset)
170                 {
171                     error!("failed to write to pflash: {}", e);
172                 }
173             }
174             State::BlockErase(expected_offset) => {
175                 self.state = State::ReadArray;
176                 self.status = STATUS_READY;
177 
178                 if data != COMMAND_BLOCK_ERASE_CONFIRM {
179                     error!("pflash write data {} after BLOCK_ERASE command, wanted COMMAND_BLOCK_ERASE_CONFIRM", data);
180                     return;
181                 }
182                 if offset != expected_offset {
183                     error!("pflash offset {} for BLOCK_ERASE_CONFIRM command does not match the one for BLOCK_ERASE {}", offset, expected_offset);
184                     return;
185                 }
186                 if offset >= self.image_size {
187                     error!(
188                         "pflash block erase attempt offset {} beyond image size {}",
189                         offset, self.image_size
190                     );
191                     return;
192                 }
193                 if offset % self.block_size as u64 != 0 {
194                     error!(
195                         "pflash block erase offset {} not on block boundary with block size {}",
196                         offset, self.block_size
197                     );
198                     return;
199                 }
200 
201                 if let Err(e) = self.image.write_all_at_volatile(
202                     VolatileSlice::new(&mut [0xff].repeat(self.block_size.try_into().unwrap())),
203                     offset,
204                 ) {
205                     error!("pflash failed to erase block: {}", e);
206                 }
207             }
208             _ => {
209                 // If we're not expecting anything else then assume this is a
210                 // command to transition states.
211                 let command = data;
212 
213                 match command {
214                     COMMAND_READ_ARRAY => {
215                         self.state = State::ReadArray;
216                         self.status = STATUS_READY;
217                     }
218                     COMMAND_READ_STATUS => self.state = State::ReadStatus,
219                     COMMAND_CLEAR_STATUS => {
220                         self.state = State::ReadArray;
221                         self.status = 0;
222                     }
223                     COMMAND_WRITE_BYTE => self.state = State::Write(offset),
224                     COMMAND_BLOCK_ERASE => self.state = State::BlockErase(offset),
225                     _ => {
226                         error!("received unexpected/unsupported pflash command {}, ignoring and returning to read mode", command);
227                         self.state = State::ReadArray
228                     }
229                 }
230             }
231         }
232     }
233 }
234 
235 impl Suspendable for Pflash {
snapshot(&mut self) -> anyhow::Result<AnySnapshot>236     fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
237         AnySnapshot::to_any((self.status, self.state))
238     }
239 
restore(&mut self, data: AnySnapshot) -> anyhow::Result<()>240     fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
241         let (status, state) = AnySnapshot::from_any(data)?;
242         self.status = status;
243         self.state = state;
244         Ok(())
245     }
246 
sleep(&mut self) -> anyhow::Result<()>247     fn sleep(&mut self) -> anyhow::Result<()> {
248         // TODO(schuffelen): Flush the disk after lifting flush() from AsyncDisk to DiskFile
249         Ok(())
250     }
251 
wake(&mut self) -> anyhow::Result<()>252     fn wake(&mut self) -> anyhow::Result<()> {
253         Ok(())
254     }
255 }
256 
257 #[cfg(test)]
258 mod tests {
259     use base::FileReadWriteAtVolatile;
260     use tempfile::tempfile;
261 
262     use super::*;
263 
264     const IMAGE_SIZE: usize = 4 * (1 << 20); // 4M
265     const BLOCK_SIZE: u32 = 4 * (1 << 10); // 4K
266 
empty_image() -> Box<dyn DiskFile>267     fn empty_image() -> Box<dyn DiskFile> {
268         let f = Box::new(tempfile().unwrap());
269         f.write_all_at_volatile(VolatileSlice::new(&mut [0xff].repeat(IMAGE_SIZE)), 0)
270             .unwrap();
271         f
272     }
273 
new(f: Box<dyn DiskFile>) -> Pflash274     fn new(f: Box<dyn DiskFile>) -> Pflash {
275         Pflash::new(f, BLOCK_SIZE).unwrap()
276     }
277 
off(offset: u64) -> BusAccessInfo278     fn off(offset: u64) -> BusAccessInfo {
279         BusAccessInfo {
280             offset,
281             address: 0,
282             id: 0,
283         }
284     }
285 
286     #[test]
read()287     fn read() {
288         let f = empty_image();
289         let mut want = [0xde, 0xad, 0xbe, 0xef];
290         let offset = 0x1000;
291         f.write_all_at_volatile(VolatileSlice::new(&mut want), offset)
292             .unwrap();
293 
294         let mut pflash = new(f);
295         let mut got = [0u8; 4];
296         pflash.read(off(offset), &mut got[..]);
297         assert_eq!(want, got);
298     }
299 
300     #[test]
write()301     fn write() {
302         let f = empty_image();
303         let want = [0xdeu8];
304         let offset = 0x1000;
305 
306         let mut pflash = new(f);
307         pflash.write(off(offset), &[COMMAND_WRITE_BYTE]);
308         pflash.write(off(offset), &want);
309 
310         // Make sure the data reads back correctly over the bus...
311         pflash.write(off(0), &[COMMAND_READ_ARRAY]);
312         let mut got = [0u8; 1];
313         pflash.read(off(offset), &mut got);
314         assert_eq!(want, got);
315 
316         // And from the backing file itself...
317         pflash
318             .image
319             .read_exact_at_volatile(VolatileSlice::new(&mut got), offset)
320             .unwrap();
321         assert_eq!(want, got);
322 
323         // And when we recreate the device.
324         let mut pflash = new(pflash.image);
325         pflash.read(off(offset), &mut got);
326         assert_eq!(want, got);
327 
328         // Finally make sure our status is ready.
329         let mut got = [0u8; 4];
330         pflash.write(off(offset), &[COMMAND_READ_STATUS]);
331         pflash.read(off(offset), &mut got);
332         let want = [STATUS_READY; 4];
333         assert_eq!(want, got);
334     }
335 
336     #[test]
erase()337     fn erase() {
338         let f = empty_image();
339         let mut data = [0xde, 0xad, 0xbe, 0xef];
340         let offset = 0x1000;
341         f.write_all_at_volatile(VolatileSlice::new(&mut data), offset)
342             .unwrap();
343         f.write_all_at_volatile(VolatileSlice::new(&mut data), offset * 2)
344             .unwrap();
345 
346         let mut pflash = new(f);
347         pflash.write(off(offset), &[COMMAND_BLOCK_ERASE]);
348         pflash.write(off(offset), &[COMMAND_BLOCK_ERASE_CONFIRM]);
349 
350         pflash.write(off(0), &[COMMAND_READ_ARRAY]);
351         let mut got = [0u8; 4];
352         pflash.read(off(offset), &mut got);
353         let want = [0xffu8; 4];
354         assert_eq!(want, got);
355 
356         let want = data;
357         pflash.read(off(offset * 2), &mut got);
358         assert_eq!(want, got);
359 
360         // Make sure our status is ready.
361         pflash.write(off(offset), &[COMMAND_READ_STATUS]);
362         pflash.read(off(offset), &mut got);
363         let want = [STATUS_READY; 4];
364         assert_eq!(want, got);
365     }
366 
367     #[test]
status()368     fn status() {
369         let f = empty_image();
370         let mut data = [0xde, 0xad, 0xbe, 0xff];
371         let offset = 0x0;
372         f.write_all_at_volatile(VolatileSlice::new(&mut data), offset)
373             .unwrap();
374 
375         let mut pflash = new(f);
376 
377         // Make sure we start off in the "ready" status.
378         pflash.write(off(offset), &[COMMAND_READ_STATUS]);
379         let mut got = [0u8; 4];
380         pflash.read(off(offset), &mut got);
381         let want = [STATUS_READY; 4];
382         assert_eq!(want, got);
383 
384         // Make sure we can clear the status properly.
385         pflash.write(off(offset), &[COMMAND_CLEAR_STATUS]);
386         pflash.write(off(offset), &[COMMAND_READ_STATUS]);
387         pflash.read(off(offset), &mut got);
388         let want = [0; 4];
389         assert_eq!(want, got);
390 
391         // We implicitly jump back into READ_ARRAY mode after reading the,
392         // status but for OVMF's probe we require that this doesn't actually
393         // affect the cleared status.
394         pflash.read(off(offset), &mut got);
395         pflash.write(off(offset), &[COMMAND_READ_STATUS]);
396         pflash.read(off(offset), &mut got);
397         let want = [0; 4];
398         assert_eq!(want, got);
399     }
400 
401     #[test]
overwrite()402     fn overwrite() {
403         let f = empty_image();
404         let data = [0];
405         let offset = off((16 * IMAGE_SIZE).try_into().unwrap());
406 
407         // Ensure a write past the pflash device doesn't grow the backing file.
408         let mut pflash = new(f);
409         let old_size = pflash.image.get_len().unwrap();
410         assert_eq!(old_size, IMAGE_SIZE as u64);
411 
412         pflash.write(offset, &[COMMAND_WRITE_BYTE]);
413         pflash.write(offset, &data);
414 
415         let new_size = pflash.image.get_len().unwrap();
416         assert_eq!(new_size, IMAGE_SIZE as u64);
417     }
418 }
419