diff --git a/bao1x-boot/boot1/src/main.rs b/bao1x-boot/boot1/src/main.rs index 13398cc61..c70fdb288 100644 --- a/bao1x-boot/boot1/src/main.rs +++ b/bao1x-boot/boot1/src/main.rs @@ -16,7 +16,8 @@ use core::{ sync::atomic::{AtomicBool, AtomicU32, Ordering}, }; -use bao1x_api::{BoardTypeCoding, BootWaitCoding}; +use bao1x_api::{BoardTypeCoding, BootWaitCoding, bollard}; +use bao1x_hal::hardening::*; use bao1x_hal::{board::KeyPress, iox::Iox, usb::driver::UsbDeviceState}; use bao1x_hal::{sh1107::Oled128x128, udma::GlobalConfig}; use critical_section::Mutex; @@ -71,6 +72,10 @@ pub fn uart_irq_handler() { /// This function is safe to call exactly once. #[export_name = "rust_entry"] pub unsafe extern "C" fn rust_entry() -> ! { + // clocks and TRNG are set up by boot0 already + let mut csprng = Csprng::new(); + csprng.random_delay(); + let one_way = bao1x_hal::acram::OneWayCounter::new(); let mut board_type = one_way.get_decoded::().expect("Board type coding error"); @@ -78,6 +83,11 @@ pub unsafe extern "C" fn rust_entry() -> ! { crate::println_d!("TX_IDLE: {:?}", crate::platform::usb::TX_IDLE.load(Ordering::SeqCst)); let perclk: u32; (board_type, perclk) = crate::platform::early_init(board_type); + // ensure we're running off the PLL + csprng.random_delay(); + bollard!(die, 4); + check_pll(); + #[cfg(not(feature = "alt-boot1"))] crate::println!("\n~~Boot1 up! ({}: {})~~\n", crate::version::SEMVER, RELEASE_DESCRIPTION); #[cfg(feature = "alt-boot1")] @@ -117,9 +127,31 @@ pub unsafe extern "C" fn rust_entry() -> ! { let one_way = bao1x_hal::acram::OneWayCounter::new(); let boot_wait = one_way.get_decoded::().expect("internal error"); + // ensure we're running off the PLL + csprng.random_delay(); + bollard!(die, 4); + check_pll(); + // Follow up the mesh check in boot1 - it takes 100ms or so for the mesh to settle, we can't afford + // to wait that long in boot0. + // Note: for alpha firmwares that don't set up the mesh check, it's thought that this won't create + // a false alarm because you effectively have a settling time from "whatever" the pattern is by default. + // So, what alpha users miss out on is the half of the states set up & checked by boot0 in the beta+ + // firmwares. + bao1x_hal::hardening::mesh_check_and_react(&mut csprng, &one_way); + bollard!(4); + // Mesh check takes 100ms for the signal to propagate. Setup the mesh check here, then check the + // result in loader. This is the opposite polarity of what was checked in boot0. + mesh_setup(true, None); + bollard!(4); + // This checks the attack counter and applies a reaction policy. This is done in boot1 so that it + // has a chance to be updated. + bao1x_hal::hardening::apply_attack_policy(&mut csprng, &one_way); + + // Below is our first divergence out of boot1, and thus, all security checks must happen above + // this line! if boot_wait == BootWaitCoding::Disable && current_key.is_none() { // diverges if there is code to run - try_boot(false); + try_boot(false, &mut csprng); // or_die == false means the rest of this gets run if there is no valid image } @@ -277,10 +309,21 @@ pub unsafe extern "C" fn rust_entry() -> ! { } } - boot(&iox, oled, se0_port, se0_pin) + // ensure we're running off the PLL + csprng.random_delay(); + bollard!(die, 4); + check_pll(); + + boot(&iox, oled, se0_port, se0_pin, &mut csprng) } -pub fn boot(iox: &Iox, mut oled: Option, se0_port: bao1x_api::IoxPort, se0_pin: u8) -> ! { +pub fn boot( + iox: &Iox, + mut oled: Option, + se0_port: bao1x_api::IoxPort, + se0_pin: u8, + csprng: &mut Csprng, +) -> ! { if let Some(ref mut sh1107) = oled { marquee(sh1107, "Booting..."); } @@ -318,7 +361,7 @@ pub fn boot(iox: &Iox, mut oled: Option, se0_port: bao1x_api::IoxPo } // when we get to this point, there's only two options... - try_boot(true); + try_boot(true, csprng); unreachable!("`or_die = true` means this should be unreachable"); } diff --git a/bao1x-boot/boot1/src/platform/bao1x/bao1x.rs b/bao1x-boot/boot1/src/platform/bao1x/bao1x.rs index 09d7eeb33..0e0d62e6b 100644 --- a/bao1x-boot/boot1/src/platform/bao1x/bao1x.rs +++ b/bao1x-boot/boot1/src/platform/bao1x/bao1x.rs @@ -409,49 +409,6 @@ mod panic_handler { } } -/// This takes in the FD input frequency (the frequency to be divided) in MHz -/// and the fd value, and returns the resulting divided frequency. -/// *not tested* -#[allow(dead_code)] -pub fn fd_to_clk(fd_in_mhz: u32, fd_val: u32) -> u32 { (fd_in_mhz * (fd_val + 1)) / 256 } - -/// Takes in the FD input frequencyin MHz, and then the desired frequency. -/// Returns Some((fd value, deviation in *hz*, not MHz)) if the requirement is satisfiable -/// Returns None if the equation is ill-formed. -/// *not tested* -#[allow(dead_code)] -pub fn clk_to_fd(fd_in_mhz: u32, desired_mhz: u32) -> Option<(u32, i32)> { - let platonic_fd: u32 = (desired_mhz * 256) / fd_in_mhz; - if platonic_fd > 0 { - let actual_fd = platonic_fd - 1; - let actual_clk = fd_to_clk(fd_in_mhz, actual_fd); - Some((actual_fd, desired_mhz as i32 - actual_clk as i32)) - } else { - None - } -} - -/// Takes in the top clock in MHz, desired perclk in MHz, and returns a tuple of -/// (min cycle, fd, actual freq) -/// *tested* -pub fn clk_to_per(top_in_mhz: u32, perclk_in_mhz: u32) -> Option<(u8, u8, u32)> { - let fd_platonic = ((256 * perclk_in_mhz) / (top_in_mhz / 2)).min(256); - if fd_platonic > 0 { - let fd = fd_platonic - 1; - let min_cycle = (2 * (256 / (fd + 1))).max(1); - let min_freq = top_in_mhz / min_cycle; - let target_freq = top_in_mhz * (fd + 1) / 512; - let actual_freq = target_freq.max(min_freq); - if fd < 256 && min_cycle < 256 && min_cycle > 0 { - Some(((min_cycle - 1) as u8, fd as u8, actual_freq)) - } else { - None - } - } else { - None - } -} - pub unsafe fn init_clock_asic(freq_hz: u32) -> u32 { use utra::sysctrl; let daric_cgu = sysctrl::HW_SYSCTRL_BASE as *mut u32; @@ -460,8 +417,7 @@ pub unsafe fn init_clock_asic(freq_hz: u32) -> u32 { const UNIT_MHZ: u32 = 1000 * 1000; const PFD_F_MHZ: u32 = 16; const FREQ_0: u32 = 16 * UNIT_MHZ; - const FREQ_OSC_MHZ: u32 = 48; // Actually 48MHz - const M: u32 = FREQ_OSC_MHZ / PFD_F_MHZ; // - 1; // OSC input was 24, replace with 48 + const M: u32 = bao1x_api::FREQ_OSC_MHZ / PFD_F_MHZ; // - 1; // OSC input was 24, replace with 48 const TBL_Q: [u16; 7] = [ // keep later DIV even number as possible @@ -500,19 +456,19 @@ pub unsafe fn init_clock_asic(freq_hz: u32) -> u32 { // perclk divider - set to divide by 8 off of an 800Mhz base. Only found on bao1x. // TODO: this only works for two clock settings. Broken @ 600. Need to fix this to instead derive // what pclk is instead of always reporting 100mhz - let (min_cycle, fd, perclk) = if let Some((min_cycle, fd, perclk)) = clk_to_per(freq_hz / 1_000_000, 100) - { - daric_cgu - .add(utra::sysctrl::SFR_CGUFDPER.offset()) - .write_volatile((min_cycle as u32) << 16 | (fd as u32) << 8 | fd as u32); - (min_cycle, fd, perclk * 1_000_000) - } else if freq_hz > 400_000_000 { - daric_cgu.add(utra::sysctrl::SFR_CGUFDPER.offset()).write_volatile(0x07_ff_ff); - (7, 0xff, freq_hz / 8) - } else { - daric_cgu.add(utra::sysctrl::SFR_CGUFDPER.offset()).write_volatile(0x03_ff_ff); - (3, 0xff, freq_hz / 4) - }; + let (min_cycle, fd, perclk) = + if let Some((min_cycle, fd, perclk)) = bao1x_api::clk_to_per(freq_hz / 1_000_000, 100) { + daric_cgu + .add(utra::sysctrl::SFR_CGUFDPER.offset()) + .write_volatile((min_cycle as u32) << 16 | (fd as u32) << 8 | fd as u32); + (min_cycle, fd, perclk * 1_000_000) + } else if freq_hz > 400_000_000 { + daric_cgu.add(utra::sysctrl::SFR_CGUFDPER.offset()).write_volatile(0x07_ff_ff); + (7, 0xff, freq_hz / 8) + } else { + daric_cgu.add(utra::sysctrl::SFR_CGUFDPER.offset()).write_volatile(0x03_ff_ff); + (3, 0xff, freq_hz / 4) + }; /* perclk fields: min-cycle-lp | min-cycle | fd-lp | fd @@ -565,7 +521,7 @@ pub unsafe fn init_clock_asic(freq_hz: u32) -> u32 { // DARIC_CGU->cgusel1 = 1; // 0: RC, 1: XTAL cgu.wo(sysctrl::SFR_CGUSEL1, 1); // DARIC_CGU->cgufscr = FREQ_OSC_MHZ; // external crystal is 48MHz - cgu.wo(sysctrl::SFR_CGUFSCR, FREQ_OSC_MHZ); + cgu.wo(sysctrl::SFR_CGUFSCR, bao1x_api::FREQ_OSC_MHZ); // __DSB(); // DARIC_CGU->cguset = 0x32UL; cgu.wo(sysctrl::SFR_CGUSET, 0x32); @@ -574,7 +530,7 @@ pub unsafe fn init_clock_asic(freq_hz: u32) -> u32 { let duart = utra::duart::HW_DUART_BASE as *mut u32; duart.add(utra::duart::SFR_CR.offset()).write_volatile(0); // set the ETUC now that we're on the xosc. - duart.add(utra::duart::SFR_ETUC.offset()).write_volatile(FREQ_OSC_MHZ); + duart.add(utra::duart::SFR_ETUC.offset()).write_volatile(bao1x_api::FREQ_OSC_MHZ); duart.add(utra::duart::SFR_CR.offset()).write_volatile(1); if freq_hz <= 1_000_000 { diff --git a/bao1x-boot/boot1/src/secboot.rs b/bao1x-boot/boot1/src/secboot.rs index b6ebf15d5..da2e16c65 100644 --- a/bao1x-boot/boot1/src/secboot.rs +++ b/bao1x-boot/boot1/src/secboot.rs @@ -1,4 +1,5 @@ -use bao1x_api::signatures::FunctionCode; +use bao1x_api::{bollard, signatures::FunctionCode}; +use bao1x_hal::hardening::Csprng; pub const ALLOWED_FUNCTIONS: [u32; 5] = [ FunctionCode::Baremetal as u32, @@ -8,6 +9,7 @@ pub const ALLOWED_FUNCTIONS: [u32; 5] = [ FunctionCode::Developer as u32, ]; +#[inline(always)] fn seal_boot1_keys() { // This is a security-critical initialization. Failure to do this correctly totally breaks // the hardware access control scheme for key/data slots. @@ -22,7 +24,7 @@ fn seal_boot1_keys() { cu.protect(); } -pub fn try_boot(or_die: bool) { +pub fn try_boot(or_die: bool, csprng: &mut Csprng) { // loader is at the same offset as baremetal. Accept either as valid boot. // This diverges if the signature check is successful match bao1x_hal::sigcheck::validate_image( @@ -32,13 +34,18 @@ pub fn try_boot(or_die: bool) { &ALLOWED_FUNCTIONS, false, None, + Some(csprng), ) { - Ok((k, tag)) => { + Ok((k, k2, tag)) => { crate::println!( - "Booting with key {}({})", + "Booting with key {}/{}({})", k, + k2, core::str::from_utf8(&tag).unwrap_or("invalid tag") ); + if k != k2 { + bao1x_hal::sigcheck::die_no_std(); + } // disable IRQs in preparation for next phase crate::platform::irq::disable_all_irqs(); @@ -50,12 +57,15 @@ pub fn try_boot(or_die: bool) { || k == bao1x_api::pubkeys::DEVELOPER_KEY_SLOT { crate::println!("Developer key detected, ensuring secrets are erased"); - bao1x_hal::sigcheck::erase_secrets(); + bao1x_hal::sigcheck::erase_secrets(&mut Some(csprng)); } // this has to be called *after* erase_secrets, because we can't erase the secrets // once the mappings have been sealed off. This is why we can't use the auto-jump method // like we do in boot0. + csprng.random_delay(); + bollard!(bao1x_hal::sigcheck::die_no_std, 4); seal_boot1_keys(); + bollard!(bao1x_hal::sigcheck::die_no_std, 4); bao1x_hal::sigcheck::jump_to(bao1x_api::LOADER_START); } Err(e) => crate::println!("Image did not validate: {:?}", e),