pvmfw: Support instance.img for storing DICE salt
On boot, attempt to read the DICE hidden input (salt) from persistent
storage, in order to provide persistence of secrets across reboots of
the same kernel/ramdisk images. To ensure that the salt is only re-used
with the same preloaded images signed by the same authority, store the
other DICE inputs so that they can be verified to match on the next
boot.
Implement support for parsing GUID Partition Tables (GPT) on top of
virtio-blk, to locate, read from, and write to named partitions.
Implement support for the instance.img format, defined by the AVF team,
and conceptually acting as a filesystem within the GPT partition named
"vm-instance", where each stage of the pVM boot process owns an entry
(sometimes called a partition), identified through a UUID.
As the host is in charge of providing the file backing the virtual disk,
the content of the instance.img entry will be encrypted in a following
commit.
Bug: 262344886
Test: atest MicrodroidHostTests
Change-Id: Ic43bb7780b5e106002590f9c97cd900c3ff5e3d9
diff --git a/pvmfw/src/gpt.rs b/pvmfw/src/gpt.rs
new file mode 100644
index 0000000..6af3047
--- /dev/null
+++ b/pvmfw/src/gpt.rs
@@ -0,0 +1,253 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Support for parsing GUID partition tables.
+
+use crate::helpers::ceiling_div;
+use crate::virtio::pci::VirtIOBlk;
+use core::cmp::min;
+use core::fmt;
+use core::mem::size_of;
+use core::ops::RangeInclusive;
+use core::slice;
+use static_assertions::const_assert;
+use static_assertions::const_assert_eq;
+use uuid::Uuid;
+use virtio_drivers::device::blk::SECTOR_SIZE;
+
+pub enum Error {
+ /// VirtIO error during read operation.
+ FailedRead(virtio_drivers::Error),
+ /// VirtIO error during write operation.
+ FailedWrite(virtio_drivers::Error),
+ /// Invalid GPT header.
+ InvalidHeader,
+ /// Invalid partition block index.
+ BlockOutsidePartition(usize),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::FailedRead(e) => write!(f, "Failed to read from disk: {e}"),
+ Self::FailedWrite(e) => write!(f, "Failed to write to disk: {e}"),
+ Self::InvalidHeader => write!(f, "Found invalid GPT header"),
+ Self::BlockOutsidePartition(i) => write!(f, "Accessed invalid block index {i}"),
+ }
+ }
+}
+
+pub type Result<T> = core::result::Result<T, Error>;
+
+pub struct Partition {
+ partitions: Partitions,
+ indices: RangeInclusive<usize>,
+}
+
+impl Partition {
+ pub fn get_by_name(device: VirtIOBlk, name: &str) -> Result<Option<Self>> {
+ Partitions::new(device)?.get_partition_by_name(name)
+ }
+
+ fn new(partitions: Partitions, entry: &Entry) -> Self {
+ let first = entry.first_lba().try_into().unwrap();
+ let last = entry.last_lba().try_into().unwrap();
+
+ Self { partitions, indices: first..=last }
+ }
+
+ pub fn indices(&self) -> RangeInclusive<usize> {
+ self.indices.clone()
+ }
+
+ pub fn read_block(&mut self, index: usize, blk: &mut [u8]) -> Result<()> {
+ let index = self.block_index(index).ok_or(Error::BlockOutsidePartition(index))?;
+ self.partitions.read_block(index, blk)
+ }
+
+ pub fn write_block(&mut self, index: usize, blk: &[u8]) -> Result<()> {
+ let index = self.block_index(index).ok_or(Error::BlockOutsidePartition(index))?;
+ self.partitions.write_block(index, blk)
+ }
+
+ fn block_index(&self, index: usize) -> Option<usize> {
+ if self.indices.contains(&index) {
+ Some(index)
+ } else {
+ None
+ }
+ }
+}
+
+pub struct Partitions {
+ device: VirtIOBlk,
+ entries_count: usize,
+}
+
+impl Partitions {
+ pub const LBA_SIZE: usize = SECTOR_SIZE;
+
+ fn new(mut device: VirtIOBlk) -> Result<Self> {
+ let mut blk = [0; Self::LBA_SIZE];
+ device.read_block(Header::LBA, &mut blk).map_err(Error::FailedRead)?;
+ let (header_bytes, _) = blk.split_at(size_of::<Header>());
+ let header = Header::from_bytes(header_bytes).ok_or(Error::InvalidHeader)?;
+ let entries_count = usize::try_from(header.entries_count()).unwrap();
+
+ Ok(Self { device, entries_count })
+ }
+
+ fn get_partition_by_name(mut self, name: &str) -> Result<Option<Partition>> {
+ const_assert_eq!(Partitions::LBA_SIZE.rem_euclid(size_of::<Entry>()), 0);
+ let entries_per_blk = Partitions::LBA_SIZE.checked_div(size_of::<Entry>()).unwrap();
+
+ // Create a UTF-16 reference against which we'll compare partition names. Note that unlike
+ // the C99 wcslen(), this comparison will cover bytes past the first L'\0' character.
+ let mut needle = [0; Entry::NAME_SIZE / size_of::<u16>()];
+ for (dest, src) in needle.iter_mut().zip(name.encode_utf16()) {
+ *dest = src;
+ }
+
+ let mut blk = [0; Self::LBA_SIZE];
+ let mut rem = self.entries_count;
+ let num_blocks = ceiling_div(self.entries_count, entries_per_blk).unwrap();
+ for i in Header::ENTRIES_LBA..Header::ENTRIES_LBA.checked_add(num_blocks).unwrap() {
+ self.read_block(i, &mut blk)?;
+ let entries = blk.as_ptr().cast::<Entry>();
+ // SAFETY - blk is assumed to be properly aligned for Entry and its size is assert-ed
+ // above. All potential values of the slice will produce valid Entry values.
+ let entries = unsafe { slice::from_raw_parts(entries, min(rem, entries_per_blk)) };
+ for entry in entries {
+ let entry_name = entry.name;
+ if entry_name == needle {
+ return Ok(Some(Partition::new(self, entry)));
+ }
+ rem -= 1;
+ }
+ }
+ Ok(None)
+ }
+
+ fn read_block(&mut self, index: usize, blk: &mut [u8]) -> Result<()> {
+ self.device.read_block(index, blk).map_err(Error::FailedRead)
+ }
+
+ fn write_block(&mut self, index: usize, blk: &[u8]) -> Result<()> {
+ self.device.write_block(index, blk).map_err(Error::FailedWrite)
+ }
+}
+
+type Lba = u64;
+
+/// Structure as defined in release 2.10 of the UEFI Specification (5.3.2 GPT Header).
+#[repr(C, packed)]
+struct Header {
+ signature: u64,
+ revision: u32,
+ header_size: u32,
+ header_crc32: u32,
+ reserved0: u32,
+ current_lba: Lba,
+ backup_lba: Lba,
+ first_lba: Lba,
+ last_lba: Lba,
+ disk_guid: Uuid,
+ entries_lba: Lba,
+ entries_count: u32,
+ entry_size: u32,
+ entries_crc32: u32,
+}
+const_assert!(size_of::<Header>() < Partitions::LBA_SIZE);
+
+impl Header {
+ const SIGNATURE: u64 = u64::from_le_bytes(*b"EFI PART");
+ const REVISION_1_0: u32 = 1 << 16;
+ const LBA: usize = 1;
+ const ENTRIES_LBA: usize = 2;
+
+ fn from_bytes(bytes: &[u8]) -> Option<&Self> {
+ let bytes = bytes.get(..size_of::<Self>())?;
+ // SAFETY - We assume that bytes is properly aligned for Header and have verified above
+ // that it holds enough bytes. All potential values of the slice will produce a valid
+ // Header.
+ let header = unsafe { &*bytes.as_ptr().cast::<Self>() };
+
+ if header.is_valid() {
+ Some(header)
+ } else {
+ None
+ }
+ }
+
+ fn is_valid(&self) -> bool {
+ self.signature() == Self::SIGNATURE
+ && self.header_size() == size_of::<Self>().try_into().unwrap()
+ && self.revision() == Self::REVISION_1_0
+ && self.entry_size() == size_of::<Entry>().try_into().unwrap()
+ && self.current_lba() == Self::LBA.try_into().unwrap()
+ && self.entries_lba() == Self::ENTRIES_LBA.try_into().unwrap()
+ }
+
+ fn signature(&self) -> u64 {
+ u64::from_le(self.signature)
+ }
+
+ fn entries_count(&self) -> u32 {
+ u32::from_le(self.entries_count)
+ }
+
+ fn header_size(&self) -> u32 {
+ u32::from_le(self.header_size)
+ }
+
+ fn revision(&self) -> u32 {
+ u32::from_le(self.revision)
+ }
+
+ fn entry_size(&self) -> u32 {
+ u32::from_le(self.entry_size)
+ }
+
+ fn entries_lba(&self) -> Lba {
+ Lba::from_le(self.entries_lba)
+ }
+
+ fn current_lba(&self) -> Lba {
+ Lba::from_le(self.current_lba)
+ }
+}
+
+/// Structure as defined in release 2.10 of the UEFI Specification (5.3.3 GPT Partition Entry
+/// Array).
+#[repr(C, packed)]
+struct Entry {
+ type_guid: Uuid,
+ guid: Uuid,
+ first_lba: Lba,
+ last_lba: Lba,
+ flags: u64,
+ name: [u16; Entry::NAME_SIZE / size_of::<u16>()], // UTF-16
+}
+
+impl Entry {
+ const NAME_SIZE: usize = 72;
+
+ fn first_lba(&self) -> Lba {
+ Lba::from_le(self.first_lba)
+ }
+
+ fn last_lba(&self) -> Lba {
+ Lba::from_le(self.last_lba)
+ }
+}