[avb] Define rust API for image verification and connect pvmfw
This CL
- defines the image verification rust API using avb_bindgen.
- defines the return error enum for the API.
- invokes the API from pvmfw.
- adds e2e test for the pvmfw run without error scenario.
Bug: 256148034
Test: atest MicrodroidHostTests
Change-Id: I272e9b8031e34137dca761b66ffabab264f055dd
diff --git a/libs/avb_bindgen/Android.bp b/libs/avb/Android.bp
similarity index 67%
rename from libs/avb_bindgen/Android.bp
rename to libs/avb/Android.bp
index 80b96a6..28e969d 100644
--- a/libs/avb_bindgen/Android.bp
+++ b/libs/avb/Android.bp
@@ -13,6 +13,9 @@
bindgen_flags: [
"--size_t-is-usize",
"--allowlist-function=.*",
+ "--use-core",
+ "--raw-line=#![no_std]",
+ "--ctypes-prefix=core::ffi",
],
static_libs: [
"libavb",
@@ -33,3 +36,18 @@
clippy_lints: "none",
lints: "none",
}
+
+rust_library_rlib {
+ name: "libavb_nostd",
+ crate_name: "avb_nostd",
+ srcs: ["src/lib.rs"],
+ no_stdlibs: true,
+ prefer_rlib: true,
+ stdlibs: [
+ "libcore.rust_sysroot",
+ ],
+ rustlibs: [
+ "libavb_bindgen",
+ "liblog_rust_nostd",
+ ],
+}
diff --git a/libs/avb_bindgen/bindgen/avb.h b/libs/avb/bindgen/avb.h
similarity index 100%
rename from libs/avb_bindgen/bindgen/avb.h
rename to libs/avb/bindgen/avb.h
diff --git a/libs/avb/src/avb_ops.rs b/libs/avb/src/avb_ops.rs
new file mode 100644
index 0000000..900e152
--- /dev/null
+++ b/libs/avb/src/avb_ops.rs
@@ -0,0 +1,153 @@
+// Copyright 2022, 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.
+
+//! This module regroups methods related to AvbOps.
+
+#![warn(unsafe_op_in_unsafe_fn)]
+// TODO(b/256148034): Remove this when the feature is code complete.
+#![allow(dead_code)]
+#![allow(unused_imports)]
+
+extern crate alloc;
+
+use alloc::ffi::CString;
+use avb_bindgen::{
+ avb_slot_verify, AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
+ AvbSlotVerifyFlags_AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK,
+};
+use core::fmt;
+use log::debug;
+
+/// Error code from AVB image verification.
+#[derive(Clone, Copy, Debug)]
+pub enum AvbImageVerifyError {
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT
+ InvalidArgument,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
+ InvalidMetadata,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO
+ Io,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM
+ Oom,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
+ PublicKeyRejected,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX
+ RollbackIndex,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION
+ UnsupportedVersion,
+ /// AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
+ Verification,
+ /// Unknown error.
+ Unknown(u32),
+}
+
+fn to_avb_verify_result(result: u32) -> Result<(), AvbImageVerifyError> {
+ #[allow(non_upper_case_globals)]
+ match result {
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK => Ok(()),
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => {
+ Err(AvbImageVerifyError::InvalidArgument)
+ }
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => {
+ Err(AvbImageVerifyError::InvalidMetadata)
+ }
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(AvbImageVerifyError::Io),
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(AvbImageVerifyError::Oom),
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => {
+ Err(AvbImageVerifyError::PublicKeyRejected)
+ }
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => {
+ Err(AvbImageVerifyError::RollbackIndex)
+ }
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => {
+ Err(AvbImageVerifyError::UnsupportedVersion)
+ }
+ AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => {
+ Err(AvbImageVerifyError::Verification)
+ }
+ _ => Err(AvbImageVerifyError::Unknown(result)),
+ }
+}
+
+impl fmt::Display for AvbImageVerifyError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::InvalidArgument => write!(f, "Invalid parameters."),
+ Self::InvalidMetadata => write!(f, "Invalid metadata."),
+ Self::Io => write!(f, "I/O error while trying to load data or get a rollback index."),
+ Self::Oom => write!(f, "Unable to allocate memory."),
+ Self::PublicKeyRejected => write!(
+ f,
+ "Everything is verified correctly out but the public key is not accepted. \
+ This includes the case where integrity data is not signed."
+ ),
+ Self::RollbackIndex => write!(f, "Rollback index is less than its stored value."),
+ Self::UnsupportedVersion => write!(
+ f,
+ "Some of the metadata requires a newer version of libavb than what is in use."
+ ),
+ Self::Verification => write!(f, "Data does not verify."),
+ Self::Unknown(e) => write!(f, "Unknown avb_slot_verify error '{e}'"),
+ }
+ }
+}
+
+/// Verifies that for the given image:
+/// - The given public key is acceptable.
+/// - The VBMeta struct is valid.
+/// - The partitions of the image match the descriptors of the verified VBMeta struct.
+/// Returns Ok if everything is verified correctly and the public key is accepted.
+pub fn verify_image(image: &[u8], public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
+ AvbOps::new().verify_image(image, public_key)
+}
+
+/// TODO(b/256148034): Make AvbOps a rust wrapper of avb_bindgen::AvbOps using foreign_types.
+struct AvbOps {}
+
+impl AvbOps {
+ fn new() -> Self {
+ AvbOps {}
+ }
+
+ fn verify_image(&self, image: &[u8], public_key: &[u8]) -> Result<(), AvbImageVerifyError> {
+ debug!("AVB image: addr={:?}, size={:#x} ({1})", image.as_ptr(), image.len());
+ debug!(
+ "AVB public key: addr={:?}, size={:#x} ({1})",
+ public_key.as_ptr(),
+ public_key.len()
+ );
+ // TODO(b/256148034): Verify the kernel image with avb_slot_verify()
+ // let result = unsafe {
+ // avb_slot_verify(
+ // self.as_ptr(),
+ // requested_partitions.as_ptr(),
+ // ab_suffix.as_ptr(),
+ // AvbSlotVerifyFlags_AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION,
+ // AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
+ // &image.as_ptr(),
+ // )
+ // };
+ let result = AvbSlotVerifyResult_AVB_SLOT_VERIFY_RESULT_OK;
+ to_avb_verify_result(result)
+ }
+}
diff --git a/libs/avb/src/lib.rs b/libs/avb/src/lib.rs
new file mode 100644
index 0000000..81b554d
--- /dev/null
+++ b/libs/avb/src/lib.rs
@@ -0,0 +1,21 @@
+// Copyright 2022, 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.
+
+//! This module regroups the rust API for libavb.
+
+#![no_std]
+
+mod avb_ops;
+
+pub use avb_ops::{verify_image, AvbImageVerifyError};
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index b78e077..0da24c7 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -13,6 +13,7 @@
],
rustlibs: [
"libaarch64_paging",
+ "libavb_nostd",
"libbuddy_system_allocator",
"liblibfdt",
"liblog_rust_nostd",
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 7859ff3..a274210 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -45,6 +45,8 @@
InvalidPayload,
/// The provided ramdisk was invalid.
InvalidRamdisk,
+ /// Failed to verify the payload.
+ PayloadVerificationError,
}
main!(start);
@@ -223,7 +225,10 @@
let slices = MemorySlices::new(fdt, payload, payload_size, &mut memory)?;
// This wrapper allows main() to be blissfully ignorant of platform details.
- crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc);
+ crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc).map_err(|e| {
+ error!("Failed to verify the payload: {e}");
+ RebootReason::PayloadVerificationError
+ })?;
// TODO: Overwrite BCC before jumping to payload to avoid leaking our sealing key.
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 6810fda..3d5629a 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -32,9 +32,16 @@
mod smccc;
use avb::PUBLIC_KEY;
+use avb_nostd::{verify_image, AvbImageVerifyError};
use log::{debug, info};
-fn main(fdt: &libfdt::Fdt, signed_kernel: &[u8], ramdisk: Option<&[u8]>, bcc: &[u8]) {
+/// TODO(b/256148034): Return RebootReason as error here
+fn main(
+ fdt: &libfdt::Fdt,
+ signed_kernel: &[u8],
+ ramdisk: Option<&[u8]>,
+ bcc: &[u8],
+) -> Result<(), AvbImageVerifyError> {
info!("pVM firmware");
debug!("FDT: {:?}", fdt as *const libfdt::Fdt);
debug!("Signed kernel: {:?} ({:#x} bytes)", signed_kernel.as_ptr(), signed_kernel.len());
@@ -44,6 +51,7 @@
debug!("Ramdisk: None");
}
debug!("BCC: {:?} ({:#x} bytes)", bcc.as_ptr(), bcc.len());
- debug!("AVB public key: addr={:?}, size={:#x} ({1})", PUBLIC_KEY.as_ptr(), PUBLIC_KEY.len());
- info!("Starting payload...");
+ verify_image(signed_kernel, PUBLIC_KEY)?;
+ info!("Payload verified. Starting payload...");
+ Ok(())
}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 231fc7b..bf2d411 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -408,6 +408,31 @@
@Test
@CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
+ public void protectedVmWithValidKernelImageRunsPvmfw() throws Exception {
+ // Arrange
+ boolean protectedVm = true;
+ assumeTrue(
+ "Skip if protected VMs are not supported",
+ getAndroidDevice().supportsMicrodroid(protectedVm));
+ File key = findTestFile("test.com.android.virt.pem");
+
+ // Act
+ // TODO(b/256148034): Do not resign kernel image
+ VmInfo vmInfo =
+ runMicrodroidWithResignedImages(key, /*keyOverrides=*/ Map.of(), protectedVm);
+
+ // Assert
+ vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS);
+ String consoleLog = getDevice().pullFileContents(CONSOLE_PATH);
+ assertWithMessage("pvmfw should start").that(consoleLog).contains("pVM firmware");
+ assertWithMessage("pvmfw should start payload")
+ .that(consoleLog)
+ .contains("Payload verified. Starting payload...");
+ vmInfo.mProcess.destroy();
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-2-1", "9.17/C-2-2", "9.17/C-2-6"})
public void protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() throws Exception {
// Arrange
boolean protectedVm = true;
@@ -420,7 +445,7 @@
VmInfo vmInfo =
runMicrodroidWithResignedImages(key, /*keyOverrides=*/ Map.of(), protectedVm);
- // Asserts
+ // Assert
vmInfo.mProcess.waitFor(5L, TimeUnit.SECONDS);
String consoleLog = getDevice().pullFileContents(CONSOLE_PATH);
assertWithMessage("pvmfw should start").that(consoleLog).contains("pVM firmware");