diff --git a/bao1x-boot/boot0/src/main.rs b/bao1x-boot/boot0/src/main.rs index b5e56c47b..fb9c2f54b 100644 --- a/bao1x-boot/boot0/src/main.rs +++ b/bao1x-boot/boot0/src/main.rs @@ -20,7 +20,9 @@ use bao1x_api::{ }; use bao1x_api::{PARANOID_MODE, PARANOID_MODE_DUPE}; use bao1x_hal::acram::OneWayCounter; -use bao1x_hal::hardening::{check_pll, die, mesh_setup}; +use bao1x_hal::hardening::{ + check_pll, die, disable_clock_skipping, mesh_setup, reseed_skipping, setup_clock_skipping, +}; use bao1x_hal::sigcheck::{hardened_erase_policy, jump_to}; #[cfg(feature = "unsafe-dev")] use critical_section::Mutex; @@ -276,6 +278,7 @@ pub unsafe extern "C" fn rust_entry() -> ! { } } + let use_skipping = setup_clock_skipping(csprng.get_u32()); let (paranoid1, paranoid2) = owc.hardened_get2(PARANOID_MODE, PARANOID_MODE_DUPE).unwrap(); // == self-validate the image with the keys we put in, just to make sure our code wasn't tampered with == @@ -332,6 +335,9 @@ pub unsafe extern "C" fn rust_entry() -> ! { for configuration in boot_order { bollard!(die, 4); csprng.random_delay(); + if use_skipping { + reseed_skipping(csprng.get_u32()); + } match bao1x_hal::sigcheck::validate_image(configuration, None, Some(&mut csprng)) { Ok((key, key_inv, tag, target)) => { if key != !key_inv { @@ -352,6 +358,9 @@ pub unsafe extern "C" fn rust_entry() -> ! { } csprng.random_delay(); bollard!(die, 4); + if use_skipping { + disable_clock_skipping(); + } jump_to((target ^ u32::from_le_bytes(tag)) as usize); } Err(e) => { diff --git a/bao1x-boot/boot1/Cargo.toml b/bao1x-boot/boot1/Cargo.toml index 23c99563d..6677fc274 100644 --- a/bao1x-boot/boot1/Cargo.toml +++ b/bao1x-boot/boot1/Cargo.toml @@ -48,6 +48,7 @@ print-ifr = [] test-boot0-keys = [] alt-boot1 = [] +test-clock-skipping = [] oem = [] oem-baosec-lite = ["oem", "bao1x-hal/oem-baosec-lite"] diff --git a/bao1x-boot/boot1/src/repl.rs b/bao1x-boot/boot1/src/repl.rs index 09422b912..2465857a6 100644 --- a/bao1x-boot/boot1/src/repl.rs +++ b/bao1x-boot/boot1/src/repl.rs @@ -6,8 +6,8 @@ use alloc::vec::Vec; use bao1x_api::pubkeys::BOOT0_TO_BOOT1; #[allow(unused_imports)] use bao1x_api::*; -use bao1x_hal::acram::OneWayCounter; -use bao1x_hal::hardening::Csprng; +use bao1x_hal::acram::{OneWayCounter, SlotManager}; +use bao1x_hal::hardening::{Csprng, skipping_enabled}; use utralib::*; pub struct Error { @@ -240,6 +240,65 @@ impl Repl { )); } } + "skipping" => { + let slot_mgr = SlotManager::new(); + if args.len() != 1 { + return Err(Error::help("skipping [check | enable | disable]")); + } + if args[0] == "check" { + let skipping_cfg = slot_mgr.read(&bao1x_api::CLOCK_SCRAMBLE_PARAMS).unwrap(); + crate::println!("Clock skipping: {:?}", skipping_enabled(skipping_cfg)); + } else if args[0] == "enable" { + bao1x_hal::hardening::enable_skipping(); + } else if args[0] == "disable" { + bao1x_hal::hardening::disable_skipping(); + } else { + return Err(Error::help("skipping [check | enable | disable]")); + } + } + #[cfg(feature = "test-clock-skipping")] + "bogomips" => { + crate::println!("start test"); + bao1x_hal::hardening::enable_skipping(); + // start the RTC + let mut ao_sysctrl = CSR::new(utralib::HW_AO_SYSCTRL_BASE as *mut u32); + ao_sysctrl.wo(utra::ao_sysctrl::CR_CLK1HZFD, 0x3fff); + unsafe { (0x4006100c as *mut u32).write_volatile(1) }; + let mut count: usize; + unsafe { + #[rustfmt::skip] + core::arch::asm!( + // grab the RTC value + "li t0, 0x40061000", + "lw t1, 0x0(t0)", + "li t3, 0", + // wait until the next second + "10:", + "lw t2, 0x0(t0)", + "beq t1, t2, 10b", + // start of test + "20:", + // count outer loops + "addi t3, t3, 1", + // inner loop 10,000 times + "li t4, 10000", + "30:", + "addi t4, t4, -1", + "bne x0, t4, 30b", + // after inner loop, check current time; do another outer loop if time is same + "lw t1, 0x0(t0)", + "beq t1, t2, 20b", + out("t0") _, + out("t1") _, + out("t2") _, + out("t3") count, + out("t4") _, + ); + } + crate::println!("{}.{} bogomips", (count * 2 * 10_000) / 1_000_000, (count * 2) % 10_000); + bao1x_hal::hardening::disable_skipping(); + ao_sysctrl.wo(utra::ao_sysctrl::CR_CLK1HZFD, 15); + } "boardtype" => { let one_way = OneWayCounter::new(); if args.len() == 0 { @@ -581,7 +640,7 @@ impl Repl { _ => { crate::println!("Command not recognized: {}", cmd); crate::print!( - "Commands include: reset, echo, altboot, boot, bootwait, idmode, localecho, uf2, boardtype, audit, lockdown, paranoid, self_destruct, ifr" + "Commands include: reset, echo, altboot, boot, bootwait, idmode, localecho, uf2, boardtype, audit, lockdown, paranoid, self_destruct, ifr, skipping" ); #[cfg(feature = "test-boot0-keys")] crate::print!(", publock"); diff --git a/bao1x-boot/boot1/src/secboot.rs b/bao1x-boot/boot1/src/secboot.rs index 4bcf6a976..0f6577e86 100644 --- a/bao1x-boot/boot1/src/secboot.rs +++ b/bao1x-boot/boot1/src/secboot.rs @@ -1,7 +1,7 @@ use bao1x_api::{ DEVELOPER_MODE, PARANOID_MODE, PARANOID_MODE_DUPE, bollard, pubkeys::BOOT1_TO_LOADER_OR_BAREMETAL, }; -use bao1x_hal::hardening::Csprng; +use bao1x_hal::hardening::{Csprng, disable_clock_skipping, reseed_skipping, setup_clock_skipping}; #[inline(always)] fn seal_boot1_keys() { @@ -19,6 +19,7 @@ fn seal_boot1_keys() { } pub fn try_boot(or_die: bool, csprng: &mut Csprng) { + let use_skipping = setup_clock_skipping(csprng.get_u32()); let one_way = bao1x_hal::acram::OneWayCounter::new(); bollard!(bao1x_hal::sigcheck::die_no_std, 4); @@ -96,6 +97,9 @@ pub fn try_boot(or_die: bool, csprng: &mut Csprng) { // double up the security check if in paranoid mode csprng.random_delay(); if paranoid1 != 0 || paranoid2 != 0 { + if use_skipping { + reseed_skipping(csprng.get_u32()); + } bollard!(bao1x_hal::sigcheck::die_no_std, 4); bao1x_hal::sigcheck::validate_image(BOOT1_TO_LOADER_OR_BAREMETAL, None, Some(csprng)) .unwrap_or_else(|_| bao1x_hal::hardening::die()); @@ -108,6 +112,9 @@ pub fn try_boot(or_die: bool, csprng: &mut Csprng) { // like we do in boot0. seal_boot1_keys(); bollard!(bao1x_hal::sigcheck::die_no_std, 4); + if use_skipping { + disable_clock_skipping(); + } bao1x_hal::sigcheck::jump_to((target ^ u32::from_le_bytes(tag)) as usize); } Err(e) => crate::println!("Image did not validate: {:?}", e), @@ -116,4 +123,7 @@ pub fn try_boot(or_die: bool, csprng: &mut Csprng) { crate::println!("No valid loader or baremetal image found. Halting!"); bao1x_hal::sigcheck::die_no_std(); } + if use_skipping { + disable_clock_skipping(); + } } diff --git a/libs/bao1x-api/src/offsets/baosec.rs b/libs/bao1x-api/src/offsets/baosec.rs index a60ba4c3f..c85dd2fd7 100644 --- a/libs/bao1x-api/src/offsets/baosec.rs +++ b/libs/bao1x-api/src/offsets/baosec.rs @@ -42,33 +42,6 @@ pub const CPU_VDD_LDO_BOOT_MV: u32 = 810; pub const DEFAULT_FCLK_FREQUENCY: u32 = 700_000_000; // =========== KEY SLOTS ============== -/// The `ROOT_SEED` is the "mothership" secret that all device identity and secrets -/// are derived from. The seed may be blended with other bits of data scattered about -/// the RRAM array, other device identifiers and hardware measurements to create the final -/// device secret key. -pub const ROOT_SEED: SlotIndex = SlotIndex::Data(256, PartitionAccess::Fw0, RwPerms::ReadWrite); - -/// The `RMA_KEY` is a secret parameter that is unique per-device secret which is recorded -/// at manufacturing time. Its purpose is to facilitate the creation of a signed RMA -/// authorization certificate which upon receipt would blank the device and unlock various -/// features for debugging failed hardware. -pub const RMA_KEY: SlotIndex = SlotIndex::Data(257, PartitionAccess::Fw0, RwPerms::ReadWrite); - -/// Reserved for use as a CP to FT tracking cookie. This is used to help track inventory -/// between CP and FT, if such a feature is desired in the supply chain. Blanked on entry -/// to developer mode. -pub const CP_COOKIE: SlotIndex = SlotIndex::Data(258, PartitionAccess::Fw0, RwPerms::ReadWrite); - -/// The swap encryption key. Used to protect swap images beyond the signing key, if we so desire. -pub const SWAP_KEY: SlotIndex = SlotIndex::Data(259, PartitionAccess::Fw0, RwPerms::ReadWrite); - -/// A test value placed by FT into the memory array. If you can read the original value, you've captured a -/// flag! -/// There is a second flag stored somewhere else. Can you find it? -pub const THE_FLAG_1: SlotIndex = SlotIndex::Data(260, PartitionAccess::Fw0, RwPerms::ReadWrite); - -// NOTE: COLLATERAL is from 261..265, but it is in the "common" page - /// `NUISANCE_KEYS` are hashed together with `ROOT_SEED` to derive the core secret. /// Their primary purpose is to annoy microscopists trying to read the secret key by /// directly imaging the RRAM array. They also exist to reduce power side channels @@ -89,15 +62,14 @@ pub const THE_FLAG_1: SlotIndex = SlotIndex::Data(260, PartitionAccess::Fw0, RwP /// is convenient because that's the natural page size over which the key range will be carved up before /// handing to other processes. /// -/// The first 8 keys in bank 0 of NUISANCE_KEYS is not used. This is because due to an ECO in the A1 -/// spin of silicon, the JTAG-wired access control on these is going to be tied to those on the similarly -/// numbered data slots. Some of the data slots in the first 8 slots will be read-only at CP time, and -/// thus they can't be initialized with random data and be used as a nuisance key. This is a minor degradation -/// in security margin. +/// The first 8 keys in bank 0 of NUISANCE_KEYS alias with data slots 0..7. This is because due to an ECO in +/// the A1 spin of silicon that effectively removes the key bank, meaning only data banks exist. +/// The IFR-managed access control on these is going to be tied to those on the similarly numbered data slots, +/// and the control happens on a stride of 8 slots at a time. +/// +/// Notice there is a second range of nuisance keys at the end of the slot range (1920..2048), stuck there +/// to force spatial diversity in the nuisance keys. pub const NUISANCE_KEYS_0: SlotIndex = SlotIndex::DataRange(8..128, PartitionAccess::Fw0, RwPerms::ReadWrite); -pub const NUISANCE_KEYS_1: SlotIndex = - SlotIndex::DataRange(1920..2048, PartitionAccess::Fw0, RwPerms::ReadWrite); -pub const NUISANCE_KEYS: [SlotIndex; 2] = [NUISANCE_KEYS_0, NUISANCE_KEYS_1]; /// `CHAFF_KEYS` are a bank of keys that are hashed into the key array, but instead of /// being read out in strict order, they are read in a random permutation every time. @@ -131,7 +103,41 @@ pub const CHAFF_KEYS: SlotIndex = SlotIndex::DataRange(128..256, PartitionAccess /// Placing this at the end of the key array ostensibly means that all the keys before it were /// erased; it might be possible to glitch all the way to the end and just have this one erased /// but I think that is reasonably unlikely... -pub const ERASE_PROOF: SlotIndex = SlotIndex::Data(256, PartitionAccess::Fw0, RwPerms::ReadWrite); +pub const ERASE_PROOF: SlotIndex = SlotIndex::Data(255, PartitionAccess::Fw0, RwPerms::ReadWrite); + +/// The `ROOT_SEED` is the "mothership" secret that all device identity and secrets +/// are derived from. The seed may be blended with other bits of data scattered about +/// the RRAM array, other device identifiers and hardware measurements to create the final +/// device secret key. +pub const ROOT_SEED: SlotIndex = SlotIndex::Data(256, PartitionAccess::Fw0, RwPerms::ReadWrite); + +/// The `RMA_KEY` is a secret parameter that is unique per-device secret which is recorded +/// at manufacturing time. Its purpose is to facilitate the creation of a signed RMA +/// authorization certificate which upon receipt would blank the device and unlock various +/// features for debugging failed hardware. +pub const RMA_KEY: SlotIndex = SlotIndex::Data(257, PartitionAccess::Fw0, RwPerms::ReadWrite); + +/// Reserved for use as a CP to FT tracking cookie. This is used to help track inventory +/// between CP and FT, if such a feature is desired in the supply chain. Blanked on entry +/// to developer mode. +pub const CP_COOKIE: SlotIndex = SlotIndex::Data(258, PartitionAccess::Fw0, RwPerms::ReadWrite); + +/// The swap encryption key. Used to protect swap images beyond the signing key, if we so desire. +pub const SWAP_KEY: SlotIndex = SlotIndex::Data(259, PartitionAccess::Fw0, RwPerms::ReadWrite); + +/// A test value placed by FT into the memory array. If you can read the original value, you've captured a +/// flag! +/// There is a second flag stored somewhere else. Can you find it? +pub const THE_FLAG_1: SlotIndex = SlotIndex::Data(260, PartitionAccess::Fw0, RwPerms::ReadWrite); + +// [261..=383] reserved for Baochip data slot usage, see the "common" page for more details +// NOTE: COLLATERAL is from 261..265, but it is in the "common" page + +// [384..=1919] are unused and available for third party use + +pub const NUISANCE_KEYS_1: SlotIndex = + SlotIndex::DataRange(1920..2048, PartitionAccess::Fw0, RwPerms::ReadWrite); +pub const NUISANCE_KEYS: [SlotIndex; 2] = [NUISANCE_KEYS_0, NUISANCE_KEYS_1]; /// All the slots of concern located in a single iterator. The idea is that everything is /// condensed here and used to check for access integrity using the array below. diff --git a/libs/bao1x-api/src/offsets/common.rs b/libs/bao1x-api/src/offsets/common.rs index ca0ac9e42..ba3a31408 100644 --- a/libs/bao1x-api/src/offsets/common.rs +++ b/libs/bao1x-api/src/offsets/common.rs @@ -237,6 +237,8 @@ pub const BAO2_PUBKEY: SlotIndex = SlotIndex::Data(5, PartitionAccess::All, RwPe pub const BETA_PUBKEY: SlotIndex = SlotIndex::Data(6, PartitionAccess::All, RwPerms::ReadOnly); pub const DEV_PUBKEY: SlotIndex = SlotIndex::Data(7, PartitionAccess::All, RwPerms::ReadOnly); +// [8..=260 used as nuisance and master keys] + /// Collateral is a data range that is *always* erased every time the system transitions from boot0 /// to a Baochip-signed boot1. The only condition under which it is not erased is if *all* the boot1 keys /// in slots 0, 1, *and* 2 do *not* match the Baochip set. @@ -269,6 +271,14 @@ pub const BOOT1_PK_RECEIPT_SLOT3: SlotIndex = SlotIndex::Data(268, PartitionAcce pub const BOOT1_RECEIPT_SLOTS: [SlotIndex; 4] = [BOOT1_PK_RECEIPT_SLOT0, BOOT1_PK_RECEIPT_SLOT1, BOOT1_PK_RECEIPT_SLOT2, BOOT1_PK_RECEIPT_SLOT3]; +// [269..=271] reserved for future use + +// 32 bytes reserved to configure/set up clock scrambling +pub const CLOCK_SCRAMBLE_PARAMS: SlotIndex = SlotIndex::Data(272, PartitionAccess::Open, RwPerms::ReadWrite); + +// [273..=383] available for future data slot usage - in particular opentitan provisioning block (2k of data) +// can fit in this with some margin. + // Notes on defining the boot0 IFR region. // RISC-V boot0 region start is defined by IFR slot 6, bits [55:48] // RISC-V boot1 region start (which is boot0 end) is defined by IFR slot 6, bits[47:40] diff --git a/libs/bao1x-hal/src/hardening.rs b/libs/bao1x-hal/src/hardening.rs index 82e1bbf1c..f567b8c2d 100644 --- a/libs/bao1x-hal/src/hardening.rs +++ b/libs/bao1x-hal/src/hardening.rs @@ -133,6 +133,8 @@ impl Csprng { bao1x_api::bollard!(die, 4); } } + + pub fn get_u32(&mut self) -> u32 { self.csprng.next_u32() } } #[inline(always)] @@ -145,6 +147,81 @@ pub fn paranoid_mode() { sensor.wo(utra::sensorc::SFR_VDMASK1, 0); // putting 0 here makes the chip reset on glitch detect } +// getters to unpack the data slot +pub fn skipping_enabled(cfg: &[u8]) -> bool { cfg[0] != 0 } +pub fn skipping_config(cfg: &[u8]) -> u32 { u32::from_le_bytes(cfg[4..8].try_into().unwrap_or([0u8; 4])) } + +pub fn enable_skipping() { + let slot_mgr = crate::acram::SlotManager::new(); + let mut rram = crate::rram::Reram::new(); + // hard-coded config: + // [0] => enable if != 0 + // [4] => bits [7:0] of cgu_sec + // [5] => bits [15:8] of cgu_sec + // enable for main clock: + // assign clkcipherlevel = cgusec[3:0]; + // assign clkcipheren = cgusec[4]; + // enable for PKE engine: + // assign clkpkecipherlevel = cgusec[11:8]; + // assign clkpkecipheren = cgusec[12]; + // + // Pseudocode: + // let prng: u8 = lfsr.get_u8(); + // if prng >= ((cipher_level as u8) << 4) { + // clock_run + // } else { + // clock_skip + // } + // + // A value of 2 for cipherlevel means, on average, 12.5% of clocks are skipped. This means the CPU + // runs effectively this much slower. + slot_mgr + .write( + &mut rram, + &bao1x_api::CLOCK_SCRAMBLE_PARAMS, + // tested + // - skipping on PKE is OK + // - skipping on CPU has some instability + &[ + 1, 0, 0, 0, 0b0_0010, 0b1_0010, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ], + ) + .ok(); +} + +pub fn disable_skipping() { + let slot_mgr = crate::acram::SlotManager::new(); + let mut rram = crate::rram::Reram::new(); + slot_mgr.write(&mut rram, &bao1x_api::CLOCK_SCRAMBLE_PARAMS, &[0u8; 32]).ok(); +} + +pub fn setup_clock_skipping(seed: u32) -> bool { + let slot_mgr = crate::acram::SlotManager::new(); + let scramble_cfg = slot_mgr.read(&bao1x_api::CLOCK_SCRAMBLE_PARAMS).unwrap_or(&[0u8; 32]); + let use_scrambling = skipping_enabled(scramble_cfg); + if use_scrambling { + let cgu_sec = skipping_config(scramble_cfg); + let mut cgu = CSR::new(utra::sysctrl::HW_SYSCTRL_BASE as *mut u32); + cgu.wo(utra::sysctrl::SFR_SEED, seed); + cgu.wo(utra::sysctrl::SFR_SEEDAR, 0x5a); + cgu.wo(utra::sysctrl::SFR_CGUSEC, cgu_sec); + } + use_scrambling +} + +pub fn disable_clock_skipping() { + let mut cgu = CSR::new(utra::sysctrl::HW_SYSCTRL_BASE as *mut u32); + cgu.wo(utra::sysctrl::SFR_CGUSEC, 0); + cgu.wo(utra::sysctrl::SFR_CGUSEC, 0); +} + +pub fn reseed_skipping(seed: u32) { + let mut cgu = CSR::new(utra::sysctrl::HW_SYSCTRL_BASE as *mut u32); + cgu.wo(utra::sysctrl::SFR_SEED, seed); + cgu.wo(utra::sysctrl::SFR_SEEDAR, 0x5a); +} + /// This call clears any current alarms and resets sensors, but does not /// enable (or re-enable) interrupts pub fn reset_sensors() { @@ -182,6 +259,12 @@ pub fn reset_sensors() { sensor.wo(utra::sensorc::SFR_LDCFG, 0xc); sensor.wo(utra::sensorc::SFR_LDMASK, 0x0); // turn on both sensors bao1x_api::bollard!(die, 4); + + // enable SRAM parity alarms + let mut sramtrm = CSR::new(utra::coresub_sramtrm::HW_CORESUB_SRAMTRM_BASE as *mut u32); + // enables for all SRAM types: + // assign { aorameven, iframeven, tcmeven, srameven } = ramsec; + sramtrm.wo(utra::coresub_sramtrm::SFR_RAMSEC, 0xF); } /// Mesh is a bit challenging to check on a fast-boot system. This is because it diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs index da7cf15a1..5129fff85 100644 --- a/xtask/src/utils.rs +++ b/xtask/src/utils.rs @@ -270,16 +270,30 @@ pub(crate) fn ensure_compiler( }, }; - let first_asset = match assets.first() { - None => continue, - Some(s) => s, - }; + // Find the asset that matches the target architecture + let matching_asset = assets.iter().find(|asset| { + asset + .get("name") + .and_then(|n| n.as_str()) + .map(|name| { + // Extract the toolchain prefix (everything before the version number) + if let Some(toolchain_prefix) = name.split('_').next() { + target.starts_with(toolchain_prefix) + } else { + false + } + }) + .unwrap_or(false) + }); - let download_url = match first_asset.get("browser_download_url") { + let download_url = match matching_asset { None => continue, - Some(s) => match s.as_str() { + Some(asset) => match asset.get("browser_download_url") { None => continue, - Some(s) => s, + Some(s) => match s.as_str() { + None => continue, + Some(s) => s, + }, }, }; // println!("Candidate Release: {}", download_url);