diff --git a/.github/workflows/sim.yaml b/.github/workflows/sim.yaml index 4c15563c0..b1849b55d 100644 --- a/.github/workflows/sim.yaml +++ b/.github/workflows/sim.yaml @@ -43,6 +43,7 @@ jobs: - "sig-rsa validate-primary-slot direct-xip" - "sig-rsa validate-primary-slot ram-load multiimage" - "sig-rsa validate-primary-slot direct-xip multiimage" + - "sig-ecdsa hw-rollback-protection multiimage" runs-on: ubuntu-latest env: MULTI_FEATURES: ${{ matrix.features }} diff --git a/boot/bootutil/include/bootutil/caps.h b/boot/bootutil/include/bootutil/caps.h index 3c0ffb364..6448f537d 100644 --- a/boot/bootutil/include/bootutil/caps.h +++ b/boot/bootutil/include/bootutil/caps.h @@ -50,6 +50,7 @@ uint32_t bootutil_get_caps(void); #define BOOTUTIL_CAP_AES256 (1<<14) #define BOOTUTIL_CAP_RAM_LOAD (1<<15) #define BOOTUTIL_CAP_DIRECT_XIP (1<<16) +#define BOOTUTIL_CAP_HW_ROLLBACK_PROT (1<<17) /* * Query the number of images this bootloader is configured for. This diff --git a/boot/bootutil/src/caps.c b/boot/bootutil/src/caps.c index 63bcd6457..49bdfecf7 100644 --- a/boot/bootutil/src/caps.c +++ b/boot/bootutil/src/caps.c @@ -75,6 +75,9 @@ uint32_t bootutil_get_caps(void) #if defined(MCUBOOT_DIRECT_XIP) res |= BOOTUTIL_CAP_DIRECT_XIP; #endif +#if defined(MCUBOOT_HW_ROLLBACK_PROT) + res |= BOOTUTIL_CAP_HW_ROLLBACK_PROT; +#endif return res; } diff --git a/boot/bootutil/src/image_validate.c b/boot/bootutil/src/image_validate.c index 699017aa9..8260e5949 100644 --- a/boot/bootutil/src/image_validate.c +++ b/boot/bootutil/src/image_validate.c @@ -494,7 +494,7 @@ bootutil_img_validate(struct enc_key_data *enc_state, int image_index, * stored security counter value. */ fih_rc = fih_ret_encode_zero_equality(img_security_cnt < - fih_int_decode(security_cnt)); + (uint32_t)fih_int_decode(security_cnt)); if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { FIH_SET(fih_rc, FIH_FAILURE); goto out; diff --git a/sim/Cargo.toml b/sim/Cargo.toml index 44234f637..38f1b3751 100644 --- a/sim/Cargo.toml +++ b/sim/Cargo.toml @@ -30,6 +30,7 @@ ram-load = ["mcuboot-sys/ram-load"] 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 6d21bf5c5..c1403411b 100644 --- a/sim/mcuboot-sys/Cargo.toml +++ b/sim/mcuboot-sys/Cargo.toml @@ -83,6 +83,9 @@ downgrade-prevention = [] # 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 27da2e3ee..a01844e42 100644 --- a/sim/mcuboot-sys/build.rs +++ b/sim/mcuboot-sys/build.rs @@ -34,6 +34,7 @@ fn main() { 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 @@ fn main() { 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 000000000..0f79d8d41 --- /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 624b3e9bd..db8564d3c 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 struct BootsimRamInfo { 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, +} + +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 = RefCell::new(FlashContext::new()); pub static SIM_CTX: RefCell = RefCell::new(CSimContextPtr::new()); pub static RAM_CTX: RefCell = RefCell::new(BootsimRamInfo::default()); + pub static NV_COUNTER_CTX: RefCell = RefCell::new(NvCounterStorage::new()); } /// Set the flash device to be used by the simulation. The pointer is unsafely stashed away. @@ -317,3 +340,39 @@ pub extern fn sim_log_enabled(level: libc::c_int) -> libc::c_int { 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 e9bac0a9f..5d2c8ca1c 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 kw_encrypt(kek: &[u8], seckey: &[u8], keylen: u32) -> Result, &'s } } +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 e7240d108..912dda16d 100644 --- a/sim/src/caps.rs +++ b/sim/src/caps.rs @@ -27,6 +27,7 @@ pub enum Caps { 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 424f64679..9b46ca07d 100644 --- a/sim/src/image.rs +++ b/sim/src/image.rs @@ -221,11 +221,11 @@ impl ImagesBuilder { 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 @@ impl ImagesBuilder { 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 @@ impl ImagesBuilder { 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 @@ impl ImagesBuilder { 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 @@ impl ImagesBuilder { 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 @@ impl ImagesBuilder { 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) -> 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 @@ impl Images { 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 @@ fn image_largest_trailer(dev: &dyn Flash) -> usize { /// 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) -> ImageData { let offset = slot.base_off; let slot_len = slot.len; let dev_id = slot.dev_id; @@ -1658,6 +1707,8 @@ fn install_image(flash: &mut SimMultiFlash, slot: &SlotInfo, len: ImageSize, let mut tlv: Box = 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 @@ fn make_tlv() -> TlvGen { 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 c92404383..99c02d96a 100644 --- a/sim/src/tlv.rs +++ b/sim/src/tlv.rs @@ -59,6 +59,7 @@ pub enum TlvKinds { ENCEC256 = 0x32, ENCX25519 = 0x33, DEPENDENCY = 0x40, + SECCNT = 0x50, } #[allow(dead_code, non_camel_case_types)] @@ -108,6 +109,9 @@ pub trait ManifestGen { /// Return the current encryption key fn get_enc_key(&self) -> Vec; + + /// Set the security counter to the specified value. + fn set_security_counter(&mut self, security_cnt: Option); } #[derive(Debug, Default)] @@ -119,6 +123,7 @@ pub struct TlvGen { enc_key: Vec, /// Should this signature be corrupted. gen_corrupted: bool, + security_cnt: Option, } #[derive(Debug)] @@ -294,6 +299,15 @@ impl TlvGen { ..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 @@ impl ManifestGen for TlvGen { } 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::() 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 @@ impl ManifestGen for TlvGen { 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 @@ impl ManifestGen for TlvGen { protected_tlv.write_u32::(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::(TlvKinds::SECCNT as u16).unwrap(); + protected_tlv.write_u16::(std::mem::size_of::() as u16).unwrap(); + protected_tlv.write_u32::(self.security_cnt.unwrap() as u32).unwrap(); + } + assert_eq!(size, protected_tlv.len() as u16, "protected TLV length incorrect"); } @@ -765,6 +789,10 @@ impl ManifestGen for TlvGen { } self.enc_key.clone() } + + fn set_security_counter(&mut self, security_cnt: Option) { + self.security_cnt = security_cnt; + } } include!("rsa_pub_key-rs.txt"); diff --git a/sim/tests/core.rs b/sim/tests/core.rs index 50e0bba5d..45233de3d 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!(downgrade_prevention, make_image(&REV_DEPS, true), run_nodowngrade()); 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, {