Repository: chipsalliance/caliptra-sw
Pull request #179 (2023-04-14 20:54:51)

[feat] Additonal checks in image verifications
This change adds the following checks:
	1. Check for validity of the load and entry point address of FMC and Runtime.
	2. SVN validity

Additional integration tests are added for testing these conditions.


Patch:
diff --git a/builder/src/lib.rs b/builder/src/lib.rs
index 507aba54..9ecb0c2c 100644
--- a/builder/src/lib.rs
+++ b/builder/src/lib.rs
@@ -169,10 +169,10 @@ pub fn elf2rom(elf_bytes: &[u8]) -> io::Result<Vec<u8>> {
 }
 
 pub struct ImageOptions {
-    fmc_min_svn: u32,
-    fmc_svn: u32,
-    app_min_svn: u32,
-    app_svn: u32,
+    pub fmc_min_svn: u32,
+    pub fmc_svn: u32,
+    pub app_min_svn: u32,
+    pub app_svn: u32,
     pub vendor_config: ImageGeneratorVendorConfig,
     pub owner_config: Option<ImageGeneratorOwnerConfig>,
 }
@@ -200,14 +200,14 @@ pub fn build_and_sign_image(
     let image = gen.generate(&ImageGeneratorConfig {
         fmc: ElfExecutable::new(
             &fmc_elf,
-            opts.fmc_min_svn,
             opts.fmc_svn,
+            opts.fmc_min_svn,
             image_revision_from_git_repo()?,
         )?,
         runtime: ElfExecutable::new(
             &app_elf,
-            opts.app_min_svn,
             opts.app_svn,
+            opts.app_min_svn,
             image_revision_from_git_repo()?,
         )?,
         vendor_config: opts.vendor_config,
diff --git a/drivers/src/fuse_bank.rs b/drivers/src/fuse_bank.rs
index 6226483d..831c8901 100644
--- a/drivers/src/fuse_bank.rs
+++ b/drivers/src/fuse_bank.rs
@@ -210,9 +210,12 @@ impl FuseBank {
     /// # Returns
     ///     fmc security version number
     ///
-    pub fn fmc_svn() -> u32 {
+    pub fn fmc_svn(&self) -> u32 {
         let soc_ifc_regs = soc_ifc::RegisterBlock::soc_ifc_reg();
-        soc_ifc_regs.fuse_fmc_key_manifest_svn().read()
+        32 - soc_ifc_regs
+            .fuse_fmc_key_manifest_svn()
+            .read()
+            .leading_zeros()
     }
 
     /// Get the runtime security version number.
@@ -223,9 +226,10 @@ impl FuseBank {
     /// # Returns
     ///     runtime security version number
     ///
-    pub fn runtime_svn() -> u64 {
+    pub fn runtime_svn(&self) -> u32 {
         let soc_ifc_regs = soc_ifc::RegisterBlock::soc_ifc_reg();
-        (soc_ifc_regs.fuse_runtime_svn().at(1).read() as u64) << 32
-            | soc_ifc_regs.fuse_runtime_svn().at(0).read() as u64
+        64 - ((soc_ifc_regs.fuse_runtime_svn().at(1).read() as u64) << 32
+            | soc_ifc_regs.fuse_runtime_svn().at(0).read() as u64)
+            .leading_zeros()
     }
 }
diff --git a/image/gen/src/generator.rs b/image/gen/src/generator.rs
index 8d1296df..ec421b8d 100644
--- a/image/gen/src/generator.rs
+++ b/image/gen/src/generator.rs
@@ -165,6 +165,12 @@ impl<Crypto: ImageGeneratorCrypto> ImageGenerator<Crypto> {
             .sha384_digest(preamble.owner_pub_keys.as_bytes())
     }
 
+    /// Calculate vendor public key(s) digest
+    pub fn vendor_pubkey_digest(&self, preamble: &ImagePreamble) -> anyhow::Result<ImageDigest> {
+        self.crypto
+            .sha384_digest(preamble.vendor_pub_keys.as_bytes())
+    }
+
     /// Generate image
     fn gen_image<E>(
         &self,
diff --git a/image/verify/src/lib.rs b/image/verify/src/lib.rs
index f3077364..ef55d9d9 100644
--- a/image/verify/src/lib.rs
+++ b/image/verify/src/lib.rs
@@ -17,6 +17,7 @@ mod verifier;
 
 use caliptra_drivers::*;
 use caliptra_image_types::*;
+use core::ops::Range;
 
 pub use verifier::ImageVerifier;
 
@@ -99,4 +100,13 @@ pub trait ImageVerificationEnv {
 
     // Save the fmc digest in the data vault on cold boot
     fn get_fmc_digest_dv(&self) -> ImageDigest;
+
+    // Get Fuse FMC Key Manifest SVN
+    fn fmc_svn(&self) -> u32;
+
+    // Get Runtime fuse SVN
+    fn runtime_svn(&self) -> u32;
+
+    // ICCM Range
+    fn iccm_range(&self) -> Range<u32>;
 }
diff --git a/image/verify/src/verifier.rs b/image/verify/src/verifier.rs
index 34e40c7c..f6f4c53d 100644
--- a/image/verify/src/verifier.rs
+++ b/image/verify/src/verifier.rs
@@ -53,6 +53,20 @@ caliptra_err_def! {
         UpdateResetVenPubKeyIdxMismatch = 30,
         UpdateResetFmcDigestMismatch = 31,
         UpdateResetVenPubKeyIdxOutOfBounds = 32,
+        FmcLoadAddrInvalid = 33,
+        FmcLoadAddrUnaligned = 34,
+        FmcEntryPointInvalid = 35,
+        FmcEntryPointUnaligned = 36,
+        FmcSvnGreaterThanMaxSupported = 37,
+        FmcSvnLessThanMinSupported = 38,
+        FmcSvnLessThanFuse = 39,
+        RuntimeLoadAddrInvalid = 40,
+        RuntimeLoadAddrUnaligned = 41,
+        RuntimeEntryPointInvalid = 42,
+        RuntimeEntryPointUnaligned = 43,
+        RuntimeSvnGreaterThanMaxSupported = 44,
+        RuntimeSvnLessThanMinSupported = 45,
+        RuntimeSvnLessThanFuse = 46,
     }
 }
 
@@ -437,6 +451,13 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
         Ok(info)
     }
 
+    // Check if SVN check is required
+    fn svn_check_required(&self, image: Env::Image) -> bool {
+        // If device is unprovisioned or if anti-rollback is disabled, don't check the SVN.
+        !(self.env.dev_lifecycle(image) == Lifecycle::Unprovisioned
+            || self.env.anti_rollback_disable(image))
+    }
+
     /// Verify FMC
     fn verify_fmc(
         &self,
@@ -455,10 +476,39 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             raise_err!(FmcDigestMismatch)
         }
 
-        // TODO: Perform following Address checks
-        // 1. Load address is a valid ICCM Address
-        // 2. Entry Point is a valid ICCM Address
-        // 3. Entry Point is within the image
+        // TODO: Perform following Address check
+        // Entry Point is within the image
+        if !self.env.iccm_range().contains(&verify_info.load_addr) {
+            raise_err!(FmcLoadAddrInvalid)
+        }
+        if verify_info.load_addr % 4 != 0 {
+            raise_err!(FmcLoadAddrUnaligned)
+        }
+
+        if !self.env.iccm_range().contains(&verify_info.entry_point) {
+            raise_err!(FmcEntryPointInvalid)
+        }
+        if verify_info.entry_point % 4 != 0 {
+            raise_err!(FmcEntryPointUnaligned)
+        }
+
+        if self.svn_check_required(image) {
+            if verify_info.svn > 32 {
+                raise_err!(FmcSvnGreaterThanMaxSupported)
+            }
+
+            if verify_info.svn < verify_info.min_svn {
+                raise_err!(FmcSvnLessThanMinSupported)
+            }
+
+            if verify_info.svn < self.env.fmc_svn() {
+                raise_err!(FmcSvnLessThanFuse)
+            }
+        }
+
+        if reason == ResetReason::UpdateReset && actual != self.env.get_fmc_digest_dv() {
+            raise_err!(UpdateResetFmcDigestMismatch)
+        }
 
         let info = ImageVerificationExeInfo {
             load_addr: verify_info.load_addr,
@@ -468,15 +518,6 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             size: verify_info.size,
         };
 
-        // TODO: SVN Check
-        // 0. Skip SVN check in unprovisioned mode or when antirollback is disabled
-        // 1. SVN is not more than 32
-        // 2. SVN is valid against min_svn and fuse_svn
-
-        if reason == ResetReason::UpdateReset && actual != self.env.get_fmc_digest_dv() {
-            raise_err!(UpdateResetFmcDigestMismatch)
-        }
-
         Ok(info)
     }
 
@@ -498,10 +539,35 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
         }
 
         // TODO: Perform following Address checks
-        // 1. Load address is a valid ICCM Address
-        // 2. Entry Point is a valid ICCM Address
         // 3. Entry Point is within the image
 
+        if !self.env.iccm_range().contains(&verify_info.load_addr) {
+            raise_err!(RuntimeLoadAddrInvalid)
+        }
+        if verify_info.load_addr % 4 != 0 {
+            raise_err!(RuntimeLoadAddrUnaligned)
+        }
+        if !self.env.iccm_range().contains(&verify_info.entry_point) {
+            raise_err!(RuntimeEntryPointInvalid)
+        }
+        if verify_info.entry_point % 4 != 0 {
+            raise_err!(RuntimeEntryPointUnaligned)
+        }
+
+        if self.svn_check_required(image) {
+            if verify_info.svn > 64 {
+                raise_err!(RuntimeSvnGreaterThanMaxSupported)
+            }
+
+            if verify_info.svn < verify_info.min_svn {
+                raise_err!(RuntimeSvnLessThanMinSupported)
+            }
+
+            if verify_info.svn < self.env.runtime_svn() {
+                raise_err!(RuntimeSvnLessThanFuse)
+            }
+        }
+
         let info = ImageVerificationExeInfo {
             load_addr: verify_info.load_addr,
             entry_point: verify_info.entry_point,
@@ -510,11 +576,6 @@ impl<Env: ImageVerificationEnv> ImageVerifier<Env> {
             size: verify_info.size,
         };
 
-        // TODO: SVN Check
-        // 0. Skip SVN check in unprovisioned mode or when antirollback is disabled
-        // 1. SVN is not more than 64
-        // 2. SVN is valid against min_svn and fuse_svn
-
         Ok(info)
     }
 }
@@ -609,6 +670,8 @@ mod tests {
 
         let verify_info = ImageTocEntry {
             digest: DUMMY_DATA,
+            load_addr: 0x40000000,
+            entry_point: 0x40000000,
             ..Default::default()
         };
 
@@ -627,12 +690,10 @@ mod tests {
         };
 
         let verifier = ImageVerifier::new(test_env);
-        // let mut verify_info = ImageTocEntry::default();
-
-        // verify_info.digest = DUMMY_DATA;
-
         let verify_info = ImageTocEntry {
             digest: DUMMY_DATA,
+            load_addr: 0x40000000,
+            entry_point: 0x40000000,
             ..Default::default()
         };
 
@@ -1004,9 +1065,9 @@ mod tests {
         let test_env = TestEnv::default();
         let verifier = ImageVerifier::new(test_env);
         let verify_info = ImageTocEntry {
-            load_addr: 0xCAFEB0BA,
-            entry_point: 0xDEADBEEF,
-            svn: 420,
+            load_addr: 0x40000000,
+            entry_point: 0x40000000,
+            svn: 1,
             size: 100,
             ..Default::default()
         };
@@ -1014,9 +1075,9 @@ mod tests {
         let result = verifier.verify_fmc((), &verify_info, ResetReason::ColdReset);
         assert!(result.is_ok());
         let info = result.unwrap();
-        assert_eq!(info.load_addr, 0xCAFEB0BA);
-        assert_eq!(info.entry_point, 0xDEADBEEF);
-        assert_eq!(info.svn, 420);
+        assert_eq!(info.load_addr, 0x40000000);
+        assert_eq!(info.entry_point, 0x40000000);
+        assert_eq!(info.svn, 1);
         assert_eq!(info.size, 100);
     }
 
@@ -1037,18 +1098,18 @@ mod tests {
         let test_env = TestEnv::default();
         let verifier = ImageVerifier::new(test_env);
         let verify_info = ImageTocEntry {
-            load_addr: 0xCAFEB0BA,
-            entry_point: 0xDEADBEEF,
-            svn: 420,
+            load_addr: 0x40000000,
+            entry_point: 0x40000000,
+            svn: 1,
             size: 100,
             ..Default::default()
         };
         let result = verifier.verify_runtime((), &verify_info);
         assert!(result.is_ok());
         let info = result.unwrap();
-        assert_eq!(info.load_addr, 0xCAFEB0BA);
-        assert_eq!(info.entry_point, 0xDEADBEEF);
-        assert_eq!(info.svn, 420);
+        assert_eq!(info.load_addr, 0x40000000);
+        assert_eq!(info.entry_point, 0x40000000);
+        assert_eq!(info.svn, 1);
         assert_eq!(info.size, 100);
     }
 
@@ -1129,5 +1190,20 @@ mod tests {
         fn get_fmc_digest_dv(&self) -> ImageDigest {
             self.fmc_digest
         }
+
+        fn fmc_svn(&self) -> u32 {
+            0
+        }
+
+        fn runtime_svn(&self) -> u32 {
+            0
+        }
+
+        fn iccm_range(&self) -> Range<u32> {
+            Range {
+                start: 0x40000000,
+                end: 0x40000000 + (128 * 1024),
+            }
+        }
     }
 }
diff --git a/rom/dev/src/rom_env.rs b/rom/dev/src/rom_env.rs
index 6498b51d..ec0b3cec 100644
--- a/rom/dev/src/rom_env.rs
+++ b/rom/dev/src/rom_env.rs
@@ -20,6 +20,10 @@ use caliptra_drivers::{
     DataVault, DeobfuscationEngine, DeviceState, Ecc384, FlowStatus, FuseBank, Hmac384, KeyVault,
     Mailbox, MfgState, PcrBank, ResetService, Sha1, Sha256, Sha384, Sha384Acc,
 };
+use core::ops::Range;
+
+const ICCM_START: u32 = 0x40000000;
+const ICCM_SIZE: u32 = 128 << 10;
 
 /// Rom Context
 pub struct RomEnv {
@@ -175,4 +179,12 @@ impl RomEnv {
     pub fn pcr_bank(&self) -> &EnvCell<PcrBank> {
         &self.pcr_bank
     }
+
+    /// Get ICCM Range
+    pub fn iccm_range(&self) -> Range<u32> {
+        Range {
+            start: ICCM_START,
+            end: ICCM_START + ICCM_SIZE,
+        }
+    }
 }
diff --git a/rom/dev/src/verifier.rs b/rom/dev/src/verifier.rs
index 9fd806b1..a08272f6 100644
--- a/rom/dev/src/verifier.rs
+++ b/rom/dev/src/verifier.rs
@@ -15,6 +15,7 @@ Abstract:
 use caliptra_drivers::*;
 use caliptra_image_types::*;
 use caliptra_image_verify::ImageVerificationEnv;
+use core::ops::Range;
 
 use crate::rom_env::RomEnv;
 
@@ -117,4 +118,18 @@ impl<'a> ImageVerificationEnv for RomImageVerificationEnv<'a> {
     fn get_fmc_digest_dv(&self) -> ImageDigest {
         self.env.data_vault().map(|dv| dv.fmc_tci()).into()
     }
+
+    // Get Fuse FMC Key Manifest SVN
+    fn fmc_svn(&self) -> u32 {
+        self.env.fuse_bank().map(|f| f.fmc_svn())
+    }
+
+    // Get Runtime fuse SVN
+    fn runtime_svn(&self) -> u32 {
+        self.env.fuse_bank().map(|f| f.runtime_svn())
+    }
+
+    fn iccm_range(&self) -> Range<u32> {
+        self.env.iccm_range()
+    }
 }


Test patch:
diff --git a/rom/dev/tests/helpers.rs b/rom/dev/tests/helpers.rs
index 31f9d7e7..d65e7285 100644
--- a/rom/dev/tests/helpers.rs
+++ b/rom/dev/tests/helpers.rs
@@ -5,7 +5,10 @@ use caliptra_hw_model::DefaultHwModel;
 use caliptra_hw_model::{BootParams, Fuses, InitParams, SecurityState};
 use caliptra_image_types::ImageBundle;
 
-pub fn build_hw_model_and_image_bundle(fuses: Fuses) -> (DefaultHwModel, ImageBundle) {
+pub fn build_hw_model_and_image_bundle(
+    fuses: Fuses,
+    image_options: ImageOptions,
+) -> (DefaultHwModel, ImageBundle) {
     let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
     let hw = caliptra_hw_model::new(BootParams {
         init_params: InitParams {
@@ -18,12 +21,9 @@ pub fn build_hw_model_and_image_bundle(fuses: Fuses) -> (DefaultHwModel, ImageBu
     })
     .unwrap();
 
-    let image_bundle = caliptra_builder::build_and_sign_image(
-        &FMC_WITH_UART,
-        &APP_WITH_UART,
-        ImageOptions::default(),
-    )
-    .unwrap();
+    let image_bundle =
+        caliptra_builder::build_and_sign_image(&FMC_WITH_UART, &APP_WITH_UART, image_options)
+            .unwrap();
 
     (hw, image_bundle)
 }
diff --git a/rom/dev/tests/test_fmcalias_derivation.rs b/rom/dev/tests/test_fmcalias_derivation.rs
index c159a8b2..41bd5fd3 100644
--- a/rom/dev/tests/test_fmcalias_derivation.rs
+++ b/rom/dev/tests/test_fmcalias_derivation.rs
@@ -1,3 +1,4 @@
+use caliptra_builder::ImageOptions;
 // Licensed under the Apache-2.0 license
 use caliptra_hw_model::{Fuses, HwModel, ModelError};
 use caliptra_image_types::IMAGE_BYTE_SIZE;
@@ -9,7 +10,8 @@ const INVALID_IMAGE_SIZE: u32 = 0x02000003;
 
 #[test]
 fn test_zero_firmware_size() {
-    let (mut hw, _image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, _image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     let mut output = vec![];
 
     // Zero-sized firmware.
@@ -27,7 +29,8 @@ fn test_firmware_gt_max_size() {
 
     // Firmware size > 128 KB.
 
-    let (mut hw, _image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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
diff --git a/rom/dev/tests/test_idevid_derivation.rs b/rom/dev/tests/test_idevid_derivation.rs
index 2cf645ee..d6c20a4c 100644
--- a/rom/dev/tests/test_idevid_derivation.rs
+++ b/rom/dev/tests/test_idevid_derivation.rs
@@ -1,5 +1,6 @@
 // Licensed under the Apache-2.0 license
 
+use caliptra_builder::ImageOptions;
 use caliptra_drivers::{state::MfgFlags, IdevidCertAttr, X509KeyIdAlgo};
 //use caliptra_drivers::{state::MfgFlags, IdevidCertAttr, X509KeyIdAlgo};
 use caliptra_hw_model::{Fuses, HwModel};
@@ -17,7 +18,8 @@ fn assert_output_contains(haystack: &str, needle: &str) {
 #[test]
 fn test_generate_csr() {
     let mut output = vec![];
-    let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
     // Set gen_idev_id_csr to generate CSR.
     let flags = MfgFlags::GENERATE_IDEVID_CSR;
@@ -48,7 +50,8 @@ fn test_idev_subj_key_id_algo() {
         let mut fuses = Fuses::default();
         fuses.idevid_cert_attr[IdevidCertAttr::Flags as usize] = algo;
 
-        let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses);
+        let (mut hw, image_bundle) =
+            helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
             .unwrap();
 
diff --git a/rom/dev/tests/test_image_validation.rs b/rom/dev/tests/test_image_validation.rs
index 7be85551..9e05194c 100644
--- a/rom/dev/tests/test_image_validation.rs
+++ b/rom/dev/tests/test_image_validation.rs
@@ -36,6 +36,23 @@ const VENDOR_PUB_KEY_DIGEST_MISMATCH: u32 = 0x0B000005;
 const VENDOR_ECC_PUB_KEY_REVOKED: u32 = 0x0B000009;
 const VENDOR_ECC_PUB_KEY_INDEX_MISMATCH: u32 = 0x0B00000D;
 const VENDOR_ECC_PUB_KEY_INDEX_OUT_OF_BOUNDS: u32 = 0x0B000008;
+const FMC_LOAD_ADDR_INVALID: u32 = 0x0B000021;
+const FMC_LOAD_ADDR_UNALIGNED: u32 = 0x0B000022;
+const FMC_ENTRY_POINT_INVALID: u32 = 0x0B000023;
+const FMC_ENTRY_POINT_UNALIGNED: u32 = 0x0B000024;
+const FMC_SVN_GREATER_THAN_MAX_SUPPORTED: u32 = 0x0B000025;
+const FMC_SVN_LESS_THAN_MIN_SUPPORTED: u32 = 0x0B000026;
+const FMC_SVN_LESS_THAN_FUSE: u32 = 0x0B000027;
+const RUNTIME_LOAD_ADDR_INVALID: u32 = 0x0B000028;
+const RUNTIME_LOAD_ADDR_UNALIGNED: u32 = 0x0B000029;
+const RUNTIME_ENTRY_POINT_INVALID: u32 = 0x0B00002A;
+const RUNTIME_ENTRY_POINT_UNALIGNED: u32 = 0x0B00002B;
+const RUNTIME_SVN_GREATER_THAN_MAX_SUPPORTED: u32 = 0x0B00002C;
+const RUNTIME_SVN_LESS_THAN_MIN_SUPPORTED: u32 = 0x0B00002D;
+const RUNTIME_SVN_LESS_THAN_FUSE: u32 = 0x0B00002E;
+
+const ICCM_START_ADDR: u32 = 0x40000000;
+const ICCM_END_ADDR: u32 = ICCM_START_ADDR + (128 * 1024) - 1;
 
 const PUB_KEY_X: [u8; 48] = [
     0xD7, 0x9C, 0x6D, 0x97, 0x2B, 0x34, 0xA1, 0xDF, 0xC9, 0x16, 0xA7, 0xB6, 0xE0, 0xA9, 0x9B, 0x6B,
@@ -63,7 +80,8 @@ const SIGNATURE_S: [u8; 48] = [
 
 #[test]
 fn test_invalid_manifest_marker() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     image_bundle.manifest.marker = 0xDEADBEEF;
 
     assert_eq!(
@@ -78,7 +96,8 @@ fn test_invalid_manifest_marker() {
 
 #[test]
 fn test_invalid_manifest_size() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     image_bundle.manifest.size = (core::mem::size_of::<ImageManifest>() - 1) as u32;
 
     assert_eq!(
@@ -98,7 +117,8 @@ fn test_preamble_zero_vendor_pubkey_digest() {
         key_manifest_pk_hash: [0u32; 12],
         ..Default::default()
     };
-    let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses);
+    let (mut hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
 
     assert_eq!(
         ModelError::MailboxCmdFailed,
@@ -118,7 +138,8 @@ fn test_preamble_vendor_pubkey_digest_mismatch() {
         ..Default::default()
     };
 
-    let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses);
+    let (mut hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
@@ -136,7 +157,8 @@ fn test_preamble_owner_pubkey_digest_mismatch() {
         ..Default::default()
     };
 
-    let (mut hw, image_bundle) = helpers::build_hw_model_and_image_bundle(fuses);
+    let (mut hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(fuses, ImageOptions::default());
 
     assert_eq!(
         ModelError::MailboxCmdFailed,
@@ -208,7 +230,8 @@ fn test_preamble_vendor_pubkey_revocation() {
 #[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());
+    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;
 
     assert_eq!(
@@ -224,7 +247,8 @@ fn test_preamble_vendor_pubkey_out_of_bounds() {
 
 #[test]
 fn test_header_verify_vendor_sig_zero_pubkey() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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![];
 
@@ -245,7 +269,8 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
         hw.copy_output_until_non_fatal_error(VENDOR_PUB_KEY_DIGEST_INVALID_ARG, &mut output);
     assert!(result.is_ok());
 
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
     // Set ecc_pub_key.y to zero.
     image_bundle.manifest.preamble.vendor_pub_keys.ecc_pub_keys[vendor_ecc_pub_key_idx].x =
@@ -267,7 +292,8 @@ fn test_header_verify_vendor_sig_zero_pubkey() {
 #[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());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
     // Set vendor_sig.r to zero.
     let vendor_sig_r_backup = image_bundle.manifest.preamble.vendor_sigs.ecc_sig.r;
@@ -282,7 +308,8 @@ fn test_header_verify_vendor_sig_zero_signature() {
         hw.copy_output_until_non_fatal_error(VENDOR_ECC_SIGNATURE_INVALID_ARG, &mut output);
     assert!(result.is_ok());
 
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
     // Set vendor_sig.s to zero.
     image_bundle.manifest.preamble.vendor_sigs.ecc_sig.r = vendor_sig_r_backup;
@@ -300,7 +327,8 @@ fn test_header_verify_vendor_sig_zero_signature() {
 
 #[test]
 fn test_header_verify_vendor_sig_mismatch() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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![];
 
@@ -323,7 +351,8 @@ fn test_header_verify_vendor_sig_mismatch() {
     let result = hw.copy_output_until_non_fatal_error(VENDOR_ECC_SIGNATURE_INVALID, &mut output);
     assert!(result.is_ok());
 
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
 
     // Modify the vendor signature.
     image_bundle.manifest.preamble.vendor_pub_keys.ecc_pub_keys[vendor_ecc_pub_key_idx] =
@@ -354,7 +383,8 @@ fn test_header_verify_vendor_sig_mismatch() {
 
 #[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());
+    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.
@@ -568,7 +598,8 @@ fn test_header_verify_owner_sig_zero_signature_s() {
 
 #[test]
 fn test_toc_invalid_entry_count() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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.
@@ -586,7 +617,8 @@ fn test_toc_invalid_entry_count() {
 
 #[test]
 fn test_toc_invalid_toc_digest() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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.
@@ -607,7 +639,8 @@ 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());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     let fmc_new_offset = image_bundle.manifest.runtime.offset;
     // These are unchanged.
     let fmc_new_size = image_bundle.manifest.fmc.size;
@@ -629,7 +662,8 @@ fn test_toc_fmc_range_overlap() {
     assert!(result.is_ok());
 
     // Case 2: FMC offset > Runtime offset
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     let fmc_new_offset = image_bundle.manifest.runtime.offset + 1;
     // These are unchanged.
     let fmc_new_size = image_bundle.manifest.fmc.size;
@@ -651,7 +685,8 @@ fn test_toc_fmc_range_overlap() {
     assert!(result.is_ok());
 
     // // Case 3: FMC start offset < Runtime offset < FMC end offset
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    let (mut hw, mut image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
     let runtime_new_offset = image_bundle.manifest.fmc.offset + 1;
     // These are unchanged.
     let fmc_new_offset = image_bundle.manifest.fmc.offset;
@@ -675,7 +710,8 @@ fn test_toc_fmc_range_overlap() {
 
 #[test]
 fn test_toc_fmc_range_incorrect_order() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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;
@@ -699,7 +735,8 @@ fn test_toc_fmc_range_incorrect_order() {
 
 #[test]
 fn test_fmc_digest_mismatch() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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.
@@ -714,14 +751,203 @@ fn test_fmc_digest_mismatch() {
     assert!(result.is_ok());
 }
 
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[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);
+    assert_eq!(
+        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());
+}
+
+#[test]
+fn test_fmc_svn_greater_than_32() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let fuses = caliptra_hw_model::Fuses {
+        life_cycle: DeviceLifecycle::Manufacturing,
+        anti_rollback_disable: false,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        ..Default::default()
+    };
+
+    let image_options = ImageOptions {
+        fmc_svn: 33,
+        ..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_GREATER_THAN_MAX_SUPPORTED, &mut output);
+    assert!(result.is_ok());
+}
+
+#[test]
+fn test_fmc_svn_less_than_min_svn() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let fuses = caliptra_hw_model::Fuses {
+        life_cycle: DeviceLifecycle::Manufacturing,
+        anti_rollback_disable: false,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        ..Default::default()
+    };
+
+    let image_options = ImageOptions {
+        fmc_min_svn: 3,
+        fmc_svn: 2,
+        ..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());
+}
+
+#[test]
+fn test_fmc_svn_less_than_fuse_svn() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let fuses = caliptra_hw_model::Fuses {
+        life_cycle: DeviceLifecycle::Manufacturing,
+        anti_rollback_disable: false,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        fmc_key_manifest_svn: 0b11, // fuse svn = 2
+        ..Default::default()
+    };
+
+    let image_options = ImageOptions {
+        fmc_svn: 1,
+        ..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_FUSE, &mut output);
+    assert!(result.is_ok());
+}
+
 #[test]
 fn test_runtime_digest_mismatch() {
-    let (mut hw, mut image_bundle) = helpers::build_hw_model_and_image_bundle(Fuses::default());
+    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());
-
     assert_eq!(
         ModelError::MailboxCmdFailed,
         hw.upload_firmware(&image_bundle.to_bytes().unwrap())
@@ -731,6 +957,195 @@ fn test_runtime_digest_mismatch() {
     assert!(result.is_ok());
 }
 
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[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());
+}
+
+#[test]
+fn test_runtime_svn_greater_than_64() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let fuses = caliptra_hw_model::Fuses {
+        life_cycle: DeviceLifecycle::Manufacturing,
+        anti_rollback_disable: false,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        ..Default::default()
+    };
+    let image_options = ImageOptions {
+        app_svn: 65,
+        ..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(RUNTIME_SVN_GREATER_THAN_MAX_SUPPORTED, &mut output);
+    assert!(result.is_ok());
+}
+
+#[test]
+fn test_runtime_svn_less_than_min_svn() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let fuses = caliptra_hw_model::Fuses {
+        life_cycle: DeviceLifecycle::Manufacturing,
+        anti_rollback_disable: false,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        ..Default::default()
+    };
+    let image_options = ImageOptions {
+        app_min_svn: 3,
+        app_svn: 2,
+        ..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(RUNTIME_SVN_LESS_THAN_MIN_SUPPORTED, &mut output);
+    assert!(result.is_ok());
+}
+
+#[test]
+fn test_runtime_svn_less_than_fuse_svn() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let fuse_svn: [u32; 4] = [0xffff_ffff, 0x7fff_ffff, 0, 0]; // fuse svn = 63
+    let fuses = caliptra_hw_model::Fuses {
+        life_cycle: DeviceLifecycle::Manufacturing,
+        anti_rollback_disable: false,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        runtime_svn: fuse_svn,
+        ..Default::default()
+    };
+    let image_options = ImageOptions {
+        app_svn: 62,
+        ..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(RUNTIME_SVN_LESS_THAN_FUSE, &mut output);
+    assert!(result.is_ok());
+}
+
 fn update_header(image_bundle: &mut ImageBundle) {
     let opts = ImageOptions::default();
     let config = ImageGeneratorConfig {
@@ -774,6 +1189,56 @@ fn update_fmc_runtime_ranges(
     update_header(image_bundle);
 
     // Generate image bytes.
+    generate_image_bytes(image_bundle)
+}
+
+fn update_load_addr(image_bundle: &mut ImageBundle, is_fmc: bool, new_load_addr: u32) -> Vec<u8> {
+    if is_fmc {
+        image_bundle.manifest.fmc.load_addr = new_load_addr;
+    } else {
+        image_bundle.manifest.runtime.load_addr = new_load_addr;
+    }
+
+    let gen = ImageGenerator::new(OsslCrypto::default());
+
+    // Update TOC digest.
+    image_bundle.manifest.header.toc_digest = gen
+        .toc_digest(&image_bundle.manifest.fmc, &image_bundle.manifest.runtime)
+        .unwrap();
+
+    // Update Header.
+    update_header(image_bundle);
+
+    // Generate image bytes.
+    generate_image_bytes(image_bundle)
+}
+
+fn update_entry_point(
+    image_bundle: &mut ImageBundle,
+    is_fmc: bool,
+    new_entry_point: u32,
+) -> Vec<u8> {
+    if is_fmc {
+        image_bundle.manifest.fmc.entry_point = new_entry_point;
+    } else {
+        image_bundle.manifest.runtime.entry_point = new_entry_point;
+    }
+
+    let gen = ImageGenerator::new(OsslCrypto::default());
+
+    // Update TOC digest.
+    image_bundle.manifest.header.toc_digest = gen
+        .toc_digest(&image_bundle.manifest.fmc, &image_bundle.manifest.runtime)
+        .unwrap();
+
+    // Update Header.
+    update_header(image_bundle);
+
+    // Generate image bytes.
+    generate_image_bytes(image_bundle)
+}
+
+fn generate_image_bytes(image_bundle: &mut ImageBundle) -> Vec<u8> {
     let mut image = vec![];
     image.extend_from_slice(image_bundle.manifest.as_bytes());
     image.extend_from_slice(&image_bundle.fmc);