pvmfw: Support com.android.virt.page_size
Replace the assumption that guests run with 4KiB page tables by adding
support for a new VBMeta descriptor property describing the size in use.
When absent, default to the previous assumption of 4KiB.
Set the property for the Micrdroid kernels with 16KiB PAGE_SIZE.
Add test coverage & document this in the README.
Bug: 339779843
Bug: 339782511
Test: atest libpvmfw_avb.integration_test
Change-Id: Ib3c2b87fd507046578cc95d892d01fa9b04bc5c7
diff --git a/build/microdroid/Android.bp b/build/microdroid/Android.bp
index 7f23ae6..20980e0 100644
--- a/build/microdroid/Android.bp
+++ b/build/microdroid/Android.bp
@@ -560,6 +560,12 @@
src: ":microdroid_kernel_prebuilt-x86_64",
},
},
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "16",
+ },
+ ],
include_descriptors_from_images: [
":microdroid_16k_initrd_normal_hashdesc",
":microdroid_16k_initrd_debug_hashdesc",
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index 8c8314d..766a923 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -461,6 +461,7 @@
- `secretkeeper_protection`: pvmfw defers rollback protection to the guest
- `supports_uefi_boot`: pvmfw boots the VM as a EFI payload (experimental)
- `trusty_security_vm`: pvmfw skips rollback protection
+- `"com.android.virt.page_size"`: the guest page size in KiB (optional, defaults to 4)
## Development
diff --git a/guest/pvmfw/avb/Android.bp b/guest/pvmfw/avb/Android.bp
index 10c7841..0294322 100644
--- a/guest/pvmfw/avb/Android.bp
+++ b/guest/pvmfw/avb/Android.bp
@@ -37,6 +37,14 @@
":test_image_with_one_hashdesc",
":test_image_with_non_initrd_hashdesc",
":test_image_with_initrd_and_non_initrd_desc",
+ ":test_image_with_invalid_page_size",
+ ":test_image_with_negative_page_size",
+ ":test_image_with_overflow_page_size",
+ ":test_image_with_0k_page_size",
+ ":test_image_with_1k_page_size",
+ ":test_image_with_4k_page_size",
+ ":test_image_with_9k_page_size",
+ ":test_image_with_16k_page_size",
":test_image_with_service_vm_prop",
":test_image_with_unknown_vm_type_prop",
":test_image_with_duplicated_capability",
@@ -115,6 +123,118 @@
}
avb_add_hash_footer {
+ name: "test_image_with_invalid_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "invalid",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_negative_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "-16",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_overflow_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "18014398509481983",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_0k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "0",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_1k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "1",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_4k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "4",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_9k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "9",
+ },
+ ],
+}
+
+avb_add_hash_footer {
+ name: "test_image_with_16k_page_size",
+ src: ":unsigned_test_image",
+ partition_name: "boot",
+ private_key: ":pvmfw_sign_key",
+ salt: "2134",
+ props: [
+ {
+ name: "com.android.virt.page_size",
+ value: "16",
+ },
+ ],
+}
+
+avb_add_hash_footer {
name: "test_image_with_service_vm_prop",
src: ":unsigned_test_image",
partition_name: "boot",
diff --git a/guest/pvmfw/avb/src/error.rs b/guest/pvmfw/avb/src/error.rs
index 2e1950a..1307e15 100644
--- a/guest/pvmfw/avb/src/error.rs
+++ b/guest/pvmfw/avb/src/error.rs
@@ -28,6 +28,8 @@
InvalidDescriptors(DescriptorError),
/// Unknown vbmeta property.
UnknownVbmetaProperty,
+ /// VBMeta has invalid page_size property.
+ InvalidPageSize,
}
impl From<SlotVerifyError<'_>> for PvmfwVerifyError {
@@ -51,6 +53,7 @@
write!(f, "VBMeta has invalid descriptors. Error: {:?}", e)
}
Self::UnknownVbmetaProperty => write!(f, "Unknown vbmeta property"),
+ Self::InvalidPageSize => write!(f, "Invalid page_size property"),
}
}
}
diff --git a/guest/pvmfw/avb/src/verify.rs b/guest/pvmfw/avb/src/verify.rs
index 2a7eed2..8810696 100644
--- a/guest/pvmfw/avb/src/verify.rs
+++ b/guest/pvmfw/avb/src/verify.rs
@@ -22,6 +22,7 @@
Descriptor, DescriptorError, DescriptorResult, HashDescriptor, PartitionData, SlotVerifyError,
SlotVerifyNoDataResult, VbmetaData,
};
+use core::str;
// We use this for the rollback_index field if SlotVerifyData has empty rollback_indexes
const DEFAULT_ROLLBACK_INDEX: u64 = 0;
@@ -220,6 +221,23 @@
Ok(digest)
}
+/// Returns the indicated payload page size, if present.
+fn read_page_size(vbmeta_data: &VbmetaData) -> Result<Option<usize>, PvmfwVerifyError> {
+ let Some(property) = vbmeta_data.get_property_value("com.android.virt.page_size") else {
+ return Ok(None);
+ };
+ let size = str::from_utf8(property)
+ .or(Err(PvmfwVerifyError::InvalidPageSize))?
+ .parse::<usize>()
+ .or(Err(PvmfwVerifyError::InvalidPageSize))?
+ .checked_mul(1024)
+ // TODO(stable(unsigned_is_multiple_of)): use .is_multiple_of()
+ .filter(|sz| sz % (4 << 10) == 0 && *sz != 0)
+ .ok_or(PvmfwVerifyError::InvalidPageSize)?;
+
+ Ok(Some(size))
+}
+
/// Verifies the given initrd partition, and checks that the resulting contents looks like expected.
fn verify_initrd(
ops: &mut Ops,
@@ -256,7 +274,7 @@
let descriptors = vbmeta_image.descriptors()?;
let hash_descriptors = HashDescriptors::get(&descriptors)?;
let capabilities = Capability::get_capabilities(vbmeta_image)?;
- let page_size = None; // TODO(ptosi): Read from payload.
+ let page_size = read_page_size(vbmeta_image)?;
if initrd.is_none() {
hash_descriptors.verify_no_initrd()?;
diff --git a/guest/pvmfw/avb/tests/api_test.rs b/guest/pvmfw/avb/tests/api_test.rs
index 1a4e247..0ed0279 100644
--- a/guest/pvmfw/avb/tests/api_test.rs
+++ b/guest/pvmfw/avb/tests/api_test.rs
@@ -28,6 +28,14 @@
use utils::*;
const TEST_IMG_WITH_ONE_HASHDESC_PATH: &str = "test_image_with_one_hashdesc.img";
+const TEST_IMG_WITH_INVALID_PAGE_SIZE_PATH: &str = "test_image_with_invalid_page_size.img";
+const TEST_IMG_WITH_NEGATIVE_PAGE_SIZE_PATH: &str = "test_image_with_negative_page_size.img";
+const TEST_IMG_WITH_OVERFLOW_PAGE_SIZE_PATH: &str = "test_image_with_overflow_page_size.img";
+const TEST_IMG_WITH_0K_PAGE_SIZE_PATH: &str = "test_image_with_0k_page_size.img";
+const TEST_IMG_WITH_1K_PAGE_SIZE_PATH: &str = "test_image_with_1k_page_size.img";
+const TEST_IMG_WITH_4K_PAGE_SIZE_PATH: &str = "test_image_with_4k_page_size.img";
+const TEST_IMG_WITH_9K_PAGE_SIZE_PATH: &str = "test_image_with_9k_page_size.img";
+const TEST_IMG_WITH_16K_PAGE_SIZE_PATH: &str = "test_image_with_16k_page_size.img";
const TEST_IMG_WITH_ROLLBACK_INDEX_5: &str = "test_image_with_rollback_index_5.img";
const TEST_IMG_WITH_SERVICE_VM_PROP_PATH: &str = "test_image_with_service_vm_prop.img";
const TEST_IMG_WITH_UNKNOWN_VM_TYPE_PROP_PATH: &str = "test_image_with_unknown_vm_type_prop.img";
@@ -240,6 +248,60 @@
}
#[test]
+fn kernel_has_expected_page_size_invalid() {
+ let kernel = fs::read(TEST_IMG_WITH_INVALID_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_negative() {
+ let kernel = fs::read(TEST_IMG_WITH_NEGATIVE_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_overflow() {
+ let kernel = fs::read(TEST_IMG_WITH_OVERFLOW_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_none() {
+ let kernel = fs::read(TEST_IMG_WITH_ONE_HASHDESC_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Ok(None));
+}
+
+#[test]
+fn kernel_has_expected_page_size_0k() {
+ let kernel = fs::read(TEST_IMG_WITH_0K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_1k() {
+ let kernel = fs::read(TEST_IMG_WITH_1K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_4k() {
+ let kernel = fs::read(TEST_IMG_WITH_4K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Ok(Some(4usize << 10)));
+}
+
+#[test]
+fn kernel_has_expected_page_size_9k() {
+ let kernel = fs::read(TEST_IMG_WITH_9K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Err(PvmfwVerifyError::InvalidPageSize));
+}
+
+#[test]
+fn kernel_has_expected_page_size_16k() {
+ let kernel = fs::read(TEST_IMG_WITH_16K_PAGE_SIZE_PATH).unwrap();
+ assert_eq!(read_page_size(&kernel), Ok(Some(16usize << 10)));
+}
+
+#[test]
fn kernel_footer_with_vbmeta_offset_overwritten_fails_verification() -> Result<()> {
// Arrange.
let mut kernel = load_latest_signed_kernel()?;
diff --git a/guest/pvmfw/avb/tests/utils.rs b/guest/pvmfw/avb/tests/utils.rs
index 86efbba..79552b5 100644
--- a/guest/pvmfw/avb/tests/utils.rs
+++ b/guest/pvmfw/avb/tests/utils.rs
@@ -173,6 +173,16 @@
Ok(())
}
+pub fn read_page_size(kernel: &[u8]) -> Result<Option<usize>, PvmfwVerifyError> {
+ let public_key = load_trusted_public_key().unwrap();
+ let verified_boot_data = verify_payload(
+ kernel,
+ None, // initrd
+ &public_key,
+ )?;
+ Ok(verified_boot_data.page_size)
+}
+
pub fn hash(inputs: &[&[u8]]) -> Digest {
let mut digester = sha::Sha256::new();
inputs.iter().for_each(|input| digester.update(input));