uefi_test_tool: Implement v1 TPM operations
Add support for installing a stub TPM protocol. This will allow us to
test TPM errors.
BUG=b:302700196
TEST=cargo xtask check
Change-Id: I6fe2e6d3b7fff99a7f6f4b14be83f29327e90a0d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crdyboot/+/5479911
Tested-by: Nicholas Bishop <nicholasbishop@google.com>
Tested-by: chromeos-cop-builder@chromeos-cop.iam.gserviceaccount.com <chromeos-cop-builder@chromeos-cop.iam.gserviceaccount.com>
Reviewed-by: Ted Brandston <tbrandston@google.com>
Commit-Queue: Nicholas Bishop <nicholasbishop@google.com>
diff --git a/tools/uefi_test_tool/src/main.rs b/tools/uefi_test_tool/src/main.rs
index 450c73f..a820082 100644
--- a/tools/uefi_test_tool/src/main.rs
+++ b/tools/uefi_test_tool/src/main.rs
@@ -9,6 +9,7 @@
mod launch;
mod operation;
+mod tpm_v1;
use core::mem;
use core::sync::atomic::{AtomicU32, Ordering};
@@ -70,7 +71,9 @@
match Operation::get() {
Operation::Unset => unreachable!(),
- Operation::Tpm1Deactivated | Operation::Tpm1ExtendFail => todo!(),
+ Operation::Tpm1Deactivated | Operation::Tpm1ExtendFail => {
+ tpm_v1::create_tpm1(st.boot_services())
+ }
}
launch::launch_crdyshim(st.boot_services());
diff --git a/tools/uefi_test_tool/src/tpm_v1.rs b/tools/uefi_test_tool/src/tpm_v1.rs
new file mode 100644
index 0000000..1282b6a
--- /dev/null
+++ b/tools/uefi_test_tool/src/tpm_v1.rs
@@ -0,0 +1,129 @@
+// Copyright 2024 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::operation::Operation;
+use core::sync::atomic::{AtomicU32, Ordering};
+use uefi::data_types::PhysicalAddress;
+use uefi::proto::tcg::v1;
+use uefi::table::boot::BootServices;
+use uefi::{Identify, Status};
+
+/// Install a TPM v1 protocol. This isn't a real TPM, just enough to
+/// test some error cases (e.g. the behavior of crdyshim if the TPM is
+/// deactivated).
+pub fn create_tpm1(boot_services: &BootServices) {
+ static TPM_V1: TpmV1 = TpmV1 {
+ status_check,
+ hash_log_extend_event,
+
+ // These aren't called, so don't bother implementing.
+ hash_all: not_implemented,
+ log_event: not_implemented,
+ pass_through_to_tpm: not_implemented,
+ };
+
+ let interface: *const _ = &TPM_V1;
+
+ // SAFETY: The layout of `TPM_V1` matches the spec, and the GUID is
+ // correct.
+ unsafe { boot_services.install_protocol_interface(None, &v1::Tcg::GUID, interface.cast()) }
+ .unwrap();
+}
+
+/// Get TPM status. Depending on the operation, the TPM may be deactivated.
+extern "efiapi" fn status_check(
+ _this: *mut TpmV1,
+ protocol_capability: *mut TpmV1Capability,
+ _feature_flags: *mut u32,
+ _event_log_location: *mut PhysicalAddress,
+ _event_log_last_entry: *mut PhysicalAddress,
+) -> Status {
+ let caps = TpmV1Capability {
+ tpm_present_flag: 1,
+ tpm_deactivated_flag: if Operation::get() == Operation::Tpm1Deactivated {
+ 1
+ } else {
+ 0
+ },
+ ..TpmV1Capability::default()
+ };
+ unsafe {
+ *protocol_capability = caps;
+ }
+ Status::SUCCESS
+}
+
+/// Stub implementation for extending a PCR. Just returns a status code
+/// that depends on the operation.
+unsafe extern "efiapi" fn hash_log_extend_event(
+ _this: *mut TpmV1,
+ _hash_data: PhysicalAddress,
+ _hash_data_len: u64,
+ _algorithm_id: u32,
+ _event: *mut v1::FfiPcrEvent,
+ _event_number: *mut u32,
+ _event_log_last_entry: *mut PhysicalAddress,
+) -> Status {
+ static CALL_COUNT: AtomicU32 = AtomicU32::new(0);
+
+ // Get the current call count, and increment the static.
+ let call_count = CALL_COUNT.fetch_add(1, Ordering::Acquire);
+
+ // The initial calls to this function happen implicitly in the
+ // firmware when loading crdyshim. Always return success for those,
+ // otherwise the load will fail.
+ //
+ // There's no guarantee that the behavior of OVMF won't change in
+ // the future, but if it does, the test will fail rather than
+ // silently passing. If `call_count` is too low, crdyshim won't be
+ // loaded. If too high, the test won't see the expected
+ // `DEVICE_ERROR` log.
+ if call_count <= 2 {
+ return Status::SUCCESS;
+ }
+
+ if Operation::get() == Operation::Tpm1ExtendFail {
+ Status::DEVICE_ERROR
+ } else {
+ Status::SUCCESS
+ }
+}
+
+extern "efiapi" fn not_implemented() -> Status {
+ unimplemented!()
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct TpmV1Capability {
+ size: u8,
+ structure_version: [u8; 4],
+ protocol_spec_version: [u8; 4],
+ hash_algorithm_bitmap: u8,
+ tpm_present_flag: u8,
+ tpm_deactivated_flag: u8,
+}
+
+#[repr(C)]
+pub struct TpmV1 {
+ status_check: unsafe extern "efiapi" fn(
+ this: *mut Self,
+ protocol_capability: *mut TpmV1Capability,
+ feature_flags: *mut u32,
+ event_log_location: *mut PhysicalAddress,
+ event_log_last_entry: *mut PhysicalAddress,
+ ) -> Status,
+ hash_all: unsafe extern "efiapi" fn() -> Status,
+ log_event: unsafe extern "efiapi" fn() -> Status,
+ pass_through_to_tpm: unsafe extern "efiapi" fn() -> Status,
+ hash_log_extend_event: unsafe extern "efiapi" fn(
+ this: *mut Self,
+ hash_data: PhysicalAddress,
+ hash_data_len: u64,
+ algorithm_id: u32,
+ event: *mut v1::FfiPcrEvent,
+ event_number: *mut u32,
+ event_log_last_entry: *mut PhysicalAddress,
+ ) -> Status,
+}