sim: Add hw-rollback-protection feature

This commit adds simulator support to test the
hw-rollback-protection feature which is using
nv-counters. In the simulator they are stored in Rust
to prevent any race conditions from happening due to
the parallel execution of the tests.

Signed-off-by: Roland Mikhel <roland.mikhel@arm.com>
Change-Id: I445fc50615ed1f0c06e5933b16811c24d9d302fc
diff --git a/sim/Cargo.toml b/sim/Cargo.toml
index 44234f6..38f1b37 100644
--- a/sim/Cargo.toml
+++ b/sim/Cargo.toml
@@ -30,6 +30,7 @@
 direct-xip = ["mcuboot-sys/direct-xip"]
 downgrade-prevention = ["mcuboot-sys/downgrade-prevention"]
 max-align-32 = ["mcuboot-sys/max-align-32"]
+hw-rollback-protection = ["mcuboot-sys/hw-rollback-protection"]
 
 [dependencies]
 byteorder = "1.4"
diff --git a/sim/mcuboot-sys/Cargo.toml b/sim/mcuboot-sys/Cargo.toml
index 6d21bf5..c140341 100644
--- a/sim/mcuboot-sys/Cargo.toml
+++ b/sim/mcuboot-sys/Cargo.toml
@@ -83,6 +83,9 @@
 # Support images with 32-byte maximum write alignment value.
 max-align-32 = []
 
+# Enable hardware rollback protection
+hw-rollback-protection = []
+
 [build-dependencies]
 cc = "1.0.25"
 
diff --git a/sim/mcuboot-sys/build.rs b/sim/mcuboot-sys/build.rs
index 27da2e3..a01844e 100644
--- a/sim/mcuboot-sys/build.rs
+++ b/sim/mcuboot-sys/build.rs
@@ -34,6 +34,7 @@
     let ram_load = env::var("CARGO_FEATURE_RAM_LOAD").is_ok();
     let direct_xip = env::var("CARGO_FEATURE_DIRECT_XIP").is_ok();
     let max_align_32 = env::var("CARGO_FEATURE_MAX_ALIGN_32").is_ok();
+    let hw_rollback_protection = env::var("CARGO_FEATURE_HW_ROLLBACK_PROTECTION").is_ok();
 
     let mut conf = CachedBuild::new();
     conf.conf.define("__BOOTSIM__", None);
@@ -75,6 +76,11 @@
         conf.conf.define("MCUBOOT_DIRECT_XIP", None);
     }
 
+    if hw_rollback_protection {
+        conf.conf.define("MCUBOOT_HW_ROLLBACK_PROT", None);
+        conf.file("csupport/security_cnt.c");
+    }
+
     // Currently no more than one sig type can be used simultaneously.
     if vec![sig_rsa, sig_rsa3072, sig_ecdsa, sig_ed25519].iter()
         .fold(0, |sum, &v| sum + v as i32) > 1 {
diff --git a/sim/mcuboot-sys/csupport/security_cnt.c b/sim/mcuboot-sys/csupport/security_cnt.c
new file mode 100644
index 0000000..0f79d8d
--- /dev/null
+++ b/sim/mcuboot-sys/csupport/security_cnt.c
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright (c) 2023 Arm Limited
+ */
+
+#include "bootutil/security_cnt.h"
+#include "mcuboot_config/mcuboot_logging.h"
+#include "bootutil/fault_injection_hardening.h"
+
+/*
+ * Since the simulator is executing unit tests in parallel,
+ * the storage area where the security counter values reside
+ * has to be managed per thread from Rust's side.
+ */
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+
+int sim_set_nv_counter_for_image(uint32_t image_index, uint32_t security_counter_value);
+
+int sim_get_nv_counter_for_image(uint32_t image_index, uint32_t* data);
+
+fih_ret boot_nv_security_counter_init(void) {
+    return FIH_SUCCESS;
+}
+
+fih_ret boot_nv_security_counter_get(uint32_t image_id, fih_int *security_cnt) {
+    uint32_t counter = 0;
+    FIH_DECLARE(fih_rc, FIH_FAILURE);
+    fih_rc = fih_ret_encode_zero_equality(sim_get_nv_counter_for_image(image_id, &counter));
+
+    MCUBOOT_LOG_INF("Read security counter value (%d) for image: %d\n", counter, image_id);
+    *security_cnt = fih_int_encode(counter);
+
+    FIH_RET(fih_rc);
+}
+
+int32_t boot_nv_security_counter_update(uint32_t image_id, uint32_t img_security_cnt) {
+    MCUBOOT_LOG_INF("Writing security counter value (%d) for image: %d\n", img_security_cnt, image_id);
+
+    return sim_set_nv_counter_for_image(image_id, img_security_cnt);
+}
+
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
diff --git a/sim/mcuboot-sys/src/api.rs b/sim/mcuboot-sys/src/api.rs
index 624b3e9..db8564d 100644
--- a/sim/mcuboot-sys/src/api.rs
+++ b/sim/mcuboot-sys/src/api.rs
@@ -1,5 +1,6 @@
 // Copyright (c) 2017-2021 Linaro LTD
 // Copyright (c) 2018-2019 JUUL Labs
+// Copyright (c) 2023 Arm Limited
 //
 // SPDX-License-Identifier: Apache-2.0
 
@@ -131,10 +132,32 @@
     pub base: usize,
 }
 
+/// This struct stores the non-volatile security counter per image. It will be stored per test thread,
+/// and the C code will set / get the values here.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct NvCounterStorage {
+    pub storage: Vec<u32>,
+}
+
+impl NvCounterStorage {
+    pub fn new() -> Self {
+        let count = if cfg!(feature = "multiimage") {
+            2
+        } else {
+            1
+        };
+        Self {
+            storage: vec![0; count]
+        }
+    }
+}
+
 thread_local! {
     pub static THREAD_CTX: RefCell<FlashContext> = RefCell::new(FlashContext::new());
     pub static SIM_CTX: RefCell<CSimContextPtr> = RefCell::new(CSimContextPtr::new());
     pub static RAM_CTX: RefCell<BootsimRamInfo> = RefCell::new(BootsimRamInfo::default());
+    pub static NV_COUNTER_CTX: RefCell<NvCounterStorage> = RefCell::new(NvCounterStorage::new());
 }
 
 /// Set the flash device to be used by the simulation.  The pointer is unsafely stashed away.
@@ -317,3 +340,39 @@
         0
     }
 }
+
+#[no_mangle]
+pub extern "C" fn sim_set_nv_counter_for_image(image_index: u32, security_counter_value: u32) -> libc::c_int {
+    let mut rc = 0;
+    NV_COUNTER_CTX.with(|ctx| {
+        let mut counter_storage = ctx.borrow_mut();
+        if image_index as usize >= counter_storage.storage.len() {
+            rc = -1;
+            return;
+        }
+        if counter_storage.storage[image_index as usize] > security_counter_value {
+            rc = -2;
+            warn!("Failed to set security counter value ({}) for image index {}", security_counter_value, image_index);
+            return;
+        }
+
+        counter_storage.storage[image_index as usize] = security_counter_value;
+    });
+
+    return rc;
+}
+
+#[no_mangle]
+pub extern "C" fn sim_get_nv_counter_for_image(image_index: u32, security_counter_value: *mut u32) -> libc::c_int {
+    let mut rc = 0;
+    NV_COUNTER_CTX.with(|ctx| {
+        let counter_storage = ctx.borrow();
+        if image_index as usize >= counter_storage.storage.len() {
+            rc = -1;
+            return;
+        }
+        unsafe { *security_counter_value = counter_storage.storage[image_index as usize] };
+
+    });
+    return rc;
+}
diff --git a/sim/mcuboot-sys/src/c.rs b/sim/mcuboot-sys/src/c.rs
index e9bac0a..5d2c8ca 100644
--- a/sim/mcuboot-sys/src/c.rs
+++ b/sim/mcuboot-sys/src/c.rs
@@ -1,6 +1,6 @@
 // Copyright (c) 2017-2021 Linaro LTD
 // Copyright (c) 2017-2019 JUUL Labs
-// Copyright (c) 2019-2021 Arm Limited
+// Copyright (c) 2019-2023 Arm Limited
 //
 // SPDX-License-Identifier: Apache-2.0
 
@@ -150,6 +150,16 @@
     }
 }
 
+pub fn set_security_counter(image_index: u32, security_counter_value: u32) {
+    api::sim_set_nv_counter_for_image(image_index, security_counter_value);
+}
+
+pub fn get_security_counter(image_index: u32) -> u32 {
+    let mut counter_val: u32 = 0;
+    api::sim_get_nv_counter_for_image(image_index, &mut counter_val as *mut u32);
+    return counter_val;
+}
+
 mod raw {
     use crate::area::CAreaDesc;
     use crate::api::{BootRsp, CSimContext};
diff --git a/sim/src/caps.rs b/sim/src/caps.rs
index e7240d1..912dda1 100644
--- a/sim/src/caps.rs
+++ b/sim/src/caps.rs
@@ -27,6 +27,7 @@
     Aes256               = (1 << 14),
     RamLoad              = (1 << 15),
     DirectXip            = (1 << 16),
+    HwRollbackProtection = (1 << 17),
 }
 
 impl Caps {
diff --git a/sim/src/image.rs b/sim/src/image.rs
index 424f646..9b46ca0 100644
--- a/sim/src/image.rs
+++ b/sim/src/image.rs
@@ -221,11 +221,11 @@
                 Box::new(BoringDep::new(image_num, deps))
             };
             let primaries = install_image(&mut flash, &slots[0],
-                maximal(42784), &ram, &*dep, false);
+                maximal(42784), &ram, &*dep, false, Some(0));
             let upgrades = match deps.depends[image_num] {
                 DepType::NoUpgrade => install_no_image(),
                 _ => install_image(&mut flash, &slots[1],
-                    maximal(46928), &ram, &*dep, false)
+                    maximal(46928), &ram, &*dep, false, Some(0))
             };
             OneImage {
                 slots,
@@ -274,9 +274,9 @@
         let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| {
             let dep = BoringDep::new(image_num, &NO_DEPS);
             let primaries = install_image(&mut bad_flash, &slots[0],
-                maximal(32784), &ram, &dep, false);
+                maximal(32784), &ram, &dep, false, Some(0));
             let upgrades = install_image(&mut bad_flash, &slots[1],
-                maximal(41928), &ram, &dep, true);
+                maximal(41928), &ram, &dep, true, Some(0));
             OneImage {
                 slots,
                 primaries,
@@ -297,9 +297,9 @@
         let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| {
             let dep = BoringDep::new(image_num, &NO_DEPS);
             let primaries = install_image(&mut bad_flash, &slots[0],
-                maximal(32784), &ram, &dep, false);
+                maximal(32784), &ram, &dep, false, Some(0));
             let upgrades = install_image(&mut bad_flash, &slots[1],
-                ImageSize::Oversized, &ram, &dep, false);
+                ImageSize::Oversized, &ram, &dep, false, Some(0));
             OneImage {
                 slots,
                 primaries,
@@ -320,7 +320,7 @@
         let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| {
             let dep = BoringDep::new(image_num, &NO_DEPS);
             let primaries = install_image(&mut flash, &slots[0],
-                maximal(32784), &ram, &dep, false);
+                maximal(32784), &ram, &dep, false, Some(0));
             let upgrades = install_no_image();
             OneImage {
                 slots,
@@ -343,7 +343,7 @@
             let dep = BoringDep::new(image_num, &NO_DEPS);
             let primaries = install_no_image();
             let upgrades = install_image(&mut flash, &slots[1],
-                maximal(32784), &ram, &dep, false);
+                maximal(32784), &ram, &dep, false, Some(0));
             OneImage {
                 slots,
                 primaries,
@@ -365,7 +365,31 @@
             let dep = BoringDep::new(image_num, &NO_DEPS);
             let primaries = install_no_image();
             let upgrades = install_image(&mut flash, &slots[1],
-                ImageSize::Oversized, &ram, &dep, false);
+                ImageSize::Oversized, &ram, &dep, false, Some(0));
+            OneImage {
+                slots,
+                primaries,
+                upgrades,
+            }}).collect();
+        Images {
+            flash,
+            areadesc: self.areadesc,
+            images,
+            total_count: None,
+            ram: self.ram,
+        }
+    }
+
+    /// If security_cnt is None then do not add a security counter TLV, otherwise add the specified value.
+    pub fn make_image_with_security_counter(self, security_cnt: Option<u32>) -> Images {
+        let mut flash = self.flash;
+        let ram = self.ram.clone(); // TODO: Avoid this clone.
+        let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| {
+            let dep = BoringDep::new(image_num, &NO_DEPS);
+            let primaries = install_image(&mut flash, &slots[0],
+                maximal(32784), &ram, &dep, false, security_cnt);
+            let upgrades = install_image(&mut flash, &slots[1],
+                maximal(41928), &ram, &dep, false, security_cnt.map(|v| v + 1));
             OneImage {
                 slots,
                 primaries,
@@ -1269,6 +1293,31 @@
         return false;
     }
 
+    pub fn run_hw_rollback_prot(&self) -> bool {
+        if !Caps::HwRollbackProtection.present() {
+            return false;
+        }
+
+        let mut flash = self.flash.clone();
+
+        // set the "stored" security counter to a fixed value.
+        c::set_security_counter(0, 30);
+
+        let result = c::boot_go(&mut flash, &self.areadesc, None, None, true);
+
+        if result.success() {
+            warn!("Successful boot when it did not suppose to happen!");
+            return true;
+        }
+        let counter_val =  c::get_security_counter(0);
+        if counter_val != 30 {
+            warn!("Counter was changed when it did not suppose to!");
+            return true;
+        }
+
+        false
+    }
+
     /// Adds a new flash area that fails statistically
     fn mark_bad_status_with_rate(&self, flash: &mut SimMultiFlash, slot: usize,
                                  rate: f32) {
@@ -1650,7 +1699,7 @@
 /// fields used by the given code.  Returns a copy of the image that was written.
 fn install_image(flash: &mut SimMultiFlash, slot: &SlotInfo, len: ImageSize,
                  ram: &RamData,
-                 deps: &dyn Depender, bad_sig: bool) -> ImageData {
+                 deps: &dyn Depender, bad_sig: bool, security_counter:Option<u32>) -> ImageData {
     let offset = slot.base_off;
     let slot_len = slot.len;
     let dev_id = slot.dev_id;
@@ -1658,6 +1707,8 @@
 
     let mut tlv: Box<dyn ManifestGen> = Box::new(make_tlv());
 
+    tlv.set_security_counter(security_counter);
+
     // Add the dependencies early to the tlv.
     for dep in deps.my_deps(offset, slot.index) {
         tlv.add_dependency(deps.other_id(), &dep);
@@ -1891,6 +1942,8 @@
             TlvGen::new_ecdsa()
         } else if Caps::Ed25519.present() {
             TlvGen::new_ed25519()
+        } else if Caps::HwRollbackProtection.present() {
+            TlvGen::new_sec_cnt()
         } else {
             TlvGen::new_hash_only()
         }
diff --git a/sim/src/tlv.rs b/sim/src/tlv.rs
index c924043..99c02d9 100644
--- a/sim/src/tlv.rs
+++ b/sim/src/tlv.rs
@@ -59,6 +59,7 @@
     ENCEC256 = 0x32,
     ENCX25519 = 0x33,
     DEPENDENCY = 0x40,
+    SECCNT = 0x50,
 }
 
 #[allow(dead_code, non_camel_case_types)]
@@ -108,6 +109,9 @@
 
     /// Return the current encryption key
     fn get_enc_key(&self) -> Vec<u8>;
+
+    /// Set the security counter to the specified value.
+    fn set_security_counter(&mut self, security_cnt: Option<u32>);
 }
 
 #[derive(Debug, Default)]
@@ -119,6 +123,7 @@
     enc_key: Vec<u8>,
     /// Should this signature be corrupted.
     gen_corrupted: bool,
+    security_cnt: Option<u32>,
 }
 
 #[derive(Debug)]
@@ -294,6 +299,15 @@
             ..Default::default()
         }
     }
+
+    #[allow(dead_code)]
+    pub fn new_sec_cnt() -> TlvGen {
+       TlvGen {
+            kinds: vec![TlvKinds::SHA256, TlvKinds::SECCNT],
+            ..Default::default()
+        }
+    }
+
 }
 
 impl ManifestGen for TlvGen {
@@ -317,12 +331,17 @@
     }
 
     fn protect_size(&self) -> u16 {
-        if self.dependencies.is_empty() {
-            0
-        } else {
-            // Include the header and space for each dependency.
-            4 + (self.dependencies.len() as u16) * (4 + 4 + 8)
+        let mut size = 0;
+        if !self.dependencies.is_empty() || (Caps::HwRollbackProtection.present() && self.security_cnt.is_some()) {
+            // include the TLV area header.
+            size += 4;
+            // add space for each dependency.
+            size +=  (self.dependencies.len() as u16) * (4 + std::mem::size_of::<Dependency>() as u16);
+            if Caps::HwRollbackProtection.present() && self.security_cnt.is_some() {
+                size += 4 + 4;
+            }
         }
+        size
     }
 
     fn add_dependency(&mut self, id: u8, version: &ImageVersion) {
@@ -385,10 +404,8 @@
             estimate += 4 + if aes256 { 96 } else { 80 };
         }
 
-        // Gather the size of the dependency information.
-        if self.protect_size() > 0 {
-            estimate += 4 + (16 * self.dependencies.len());
-        }
+        // Gather the size of the protected TLV area.
+        estimate += self.protect_size() as usize;
 
         estimate
     }
@@ -418,6 +435,13 @@
                 protected_tlv.write_u32::<LittleEndian>(dep.version.build_num).unwrap();
             }
 
+            // Security counter has to be at the protected TLV area also
+            if Caps::HwRollbackProtection.present() && self.security_cnt.is_some() {
+                protected_tlv.write_u16::<LittleEndian>(TlvKinds::SECCNT as u16).unwrap();
+                protected_tlv.write_u16::<LittleEndian>(std::mem::size_of::<u32>() as u16).unwrap();
+                protected_tlv.write_u32::<LittleEndian>(self.security_cnt.unwrap() as u32).unwrap();
+            }
+
             assert_eq!(size, protected_tlv.len() as u16, "protected TLV length incorrect");
         }
 
@@ -765,6 +789,10 @@
         }
         self.enc_key.clone()
     }
+
+    fn set_security_counter(&mut self, security_cnt: Option<u32>) {
+        self.security_cnt = security_cnt;
+    }
 }
 
 include!("rsa_pub_key-rs.txt");
diff --git a/sim/tests/core.rs b/sim/tests/core.rs
index 50e0bba..45233de 100644
--- a/sim/tests/core.rs
+++ b/sim/tests/core.rs
@@ -1,5 +1,6 @@
 // Copyright (c) 2017-2021 Linaro LTD
 // Copyright (c) 2017-2019 JUUL Labs
+// Copyright (c) 2023 Arm Limited
 //
 // SPDX-License-Identifier: Apache-2.0
 
@@ -67,6 +68,8 @@
 sim_test!(direct_xip_first, make_no_upgrade_image(&NO_DEPS), run_direct_xip());
 sim_test!(ram_load_first, make_no_upgrade_image(&NO_DEPS), run_ram_load());
 sim_test!(ram_load_split, make_no_upgrade_image(&NO_DEPS), run_split_ram_load());
+sim_test!(hw_prot_failed_security_cnt_check, make_image_with_security_counter(Some(0)), run_hw_rollback_prot());
+sim_test!(hw_prot_missing_security_cnt, make_image_with_security_counter(None), run_hw_rollback_prot());
 
 // Test various combinations of incorrect dependencies.
 test_shell!(dependency_combos, r, {