From 5f88202d1c176f91c1c15a68952faab977aa5400 Mon Sep 17 00:00:00 2001 From: Christopher Swenson Date: Tue, 17 Dec 2024 22:39:13 -0800 Subject: [PATCH 1/3] emulator: Decode PMP NAPOT addresses correctly (#1858) Decoding NAPOT (natural power of 2) PMP regions involves some bit twiddly math, and the current code did it incorrectly, which I discovered when testing against a Tock library. I added some tests for the function as well. --- sw-emulator/lib/cpu/src/csr_file.rs | 52 ++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/sw-emulator/lib/cpu/src/csr_file.rs b/sw-emulator/lib/cpu/src/csr_file.rs index 858d2f95ab..bc4ee59c8f 100644 --- a/sw-emulator/lib/cpu/src/csr_file.rs +++ b/sw-emulator/lib/cpu/src/csr_file.rs @@ -73,13 +73,13 @@ impl Csr { /// PMP configuration register range start, inclusive pub const PMPCFG_START: RvAddr = 0x3A0; /// PMP configuration register range end, inclusive - pub const PMPCFG_END: RvAddr = 0x3A3; + pub const PMPCFG_END: RvAddr = 0x3AF; /// PMP address register range start, inclusive pub const PMPADDR_START: RvAddr = 0x3B0; /// PMP address register range end, inclusive - pub const PMPADDR_END: RvAddr = 0x3C0; + pub const PMPADDR_END: RvAddr = 0x3EF; /// Number of PMP address/cfg registers - pub const PMPCOUNT: usize = 16; + pub const PMPCOUNT: usize = 64; /// Create a new Configurations and Status register /// @@ -669,8 +669,9 @@ impl CsrFile { return Ok(false); } + let addr = addr as u64; let pmpaddr = self.any_read(RvPrivMode::M, Csr::PMPADDR_START + index as RvAddr)?; - let pmpaddr_shift = pmpaddr << 2; + let pmpaddr_shift = (pmpaddr as u64) << 2; let addr_top; let addr_bottom; @@ -680,7 +681,9 @@ impl CsrFile { // otherwise it's the previous one addr_top = pmpaddr_shift; addr_bottom = if index > 0 { - self.any_read(RvPrivMode::M, Csr::PMPADDR_START + (index - 1) as RvAddr)? << 2 + (self.any_read(RvPrivMode::M, Csr::PMPADDR_START + (index - 1) as RvAddr)? + as u64) + << 2 } else { 0 }; @@ -691,13 +694,12 @@ impl CsrFile { addr_bottom = pmpaddr_shift; } RvPmpAddrMode::Napot => { - // Range from 8..32 - addr_top = pmpaddr_shift + (1 << (pmpaddr.trailing_ones() + 3)); - addr_bottom = pmpaddr_shift; + let (bot, top) = decode_napot_pmpaddr(pmpaddr); + addr_bottom = bot; + addr_top = top; } _ => unreachable!(), } - Ok(addr >= addr_bottom && addr < addr_top) } @@ -748,11 +750,43 @@ impl CsrFile { } } +// Returns the base address (inclusive) and end address (exclusive) of a NAPOT PMP address +fn decode_napot_pmpaddr(addr: u32) -> (u64, u64) { + let bits = addr.trailing_ones(); + let addr = addr as u64; + let base = (addr & !((1 << bits) - 1)) << 2; + (base, base + (1 << (bits + 3))) +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn test_decode_napot_pmpaddr() { + assert_eq!((0x0000_0000, 0x0000_0008), decode_napot_pmpaddr(0x00000000)); + assert_eq!((0x0000_0000, 0x0000_0010), decode_napot_pmpaddr(0x00000001)); + assert_eq!((0x0040_0000, 0x0040_8000), decode_napot_pmpaddr(0x00100fff)); + assert_eq!((0x1000_0000, 0x2000_0000), decode_napot_pmpaddr(0x05ffffff)); + assert_eq!( + (0x0000_0000, 0x1_0000_0000), + decode_napot_pmpaddr(0x1fffffff) + ); + assert_eq!( + (0x0000_0000, 0x2_0000_0000), + decode_napot_pmpaddr(0x3fffffff) + ); + assert_eq!( + (0x0000_0000, 0x4_0000_0000), + decode_napot_pmpaddr(0x7fffffff) + ); + assert_eq!( + (0x0000_0000, 0x8_0000_0000), + decode_napot_pmpaddr(0xffffffff) + ); + } + #[test] fn test_u_mode_read_m_mode_csr() { let clock = Clock::new(); From 1186ca861270169dbbdb249845eaa1108be85190 Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Thu, 19 Dec 2024 03:25:34 +0100 Subject: [PATCH 2/3] Fix driver/dma: Operate on 64bit addresses (#1861) This fixes the driver side to operate on 64bit AXI addresses. --- drivers/src/dma.rs | 60 ++++++++++++++++------------ drivers/src/lib.rs | 4 +- drivers/test-fw/src/bin/dma_tests.rs | 17 +++++--- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/drivers/src/dma.rs b/drivers/src/dma.rs index a2b8262757..6c15e8ef28 100644 --- a/drivers/src/dma.rs +++ b/drivers/src/dma.rs @@ -22,11 +22,26 @@ use zerocopy::AsBytes; pub enum DmaReadTarget { Mbox, AhbFifo, - AxiWr(usize), + AxiWr(AxiAddr), +} + +#[derive(Debug, Clone, Copy)] +pub struct AxiAddr { + pub lo: u32, + pub hi: u32, +} + +impl From for AxiAddr { + fn from(addr: u64) -> Self { + Self { + lo: addr as u32, + hi: (addr >> 32) as u32, + } + } } pub struct DmaReadTransaction { - pub read_addr: usize, + pub read_addr: AxiAddr, pub fixed_addr: bool, pub length: u32, pub target: DmaReadTarget, @@ -35,11 +50,11 @@ pub struct DmaReadTransaction { pub enum DmaWriteOrigin { Mbox, AhbFifo, - AxiRd(usize), + AxiRd(AxiAddr), } pub struct DmaWriteTransaction { - pub write_addr: usize, + pub write_addr: AxiAddr, pub fixed_addr: bool, pub length: u32, pub origin: DmaWriteOrigin, @@ -78,16 +93,13 @@ impl Dma { fn setup_dma_read(&mut self, read_transaction: DmaReadTransaction) { let dma = self.dma.regs_mut(); - let read_addr: usize = read_transaction.read_addr; - #[cfg(target_pointer_width = "64")] - dma.src_addr_h().write(|_| (read_addr >> 32) as u32); - dma.src_addr_l().write(|_| (read_addr & 0xffff_ffff) as u32); + let read_addr = read_transaction.read_addr; + dma.src_addr_l().write(|_| read_addr.lo); + dma.src_addr_h().write(|_| read_addr.hi); if let DmaReadTarget::AxiWr(target_addr) = read_transaction.target { - #[cfg(target_pointer_width = "64")] - dma.dst_addr_h().write(|_| (target_addr >> 32) as u32); - dma.dst_addr_l() - .write(|_| (target_addr & 0xffff_ffff) as u32); + dma.dst_addr_l().write(|_| target_addr.lo); + dma.dst_addr_h().write(|_| target_addr.hi); } dma.ctrl().modify(|c| { @@ -110,16 +122,12 @@ impl Dma { let dma = self.dma.regs_mut(); let write_addr = write_transaction.write_addr; - #[cfg(target_pointer_width = "64")] - dma.dst_addr_h().write(|_| (write_addr >> 32) as u32); - dma.dst_addr_l() - .write(|_| (write_addr & 0xffff_ffff) as u32); + dma.dst_addr_l().write(|_| write_addr.lo); + dma.dst_addr_h().write(|_| write_addr.hi); if let DmaWriteOrigin::AxiRd(origin_addr) = write_transaction.origin { - #[cfg(target_pointer_width = "64")] - dma.dst_addr_h().write(|_| (origin_addr >> 32) as u32); - dma.dst_addr_l() - .write(|_| (origin_addr & 0xffff_ffff) as u32); + dma.dst_addr_l().write(|_| origin_addr.lo); + dma.dst_addr_h().write(|_| origin_addr.hi); } dma.ctrl().modify(|c| { @@ -193,18 +201,18 @@ impl Dma { let status0 = dma.status0().read(); if status0.busy() { - return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ALREADY_BUSY); + Err(CaliptraError::DRIVER_DMA_TRANSACTION_ALREADY_BUSY)?; } if status0.error() { - return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR); + Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR)?; } dma.ctrl().modify(|c| c.go(true)); while dma.status0().read().busy() { if dma.status0().read().error() { - return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR); + Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR)?; } } @@ -220,7 +228,7 @@ impl Dma { /// # Returns /// /// * `CaliptraResult` - Read value or error code - pub fn read_dword(&mut self, read_addr: usize) -> CaliptraResult { + pub fn read_dword(&mut self, read_addr: AxiAddr) -> CaliptraResult { let mut read_val: u32 = 0; self.flush(); @@ -248,7 +256,7 @@ impl Dma { /// # Returns /// /// * `CaliptraResult<()>` - Success or error code - pub fn write_dword(&mut self, write_addr: usize, write_val: u32) -> CaliptraResult<()> { + pub fn write_dword(&mut self, write_addr: AxiAddr, write_val: u32) -> CaliptraResult<()> { self.flush(); let write_transaction = DmaWriteTransaction { @@ -279,7 +287,7 @@ impl Dma { /// * `CaliptraResult<()>` - Success or error code pub fn transfer_payload_to_mbox( &mut self, - read_addr: usize, + read_addr: AxiAddr, payload_len_bytes: u32, fixed_addr: bool, block_size: u32, diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index b3c6d8b6c9..6632a03268 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -60,7 +60,9 @@ pub use bounded_address::{BoundedAddr, MemBounds, RomAddr}; pub use caliptra_error::{CaliptraError, CaliptraResult}; pub use csrng::{Csrng, HealthFailCounts as CsrngHealthFailCounts, Seed as CsrngSeed}; pub use data_vault::{ColdResetEntries, DataVault, WarmResetEntries}; -pub use dma::{Dma, DmaReadTarget, DmaReadTransaction, DmaWriteOrigin, DmaWriteTransaction}; +pub use dma::{ + AxiAddr, Dma, DmaReadTarget, DmaReadTransaction, DmaWriteOrigin, DmaWriteTransaction, +}; pub use doe::DeobfuscationEngine; pub use ecc384::{ Ecc384, Ecc384PrivKeyIn, Ecc384PrivKeyOut, Ecc384PubKey, Ecc384Result, Ecc384Scalar, diff --git a/drivers/test-fw/src/bin/dma_tests.rs b/drivers/test-fw/src/bin/dma_tests.rs index d87cc78b50..912d3e173e 100644 --- a/drivers/test-fw/src/bin/dma_tests.rs +++ b/drivers/test-fw/src/bin/dma_tests.rs @@ -15,7 +15,7 @@ Abstract: #![no_std] #![no_main] -use caliptra_drivers::{memory_layout, Dma, Mailbox}; +use caliptra_drivers::{memory_layout, AxiAddr, Dma, Mailbox}; use caliptra_registers::{axi_dma::AxiDmaReg, ecc::EccReg, mbox::MboxCsr}; use caliptra_test_harness::test_suite; use core::slice; @@ -27,7 +27,7 @@ fn test_dma_read_from_periph() { let ecc_regs = unsafe { EccReg::new() }; let ecc_name = ecc_regs.regs().name().ptr(); - let dword = dma.read_dword(ecc_name as usize).unwrap(); + let dword = dma.read_dword(AxiAddr::from(ecc_name as u64)).unwrap(); assert_eq!(dword.to_ne_bytes(), [0x70, 0x63, 0x65, 0x73]); // secp } @@ -40,8 +40,8 @@ fn test_dma_write_to_periph() { let data: u32 = 0xdead_beef; - dma.write_dword(ecc_iv as usize, data).unwrap(); - let dword = dma.read_dword(ecc_iv as usize).unwrap(); + dma.write_dword(AxiAddr::from(ecc_iv as u64), data).unwrap(); + let dword = dma.read_dword(AxiAddr::from(ecc_iv as u64)).unwrap(); assert_eq!(dword, data); } @@ -59,8 +59,13 @@ fn test_read_rri_to_mailbox() { let mut txn = mbox_driver.try_start_send_txn().unwrap(); txn.send_request(0xdead_beef, b"").unwrap(); - dma.transfer_payload_to_mbox(rri_regs, test_image.len() as u32, true, block_size) - .unwrap(); + dma.transfer_payload_to_mbox( + AxiAddr::from(rri_regs as u64), + test_image.len() as u32, + true, + block_size, + ) + .unwrap(); let mbox_fifo = unsafe { slice::from_raw_parts( From d24136fcad253573c99b9c158e0cb0cb5b05d8ad Mon Sep 17 00:00:00 2001 From: Jeff Andersen Date: Tue, 3 Dec 2024 21:34:40 -0800 Subject: [PATCH 3/3] Add documentation for the key ladder feature in ROM. --- rom/dev/README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/rom/dev/README.md b/rom/dev/README.md index c201a77218..9d5ea43bd3 100644 --- a/rom/dev/README.md +++ b/rom/dev/README.md @@ -668,6 +668,42 @@ Following is the sequence of steps that are performed to download the firmware i See Firmware [Image Validation Process](#firmware-image-validation-process). +### Derivation of the key ladder for Stable Identity + +Stable Identity calls for a secret that remains stable across firmware updates, but which can ratchet forward when major firmware vulnerabilities are fixed. Caliptra ROM implements this feature in terms of a "key ladder". + +The key ladder is initialized from the LDevID CDI during cold-boot. The key ladder length is inversely related to the firmware's SVN. Each step of the ladder is an SVN-unique key. The key for SVN X can be obtained by applying a one-way cryptographic operation to the key for SVN X+1. In this manner, firmware with a given SVN can wield keys bound to its SVN or older, but cannot wield keys bound to newer SVNs. + +To comply with FIPS, the one-way cryptographic operation used to compute keys is an SP 800-108 KDF. + +When the key ladder is initialized at cold-boot, it is bound to the lifecycle state, debug-locked state, and the firmware's "epoch" from the image header. This ensures that across lifecycle or debug state transtions, or across intentional epoch changes, the keys of the ladder will change. + +Across update-resets, ROM tracks the minimum SVN that has run since cold-boot. It ensures that the ladder's length always corresponds to that minimum SVN. The key ladder can only be shortened (and thereby give access to newer SVNs' keys) by cold-booting into firmware with a newer SVN and re-initializing the ladder. + +#### Cold-boot + +ROM initializes a key ladder for the firmware. LDevID CDI in Key Vault Slot6 is used as an HMAC Key, and the data is a fixed string. The resultant MAC is stored in Slot 2. + + KeyLadderContext = lifecycle state || debug_locked state || firmware epoch + + hmac512_kdf(KvSlot6, label: b"si_init", context: KeyLadderContext, KvSlot2) + + Loop (MAX_FIRMWARE_SVN - (current firmware SVN)) times: + + hmac512_kdf(KvSlot2, label: b"si_extend", context: None, KvSlot2) + +#### Update-reset + +During update-reset, the key ladder initialized at cold boot is lengthened if necessary, such that its length always corresponds with the minimum SVN since cold boot. + + old_min_svn = [retrieved from data vault] + new_min_svn = min(old_min_svn, new_fw_svn) + [store new_min_svn in data vault] + + Loop (`old_min_svn` - `new_min_svn`) times: + + hmac512_kdf(KvSlot2, label: b"si_extend", context: None, KvSlot2) + ### Alias FMC DICE layer & PCR extension Alias FMC Layer includes the measurement of the FMC and other security states. This layer is used to assert a composite identity which includes the security state, FMC measurement along with the previous layer identities. @@ -1067,6 +1103,8 @@ Compare the computed hash with the hash specified in the RT TOC. - Validate the toc exactly like in cold boot. - We still need to make sure that the digest of the FMC which was stored in the data vault register at cold boot still matches the FMC image section. + - Store the minimum firmware SVN that has run since cold-boot in the data vault. + - Ratchet the key ladder if necessary. - If validation fails during ROM boot, the new RT image will not be copied from the mailbox. ROM will boot the existing FMC/Runtime images. Validation errors will be reported via the CPTRA_FW_ERROR_NON_FATAL register.