Merge branch 'main' into source-date-epoch

This commit is contained in:
bunnie 2026-02-11 01:27:12 +08:00 committed by GitHub
commit 4aba11df5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 240 additions and 48 deletions

View file

@ -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) => {

View file

@ -48,6 +48,7 @@ print-ifr = []
test-boot0-keys = []
alt-boot1 = []
test-clock-skipping = []
oem = []
oem-baosec-lite = ["oem", "bao1x-hal/oem-baosec-lite"]

View file

@ -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");

View file

@ -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();
}
}

View file

@ -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.

View file

@ -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]

View file

@ -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

View file

@ -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);