pvmfw: Encrypt instance.img entries
As the host owns the files backing the virtio-blk devices, encrypt the
entries in a tamper-evident way.
Derive the private key used for encryption from the sealing CDI. Note
that this patch uses a _randnonce() AEAD but doesn't provide entropy,
which will be added in a future patch.
Implement a wrapper for BoringSSL AEAD functions, key derivation,
hashing, and error handling. Implement the CRYPTO_sysrand* symbols those
require.
Add sterror(), required by ERR_reason_error_string, to vmbase instead of
using the Bionic version, which is harder to integrate due to
thread-safety support and TLS layout. Error reporting also requires the
standard bsearch() function.
Note: Entries added to an instance.img before applying this patch will
now be rejected.
Bug: 249723852
Test: atest MicrodroidHostTests
Change-Id: If41aa8e1961121d9aee116c14b54d983dd10f61e
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index 3657fb6..6a54623 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -14,6 +14,9 @@
//! Support for reading and writing to the instance.img.
+use crate::crypto;
+use crate::crypto::hkdf_sh512;
+use crate::crypto::AeadCtx;
use crate::dice::PartialInputs;
use crate::gpt;
use crate::gpt::Partition;
@@ -33,6 +36,10 @@
pub enum Error {
/// Unexpected I/O error while accessing the underlying disk.
FailedIo(gpt::Error),
+ /// Failed to decrypt the entry.
+ FailedOpen(crypto::ErrorIterator),
+ /// Failed to encrypt the entry.
+ FailedSeal(crypto::ErrorIterator),
/// Impossible to create a new instance.img entry.
InstanceImageFull,
/// Badly formatted instance.img header block.
@@ -55,6 +62,20 @@
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
+ Self::FailedOpen(e_iter) => {
+ writeln!(f, "Failed to open the instance.img partition:")?;
+ for e in *e_iter {
+ writeln!(f, "\t{e}")?;
+ }
+ Ok(())
+ }
+ Self::FailedSeal(e_iter) => {
+ writeln!(f, "Failed to seal the instance.img partition:")?;
+ for e in *e_iter {
+ writeln!(f, "\t{e}")?;
+ }
+ Ok(())
+ }
Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
@@ -72,12 +93,14 @@
pub fn get_or_generate_instance_salt(
pci_root: &mut PciRoot,
dice_inputs: &PartialInputs,
+ secret: &[u8],
) -> Result<(bool, Hidden)> {
let mut instance_img = find_instance_img(pci_root)?;
let entry = locate_entry(&mut instance_img)?;
trace!("Found pvmfw instance.img entry: {entry:?}");
+ let key = hkdf_sh512::<32>(secret, /*salt=*/ &[], b"vm-instance");
let mut blk = [0; BLK_SIZE];
match entry {
PvmfwEntry::Existing { header_index, payload_size } => {
@@ -88,9 +111,13 @@
let payload_index = header_index + 1;
instance_img.read_block(payload_index, &mut blk).map_err(Error::FailedIo)?;
- let payload = &blk[..payload_size]; // TODO(b/249723852): Decrypt entries.
+ let payload = &blk[..payload_size];
+ let mut entry = [0; size_of::<EntryBody>()];
+ let key = key.map_err(Error::FailedOpen)?;
+ let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedOpen)?;
+ let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
- let body: &EntryBody = payload.as_ref();
+ let body: &EntryBody = decrypted.as_ref();
if body.code_hash != dice_inputs.code_hash {
Err(Error::RecordedCodeHashMismatch)
} else if body.auth_hash != dice_inputs.auth_hash {
@@ -105,11 +132,13 @@
let salt = [0; size_of::<Hidden>()]; // TODO(b/262393451): Generate using TRNG.
let entry_body = EntryBody::new(dice_inputs, &salt);
let body = entry_body.as_ref();
- // We currently only support single-blk entries.
- assert!(body.len() < blk.len());
- let payload_size = body.len();
- blk[..payload_size].copy_from_slice(body); // TODO(b/249723852): Encrypt entries.
+ let key = key.map_err(Error::FailedSeal)?;
+ let aead = AeadCtx::new_aes_256_gcm_randnonce(&key).map_err(Error::FailedSeal)?;
+ // We currently only support single-blk entries.
+ assert!(body.len() + aead.aead().unwrap().max_overhead() < blk.len());
+ let encrypted = aead.seal(&mut blk, body).map_err(Error::FailedSeal)?;
+ let payload_size = encrypted.len();
let payload_index = header_index + 1;
instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;