Repository: chipsalliance/caliptra-sw
Pull request #189 (2023-04-20 22:27:53)

Don't complete failing mailbox txn until report_error()
This is a less controversial version of #185; it fixes the error race condition, but doesn't change the success completion, which still completes shortly after the firmware is copied from the mailbox into ICCM. The SoC can still use the mailbox for sha512_acc operations while the ROM or FMC are signing the DICE certs.

This prevents a potential race condition: CPTRA_FW_ERROR_NON_FATAL is
set N clock cycles after the mailbox transaction is aborted (by the
transaction Drop handler), so it is possible that the SoC will read the
error register too early and report a bogus error code.


Patch:
diff --git a/drivers/src/exit_ctrl.rs b/drivers/src/exit_ctrl.rs
index d11896fe..6dbbe517 100644
--- a/drivers/src/exit_ctrl.rs
+++ b/drivers/src/exit_ctrl.rs
@@ -12,6 +12,8 @@ Abstract:
 
 --*/
 
+use crate::Mailbox;
+
 /// Exit control
 pub enum ExitCtrl {}
 
@@ -33,7 +35,8 @@ impl ExitCtrl {
             }
         }
 
-        #[allow(clippy::empty_loop)]
-        loop {}
+        loop {
+            unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+        }
     }
 }
diff --git a/drivers/src/mailbox.rs b/drivers/src/mailbox.rs
index 2c835d09..70d71b5d 100644
--- a/drivers/src/mailbox.rs
+++ b/drivers/src/mailbox.rs
@@ -75,6 +75,28 @@ impl Mailbox {
             _ => None,
         }
     }
+
+    /// Aborts with failure any pending SoC->Uc transactions.
+    ///
+    /// This is useful to call from a fatal-error-handling routine.
+    ///
+    /// # Safety
+    ///
+    /// Callers must guarantee that no other code is interacting with the
+    /// mailbox at the time this function is called. (For example, any
+    /// MailboxRecvTxn and MailboxSendTxn instances have been destroyed or
+    /// forgotten).
+    ///
+    /// This function is safe to call from a trap handler.
+    pub unsafe fn abort_pending_soc_to_uc_transactions() {
+        let mbox = mbox::RegisterBlock::mbox_csr();
+        if mbox.status().read().mbox_fsm_ps().mbox_execute_uc() {
+            // SoC firmware might be stuck waiting for Caliptra to finish
+            // executing this pending mailbox transaction. Notify them that
+            // we've failed.
+            mbox.status().write(|w| w.status(|w| w.cmd_failure()));
+        }
+    }
 }
 
 #[derive(Default)]
diff --git a/fmc/src/main.rs b/fmc/src/main.rs
index 4554a97a..7d5f9c20 100644
--- a/fmc/src/main.rs
+++ b/fmc/src/main.rs
@@ -16,7 +16,7 @@ Abstract:
 use core::hint::black_box;
 
 use caliptra_common::cprintln;
-use caliptra_drivers::report_fw_error_non_fatal;
+use caliptra_drivers::{report_fw_error_non_fatal, Mailbox};
 mod flow;
 pub mod fmc_env;
 pub mod fmc_env_cell;
@@ -91,7 +91,12 @@ fn report_error(code: u32) -> ! {
     cprintln!("FMC Error: 0x{:08X}", code);
     report_fw_error_non_fatal(code);
 
-    loop {}
+    loop {
+        // SoC firmware might be stuck waiting for Caliptra to finish
+        // executing this pending mailbox transaction. Notify them that
+        // we've failed.
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[no_mangle]
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index 35dd0ebe..cd4a3358 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -274,32 +274,6 @@ pub trait HwModel {
         }
     }
 
-    fn copy_output_until_non_fatal_error(
-        &mut self,
-        expected_fw_nonfatal_error: u32,
-        mut w: impl std::io::Write,
-    ) -> std::io::Result<()> {
-        loop {
-            if self.soc_ifc().cptra_fw_error_non_fatal().read() == expected_fw_nonfatal_error {
-                return Ok(());
-            }
-
-            if !self.output().peek().is_empty() {
-                w.write_all(self.output().take(usize::MAX).as_bytes())?;
-            }
-            match self.output().exit_status() {
-                Some(ExitStatus::Passed) | Some(ExitStatus::Failed) => {
-                    return Err(std::io::Error::new(
-                        ErrorKind::Other,
-                        "firmware exited unexpectedly",
-                    ))
-                }
-                None => {}
-            }
-            self.step();
-        }
-    }
-
     /// Execute until the output contains `expected_output`.
     fn step_until_output(&mut self, expected_output: &str) -> Result<(), Box<dyn Error>> {
         self.step_until(|m| m.output().peek().len() >= expected_output.len());
diff --git a/rom/dev/src/flow/cold_reset/fmc_alias.rs b/rom/dev/src/flow/cold_reset/fmc_alias.rs
index 07375088..cdff8009 100644
--- a/rom/dev/src/flow/cold_reset/fmc_alias.rs
+++ b/rom/dev/src/flow/cold_reset/fmc_alias.rs
@@ -13,6 +13,8 @@ Abstract:
 
 --*/
 
+use core::mem::ManuallyDrop;
+
 use super::crypto::{Crypto, Ecc384KeyPair};
 use super::dice::{DiceInput, DiceLayer, DiceOutput};
 use super::x509::X509;
@@ -76,7 +78,6 @@ impl DiceLayer for FmcAliasLayer {
 
         // Complete the mailbox transaction indicating success.
         txn.complete(true)?;
-        drop(txn);
 
         // At this point PCR0 & PCR1 must have the same value. We use the value
         // of PCR1 as the UDS for deriving the CDI
@@ -121,8 +122,12 @@ impl FmcAliasLayer {
     ///
     /// # Returns
     ///
-    /// * `MailboxRecvTxn` - Mailbox transaction handle
-    fn download_image(env: &RomEnv) -> CaliptraResult<MailboxRecvTxn> {
+    /// Mailbox transaction handle. This transaction is ManuallyDrop because we
+    /// don't want the transaction to be completed with failure until after
+    /// report_error is called. This prevents a race condition where the SoC
+    /// reads FW_ERROR_NON_FATAL immediately after the mailbox transaction
+    /// fails, but before caliptra has set the FW_ERROR_NON_FATAL register.
+    fn download_image(env: &RomEnv) -> CaliptraResult<ManuallyDrop<MailboxRecvTxn>> {
         env.flow_status().map(|f| f.set_ready_for_firmware());
 
         cprint!("[afmc] Waiting for Image ");
@@ -134,10 +139,12 @@ impl FmcAliasLayer {
                     txn.complete(false)?;
                     continue;
                 }
-
+                // This is a download-firmware command; don't drop this, as the
+                // transaction will be completed by either report_error() (on
+                // failure) or by a manual complete call upon success.
+                let txn = ManuallyDrop::new(txn);
                 if txn.dlen() == 0 || txn.dlen() > IMAGE_BYTE_SIZE as u32 {
                     cprintln!("Invalid Image of size {} bytes" txn.dlen());
-                    txn.complete(false)?;
                     raise_err!(InvalidImageSize);
                 }
 
diff --git a/rom/dev/src/main.rs b/rom/dev/src/main.rs
index f8224353..4114867c 100644
--- a/rom/dev/src/main.rs
+++ b/rom/dev/src/main.rs
@@ -17,7 +17,7 @@ Abstract:
 use crate::lock::lock_registers;
 use core::hint::black_box;
 
-use caliptra_drivers::report_fw_error_non_fatal;
+use caliptra_drivers::{report_fw_error_non_fatal, Mailbox};
 use rom_env::RomEnv;
 
 #[cfg(not(feature = "std"))]
@@ -159,7 +159,12 @@ fn report_error(code: u32) -> ! {
     cprintln!("ROM Error: 0x{:08X}", code);
     report_fw_error_non_fatal(code);
 
-    loop {}
+    loop {
+        // SoC firmware might be stuck waiting for Caliptra to finish
+        // executing this pending mailbox transaction. Notify them that
+        // we've failed.
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[no_mangle]
diff --git a/runtime/src/main.rs b/runtime/src/main.rs
index e5d3c162..dd5f6fd3 100644
--- a/runtime/src/main.rs
+++ b/runtime/src/main.rs
@@ -16,7 +16,7 @@ Abstract:
 
 use caliptra_common::cprintln;
 use caliptra_cpu::TrapRecord;
-use caliptra_drivers::report_fw_error_non_fatal;
+use caliptra_drivers::{report_fw_error_non_fatal, Mailbox};
 use core::hint::black_box;
 
 #[cfg(feature = "std")]
@@ -98,7 +98,12 @@ fn runtime_panic(_: &core::panic::PanicInfo) -> ! {
 fn report_error(code: u32) -> ! {
     cprintln!("RT Error: 0x{:08X}", code);
     report_fw_error_non_fatal(code);
-    loop {}
+    loop {
+        // SoC firmware might be stuck waiting for Caliptra to finish
+        // executing this pending mailbox transaction. Notify them that
+        // we've failed.
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[no_mangle]


Test patch:
diff --git a/fmc/test-fw/test-rt/src/main.rs b/fmc/test-fw/test-rt/src/main.rs
index 15ec52d6..aa8ed3c3 100644
--- a/fmc/test-fw/test-rt/src/main.rs
+++ b/fmc/test-fw/test-rt/src/main.rs
@@ -16,7 +16,7 @@ Abstract:
 
 use caliptra_common::cprintln;
 use caliptra_cpu::TrapRecord;
-use caliptra_drivers::report_fw_error_non_fatal;
+use caliptra_drivers::{report_fw_error_non_fatal, Mailbox};
 use core::hint::black_box;
 
 #[cfg(feature = "std")]
@@ -98,7 +98,12 @@ fn fmc_panic(_: &core::panic::PanicInfo) -> ! {
 fn report_error(code: u32) -> ! {
     cprintln!("RT Error: 0x{:08X}", code);
     report_fw_error_non_fatal(code);
-    loop {}
+    loop {
+        // SoC firmware might be stuck waiting for Caliptra to finish
+        // executing this pending mailbox transaction. Notify them that
+        // we've failed.
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[no_mangle]
diff --git a/rom/dev/tests/test_fmcalias_derivation.rs b/rom/dev/tests/test_fmcalias_derivation.rs
index 41bd5fd3..12a8c0ab 100644
--- a/rom/dev/tests/test_fmcalias_derivation.rs
+++ b/rom/dev/tests/test_fmcalias_derivation.rs
@@ -12,15 +12,16 @@ const INVALID_IMAGE_SIZE: u32 = 0x02000003;
 fn test_zero_firmware_size() {
     let (mut hw, _image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Zero-sized firmware.
     assert_eq!(
         hw.upload_firmware(&[]).unwrap_err(),
         ModelError::MailboxCmdFailed
     );
-    let result = hw.copy_output_until_non_fatal_error(INVALID_IMAGE_SIZE, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        INVALID_IMAGE_SIZE
+    );
 }
 
 #[test]
@@ -31,7 +32,6 @@ fn test_firmware_gt_max_size() {
 
     let (mut hw, _image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Manually put the oversize data in the mailbox because
     // HwModel::upload_firmware won't let us.
@@ -42,6 +42,13 @@ fn test_firmware_gt_max_size() {
         hw.soc_mbox().datain().write(|_| i as u32);
     }
     hw.soc_mbox().execute().write(|w| w.execute(true));
-    let result = hw.copy_output_until_non_fatal_error(INVALID_IMAGE_SIZE, &mut output);
-    assert!(result.is_ok());
+    while hw.soc_mbox().status().read().status().cmd_busy() {
+        hw.step();
+    }
+    hw.soc_mbox().execute().write(|w| w.execute(false));
+
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        INVALID_IMAGE_SIZE
+    );
 }
diff --git a/rom/dev/tests/test_image_validation.rs b/rom/dev/tests/test_image_validation.rs
index 9e05194c..78c0dea5 100644
--- a/rom/dev/tests/test_image_validation.rs
+++ b/rom/dev/tests/test_image_validation.rs
@@ -89,9 +89,10 @@ fn test_invalid_manifest_marker() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let mut output = vec![];
-    let result = hw.copy_output_until_non_fatal_error(MANIFEST_MARKER_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        MANIFEST_MARKER_MISMATCH
+    );
 }
 
 #[test]
@@ -105,9 +106,10 @@ fn test_invalid_manifest_size() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let mut output = vec![];
-    let result = hw.copy_output_until_non_fatal_error(MANIFEST_SIZE_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        MANIFEST_SIZE_MISMATCH
+    );
 }
 
 #[test]
@@ -125,9 +127,10 @@ fn test_preamble_zero_vendor_pubkey_digest() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let mut output = vec![];
-    let result = hw.copy_output_until_non_fatal_error(VENDOR_PUB_KEY_DIGEST_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_PUB_KEY_DIGEST_INVALID
+    );
 }
 
 #[test]
@@ -145,9 +148,10 @@ fn test_preamble_vendor_pubkey_digest_mismatch() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let mut output = vec![];
-    let result = hw.copy_output_until_non_fatal_error(VENDOR_PUB_KEY_DIGEST_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_PUB_KEY_DIGEST_MISMATCH
+    );
 }
 
 #[test]
@@ -165,9 +169,10 @@ fn test_preamble_owner_pubkey_digest_mismatch() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let mut output = vec![];
-    let result = hw.copy_output_until_non_fatal_error(OWNER_PUB_KEY_DIGEST_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        OWNER_PUB_KEY_DIGEST_MISMATCH
+    );
 }
 
 #[test]
@@ -209,27 +214,27 @@ fn test_preamble_vendor_pubkey_revocation() {
             caliptra_builder::build_and_sign_image(&FMC_WITH_UART, &APP_WITH_UART, image_options)
                 .unwrap();
 
-        let result = if key_idx == LAST_KEY_IDX {
+        if key_idx == LAST_KEY_IDX {
             // Last key is never revoked.
             hw.upload_firmware(&image_bundle.to_bytes().unwrap())
                 .unwrap();
-            hw.copy_output_until_exit_success(&mut output)
+            hw.copy_output_until_exit_success(&mut output).unwrap();
         } else {
             assert_eq!(
                 ModelError::MailboxCmdFailed,
                 hw.upload_firmware(&image_bundle.to_bytes().unwrap())
                     .unwrap_err()
             );
-            hw.copy_output_until_non_fatal_error(VENDOR_ECC_PUB_KEY_REVOKED, &mut output)
-        };
-
-        assert!(result.is_ok());
+            assert_eq!(
+                hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+                VENDOR_ECC_PUB_KEY_REVOKED
+            );
+        }
     }
 }
 
 #[test]
 fn test_preamble_vendor_pubkey_out_of_bounds() {
-    let mut output = vec![];
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     image_bundle.manifest.preamble.vendor_ecc_pub_key_idx = VENDOR_ECC_KEY_COUNT;
@@ -239,10 +244,10 @@ fn test_preamble_vendor_pubkey_out_of_bounds() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(VENDOR_ECC_PUB_KEY_INDEX_OUT_OF_BOUNDS, &mut output);
-
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_ECC_PUB_KEY_INDEX_OUT_OF_BOUNDS
+    );
 }
 
 #[test]
@@ -250,7 +255,6 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     let vendor_ecc_pub_key_idx = image_bundle.manifest.preamble.vendor_ecc_pub_key_idx as usize;
-    let mut output = vec![];
 
     // Set ecc_pub_key.x to zero.
     let ecc_pub_key_x_backup =
@@ -265,9 +269,10 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
             .unwrap_err()
     );
 
-    let result =
-        hw.copy_output_until_non_fatal_error(VENDOR_PUB_KEY_DIGEST_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_PUB_KEY_DIGEST_INVALID_ARG
+    );
 
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
@@ -284,14 +289,14 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(VENDOR_PUB_KEY_DIGEST_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_PUB_KEY_DIGEST_INVALID_ARG
+    );
 }
 
 #[test]
 fn test_header_verify_vendor_sig_zero_signature() {
-    let mut output = vec![];
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
@@ -304,9 +309,10 @@ fn test_header_verify_vendor_sig_zero_signature() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(VENDOR_ECC_SIGNATURE_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_ECC_SIGNATURE_INVALID_ARG
+    );
 
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
@@ -320,9 +326,10 @@ fn test_header_verify_vendor_sig_zero_signature() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(VENDOR_ECC_SIGNATURE_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_ECC_SIGNATURE_INVALID_ARG
+    );
 }
 
 #[test]
@@ -330,7 +337,6 @@ fn test_header_verify_vendor_sig_mismatch() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     let vendor_ecc_pub_key_idx = image_bundle.manifest.preamble.vendor_ecc_pub_key_idx as usize;
-    let mut output = vec![];
 
     // Modify the owner public key.
     let ecc_pub_key_backup =
@@ -348,8 +354,10 @@ fn test_header_verify_vendor_sig_mismatch() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(VENDOR_ECC_SIGNATURE_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_ECC_SIGNATURE_INVALID
+    );
 
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
@@ -377,15 +385,16 @@ fn test_header_verify_vendor_sig_mismatch() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(VENDOR_ECC_SIGNATURE_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_ECC_SIGNATURE_INVALID
+    );
 }
 
 #[test]
 fn test_header_verify_vendor_pub_key_in_preamble_and_header() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Change vendor pubkey index.
     image_bundle.manifest.header.vendor_ecc_pub_key_idx =
@@ -397,15 +406,14 @@ fn test_header_verify_vendor_pub_key_in_preamble_and_header() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(VENDOR_ECC_PUB_KEY_INDEX_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        VENDOR_ECC_PUB_KEY_INDEX_MISMATCH
+    );
 }
 
 #[test]
 fn test_header_verify_owner_sig_zero_pubkey_x() {
-    let mut output = vec![];
-
     let mut image_bundle = caliptra_builder::build_and_sign_image(
         &FMC_WITH_UART,
         &APP_WITH_UART,
@@ -449,15 +457,14 @@ fn test_header_verify_owner_sig_zero_pubkey_x() {
             .unwrap_err()
     );
 
-    let result =
-        hw.copy_output_until_non_fatal_error(OWNER_PUB_KEY_DIGEST_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        OWNER_PUB_KEY_DIGEST_INVALID_ARG
+    );
 }
 
 #[test]
 fn test_header_verify_owner_sig_zero_pubkey_y() {
-    let mut output = vec![];
-
     let mut image_bundle = caliptra_builder::build_and_sign_image(
         &FMC_WITH_UART,
         &APP_WITH_UART,
@@ -501,15 +508,14 @@ fn test_header_verify_owner_sig_zero_pubkey_y() {
             .unwrap_err()
     );
 
-    let result =
-        hw.copy_output_until_non_fatal_error(OWNER_PUB_KEY_DIGEST_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        OWNER_PUB_KEY_DIGEST_INVALID_ARG
+    );
 }
 
 #[test]
 fn test_header_verify_owner_sig_zero_signature_r() {
-    let mut output = vec![];
-
     let mut image_bundle = caliptra_builder::build_and_sign_image(
         &FMC_WITH_UART,
         &APP_WITH_UART,
@@ -547,14 +553,14 @@ fn test_header_verify_owner_sig_zero_signature_r() {
             .unwrap_err()
     );
 
-    let result = hw.copy_output_until_non_fatal_error(OWNER_ECC_SIGNATURE_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        OWNER_ECC_SIGNATURE_INVALID_ARG
+    );
 }
 
 #[test]
 fn test_header_verify_owner_sig_zero_signature_s() {
-    let mut output = vec![];
-
     let mut image_bundle = caliptra_builder::build_and_sign_image(
         &FMC_WITH_UART,
         &APP_WITH_UART,
@@ -592,15 +598,16 @@ fn test_header_verify_owner_sig_zero_signature_s() {
             .unwrap_err()
     );
 
-    let result = hw.copy_output_until_non_fatal_error(OWNER_ECC_SIGNATURE_INVALID_ARG, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        OWNER_ECC_SIGNATURE_INVALID_ARG
+    );
 }
 
 #[test]
 fn test_toc_invalid_entry_count() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Change the TOC length.
     image_bundle.manifest.header.toc_len = caliptra_image_types::MAX_TOC_ENTRY_COUNT + 1;
@@ -611,15 +618,16 @@ fn test_toc_invalid_entry_count() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(TOC_ENTRY_COUNT_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        TOC_ENTRY_COUNT_INVALID
+    );
 }
 
 #[test]
 fn test_toc_invalid_toc_digest() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Change the TOC digest.
     image_bundle.manifest.header.toc_digest[0] = 0xDEADBEEF;
@@ -630,14 +638,14 @@ fn test_toc_invalid_toc_digest() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(TOC_DIGEST_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        TOC_DIGEST_MISMATCH
+    );
 }
 
 #[test]
 fn test_toc_fmc_range_overlap() {
-    let mut output = vec![];
-
     // Case 1: FMC offset == Runtime offset
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
@@ -658,8 +666,10 @@ fn test_toc_fmc_range_overlap() {
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_RUNTIME_OVERLAP, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_RUNTIME_OVERLAP
+    );
 
     // Case 2: FMC offset > Runtime offset
     let (mut hw, mut image_bundle) =
@@ -681,8 +691,10 @@ fn test_toc_fmc_range_overlap() {
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_RUNTIME_OVERLAP, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_RUNTIME_OVERLAP
+    );
 
     // // Case 3: FMC start offset < Runtime offset < FMC end offset
     let (mut hw, mut image_bundle) =
@@ -704,15 +716,16 @@ fn test_toc_fmc_range_overlap() {
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_RUNTIME_OVERLAP, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_RUNTIME_OVERLAP
+    );
 }
 
 #[test]
 fn test_toc_fmc_range_incorrect_order() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
     let fmc_new_offset = image_bundle.manifest.runtime.offset;
     let fmc_new_size = image_bundle.manifest.runtime.size;
     let runtime_new_offset = image_bundle.manifest.fmc.offset;
@@ -729,15 +742,16 @@ fn test_toc_fmc_range_incorrect_order() {
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_RUNTIME_INCORRECT_ORDER, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_RUNTIME_INCORRECT_ORDER
+    );
 }
 
 #[test]
 fn test_fmc_digest_mismatch() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Change the FMC image.
     image_bundle.fmc[0..4].copy_from_slice(0xDEADBEEFu32.as_bytes());
@@ -747,90 +761,96 @@ fn test_fmc_digest_mismatch() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_DIGEST_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_DIGEST_MISMATCH
+    );
 }
 
 #[test]
 fn test_fmc_invalid_load_addr_before_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_load_addr(&mut image_bundle, true, ICCM_START_ADDR - 4);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_LOAD_ADDR_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_LOAD_ADDR_INVALID
+    );
 }
 
 #[test]
 fn test_fmc_invalid_load_addr_after_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_load_addr(&mut image_bundle, true, ICCM_END_ADDR + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_LOAD_ADDR_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_LOAD_ADDR_INVALID
+    );
 }
 
 #[test]
 fn test_fmc_load_addr_unaligned() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
     let load_addr = image_bundle.manifest.fmc.load_addr;
     let image = update_load_addr(&mut image_bundle, true, load_addr + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_LOAD_ADDR_UNALIGNED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_LOAD_ADDR_UNALIGNED
+    );
 }
 
 #[test]
 fn test_fmc_invalid_entry_point_before_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_entry_point(&mut image_bundle, true, ICCM_START_ADDR - 4);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_ENTRY_POINT_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_ENTRY_POINT_INVALID
+    );
 }
 
 #[test]
 fn test_fmc_invalid_entry_point_after_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_entry_point(&mut image_bundle, true, ICCM_END_ADDR + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_ENTRY_POINT_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_ENTRY_POINT_INVALID
+    );
 }
 
 #[test]
 fn test_fmc_entry_point_unaligned() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
     let entry_point = image_bundle.manifest.fmc.entry_point;
 
     let image = update_entry_point(&mut image_bundle, true, entry_point + 1);
@@ -838,8 +858,10 @@ fn test_fmc_entry_point_unaligned() {
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_ENTRY_POINT_UNALIGNED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_ENTRY_POINT_UNALIGNED
+    );
 }
 
 #[test]
@@ -864,15 +886,15 @@ fn test_fmc_svn_greater_than_32() {
     };
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
-    let mut output = vec![];
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(FMC_SVN_GREATER_THAN_MAX_SUPPORTED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_SVN_GREATER_THAN_MAX_SUPPORTED
+    );
 }
 
 #[test]
@@ -897,14 +919,15 @@ fn test_fmc_svn_less_than_min_svn() {
         ..Default::default()
     };
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
-    let mut output = vec![];
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_SVN_LESS_THAN_MIN_SUPPORTED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_SVN_LESS_THAN_MIN_SUPPORTED
+    );
 }
 
 #[test]
@@ -930,21 +953,21 @@ fn test_fmc_svn_less_than_fuse_svn() {
     };
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
-    let mut output = vec![];
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(FMC_SVN_LESS_THAN_FUSE, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        FMC_SVN_LESS_THAN_FUSE
+    );
 }
 
 #[test]
 fn test_runtime_digest_mismatch() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     // Change the FMC image.
     image_bundle.runtime[0..4].copy_from_slice(0xDEADBEEFu32.as_bytes());
@@ -953,98 +976,106 @@ fn test_runtime_digest_mismatch() {
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_DIGEST_MISMATCH, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_DIGEST_MISMATCH
+    );
 }
 
 #[test]
 fn test_runtime_invalid_load_addr_before_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_load_addr(&mut image_bundle, false, ICCM_START_ADDR - 4);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_LOAD_ADDR_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_LOAD_ADDR_INVALID
+    );
 }
 
 #[test]
 fn test_runtime_invalid_load_addr_after_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_load_addr(&mut image_bundle, false, ICCM_END_ADDR + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_LOAD_ADDR_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_LOAD_ADDR_INVALID
+    );
 }
 
 #[test]
 fn test_runtime_load_addr_unaligned() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
     let load_addr = image_bundle.manifest.runtime.load_addr;
     let image = update_load_addr(&mut image_bundle, false, load_addr + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_LOAD_ADDR_UNALIGNED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_LOAD_ADDR_UNALIGNED
+    );
 }
 
 #[test]
 fn test_runtime_invalid_entry_point_before_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_entry_point(&mut image_bundle, false, ICCM_START_ADDR - 4);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_ENTRY_POINT_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_ENTRY_POINT_INVALID
+    );
 }
 
 #[test]
 fn test_runtime_invalid_entry_point_after_iccm() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
 
     let image = update_entry_point(&mut image_bundle, false, ICCM_END_ADDR + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_ENTRY_POINT_INVALID, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_ENTRY_POINT_INVALID
+    );
 }
 
 #[test]
 fn test_runtime_entry_point_unaligned() {
     let (mut hw, mut image_bundle) =
         helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
-    let mut output = vec![];
     let entry_point = image_bundle.manifest.runtime.entry_point;
     let image = update_entry_point(&mut image_bundle, false, entry_point + 1);
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image).unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_ENTRY_POINT_UNALIGNED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_ENTRY_POINT_UNALIGNED
+    );
 }
 
 #[test]
@@ -1068,15 +1099,15 @@ fn test_runtime_svn_greater_than_64() {
     };
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
-    let mut output = vec![];
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(RUNTIME_SVN_GREATER_THAN_MAX_SUPPORTED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_SVN_GREATER_THAN_MAX_SUPPORTED
+    );
 }
 
 #[test]
@@ -1101,16 +1132,16 @@ fn test_runtime_svn_less_than_min_svn() {
     };
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
-    let mut output = vec![];
 
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result =
-        hw.copy_output_until_non_fatal_error(RUNTIME_SVN_LESS_THAN_MIN_SUPPORTED, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_SVN_LESS_THAN_MIN_SUPPORTED
+    );
 }
 
 #[test]
@@ -1136,14 +1167,15 @@ fn test_runtime_svn_less_than_fuse_svn() {
     };
 
     let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses, image_options);
-    let mut output = vec![];
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap_err()
     );
-    let result = hw.copy_output_until_non_fatal_error(RUNTIME_SVN_LESS_THAN_FUSE, &mut output);
-    assert!(result.is_ok());
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        RUNTIME_SVN_LESS_THAN_FUSE
+    );
 }
 
 fn update_header(image_bundle: &mut ImageBundle) {
diff --git a/rom/dev/tests/test_mailbox_errors.rs b/rom/dev/tests/test_mailbox_errors.rs
new file mode 100644
index 00000000..6f697919
--- /dev/null
+++ b/rom/dev/tests/test_mailbox_errors.rs
@@ -0,0 +1,55 @@
+// Licensed under the Apache-2.0 license
+
+use caliptra_builder::ImageOptions;
+use caliptra_hw_model::{Fuses, HwModel, ModelError};
+
+mod helpers;
+
+// [TODO] Use the error codes from the common library.
+const INVALID_IMAGE_SIZE: u32 = 0x02000003;
+
+#[test]
+fn test_unknown_command_is_not_fatal() {
+    let (mut hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+
+    // This command does not exist
+    assert_eq!(
+        hw.mailbox_execute(0xabcd_1234, &[]),
+        Err(ModelError::MailboxCmdFailed)
+    );
+
+    // The ROM does not currently report an error for this
+    // TODO: Is this right?
+    assert_eq!(hw.soc_ifc().cptra_fw_error_non_fatal().read(), 0);
+
+    // Make sure we can still upload new firmware after the unknown
+    // command.
+    hw.upload_firmware(&image_bundle.to_bytes().unwrap())
+        .unwrap();
+}
+
+#[test]
+fn test_mailbox_command_aborted_after_report_error() {
+    let (mut hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    assert_eq!(Err(ModelError::MailboxCmdFailed), hw.upload_firmware(&[]));
+
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        INVALID_IMAGE_SIZE
+    );
+
+    // Make sure a new attempt to upload firmware is rejected (even though this
+    // command would otherwise succeed)
+    assert_eq!(
+        hw.upload_firmware(&image_bundle.to_bytes().unwrap()),
+        Err(ModelError::MailboxCmdFailed)
+    );
+
+    // The original failure reason should still be in the register
+    assert_eq!(
+        hw.soc_ifc().cptra_fw_error_non_fatal().read(),
+        INVALID_IMAGE_SIZE
+    );
+}
diff --git a/rom/dev/tools/test-fmc/src/main.rs b/rom/dev/tools/test-fmc/src/main.rs
index 064eec98..8a398d24 100644
--- a/rom/dev/tools/test-fmc/src/main.rs
+++ b/rom/dev/tools/test-fmc/src/main.rs
@@ -15,6 +15,7 @@ Abstract:
 #![cfg_attr(not(feature = "std"), no_main)]
 
 use caliptra_common::FirmwareHandoffTable;
+use caliptra_drivers::Mailbox;
 use zerocopy::FromBytes;
 
 #[cfg(not(feature = "std"))]
@@ -80,7 +81,9 @@ extern "C" fn exception_handler(exception: &exception::ExceptionRecord) {
 
     // TODO: Signal non-fatal error to SOC
 
-    loop {}
+    loop {
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[no_mangle]
@@ -94,7 +97,9 @@ extern "C" fn nmi_handler(exception: &exception::ExceptionRecord) {
         exception.mepc
     );
 
-    loop {}
+    loop {
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[panic_handler]
diff --git a/rom/dev/tools/test-rt/src/main.rs b/rom/dev/tools/test-rt/src/main.rs
index 40af8bdd..ab3b5819 100644
--- a/rom/dev/tools/test-rt/src/main.rs
+++ b/rom/dev/tools/test-rt/src/main.rs
@@ -14,6 +14,8 @@ Abstract:
 #![cfg_attr(not(feature = "std"), no_std)]
 #![cfg_attr(not(feature = "std"), no_main)]
 
+use caliptra_drivers::Mailbox;
+
 #[cfg(not(feature = "std"))]
 core::arch::global_asm!(include_str!("start.S"));
 
@@ -52,7 +54,9 @@ extern "C" fn exception_handler(exception: &exception::ExceptionRecord) {
 
     // TODO: Signal non-fatal error to SOC
 
-    loop {}
+    loop {
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[no_mangle]
@@ -66,7 +70,9 @@ extern "C" fn nmi_handler(exception: &exception::ExceptionRecord) {
         exception.mepc
     );
 
-    loop {}
+    loop {
+        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+    }
 }
 
 #[panic_handler]
diff --git a/test-harness/src/lib.rs b/test-harness/src/lib.rs
index 2b412c42..4ecf1fec 100644
--- a/test-harness/src/lib.rs
+++ b/test-harness/src/lib.rs
@@ -97,7 +97,10 @@ macro_rules! test_suite {
                     use caliptra_drivers::ExitCtrl;
                     ExitCtrl::exit(u32::MAX);
                 } else {
-                    loop {}
+                    loop {
+                        use caliptra_drivers::Mailbox;
+                        unsafe { Mailbox::abort_pending_soc_to_uc_transactions() };
+                    }
                 }
             }
         }