xtask: Add TPM VM tests that use uefi_test_tool

The new tests test a deactivated TPM and a TPM that appears valid but
has a failing PCR extend operation. These both make use of
uefi_test_tool to provide the TPM protocol.

BUG=b:302700196
TEST=cargo xtask check --vm-tests

Change-Id: I696fd3d845904ac2f39eb422da134dc52d623741
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crdyboot/+/5453733
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>
Commit-Queue: Nicholas Bishop <nicholasbishop@google.com>
Reviewed-by: Ted Brandston <tbrandston@google.com>
diff --git a/xtask/src/gen_disk.rs b/xtask/src/gen_disk.rs
index b6db7c0..afd9a2c 100644
--- a/xtask/src/gen_disk.rs
+++ b/xtask/src/gen_disk.rs
@@ -5,6 +5,7 @@
 use crate::arch::Arch;
 use crate::config::{Config, EfiExe};
 use crate::secure_boot::{self, SecureBootKeyPaths};
+use crate::vm_test::Operation;
 use anyhow::{bail, Context, Result};
 use camino::{Utf8Path, Utf8PathBuf};
 use command_run::Command;
@@ -680,6 +681,46 @@
     })
 }
 
+/// Install the signed `uefi_test_runner` executable as the first-stage
+/// bootloader (for VM testing).
+pub fn install_uefi_test_tool(conf: &Config, operation: Operation) -> Result<()> {
+    modify_system_partition(&conf.test_disk_path(), |root_dir| {
+        let efi_dir = root_dir.open_dir("EFI")?;
+        let boot_dir = efi_dir.open_dir("BOOT")?;
+
+        // Rename the boot executables to crdyshim.
+        boot_dir.rename("bootx64.efi", &boot_dir, "crdyshimx64.efi")?;
+        boot_dir.rename("bootia32.efi", &boot_dir, "crdyshimia32.efi")?;
+
+        // Create the test control file.
+        fat_write_file(
+            &boot_dir,
+            "crdy_test_control",
+            format!("{operation}\n").as_bytes(),
+        )?;
+
+        Ok(())
+    })?;
+
+    // Sign the test tool and copy it to the ESP.
+    SignAndUpdateBootloader {
+        disk_path: &conf.test_disk_path(),
+        key_paths: conf.secure_boot_root_key_paths(),
+        mapping: Arch::all()
+            .iter()
+            .map(|arch| {
+                (
+                    conf.target_exec_path(*arch, EfiExe::UefiTestTool),
+                    arch.efi_file_name("boot"),
+                )
+            })
+            .collect(),
+    }
+    .run()?;
+
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index a40e3f5..4881ea0 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+extern crate alloc;
+
 mod arch;
 mod bin_checks;
 mod config;
diff --git a/xtask/src/vm_test.rs b/xtask/src/vm_test.rs
index 503b8ef..a52b093 100644
--- a/xtask/src/vm_test.rs
+++ b/xtask/src/vm_test.rs
@@ -2,11 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Directly include the `Operation` enum from `uefi_test_tool`.
+mod operation {
+    include!("../../tools/uefi_test_tool/src/operation.rs");
+}
+pub use operation::Operation;
+
 use crate::arch::Arch;
 use crate::config::Config;
 use crate::gen_disk::{
     copy_partition_from_disk_to_disk, corrupt_crdyboot_signatures, corrupt_kern_a,
-    corrupt_pubkey_section, delete_crdyboot_signatures, SignAfterCorrupt, VerboseRuntimeLogs,
+    corrupt_pubkey_section, delete_crdyboot_signatures, install_uefi_test_tool, SignAfterCorrupt,
+    VerboseRuntimeLogs,
 };
 use crate::network::HttpsResource;
 use crate::qemu::{Display, QemuOpts};
@@ -360,6 +367,38 @@
     launch_test_disk_and_expect_output(conf, default_qemu_opts(conf), expected_output)
 }
 
+/// Test that a deactivated v1 TPM is correctly ignored.
+fn test_tpm1_deactivated_success(conf: &Config) -> Result<()> {
+    println!("test that a deactivated v1 TPM is correctly ignored");
+
+    install_uefi_test_tool(conf, Operation::Tpm1Deactivated)?;
+
+    let expected_output = &[
+        // Expect this message twice, first crdyshim then crdyboot:
+        "TPMv1 protocol exists, but TPM is deactivated",
+        "TPMv1 protocol exists, but TPM is deactivated",
+        // Indicates the kernel has launched:
+        "EFI stub: UEFI Secure Boot is enabled.",
+    ];
+    launch_test_disk_and_expect_output(conf, default_qemu_opts(conf), expected_output)
+}
+
+/// Test that an error from extending a v1 TPM PCR is correctly ignored.
+fn test_tpm1_extend_error_success(conf: &Config) -> Result<()> {
+    println!("test that an error from extending a v1 TPM PCR is correctly ignored");
+
+    install_uefi_test_tool(conf, Operation::Tpm1ExtendFail)?;
+
+    let expected_output = &[
+        // Expect this message twice, first crdyshim then crdyboot:
+        "failed to extend PCR: TPMv1 hash_log_extend_event failed: DEVICE_ERROR",
+        "failed to extend PCR: TPMv1 hash_log_extend_event failed: DEVICE_ERROR",
+        // Indicates the kernel has launched:
+        "EFI stub: UEFI Secure Boot is enabled.",
+    ];
+    launch_test_disk_and_expect_output(conf, default_qemu_opts(conf), expected_output)
+}
+
 pub fn run_vm_tests(conf: &Config) -> Result<()> {
     run_bootloader_build(conf, VerboseRuntimeLogs(true))?;
     download_test_key(conf)?;
@@ -367,6 +406,8 @@
     create_test_disk(conf)?;
 
     let tests = [
+        test_tpm1_deactivated_success,
+        test_tpm1_extend_error_success,
         test_missing_signature_prevents_crdyboot_launch,
         test_invalid_signature_prevents_crdyboot_launch,
         test_signed_vbpubk_mod_breaks_vboot,