trace
diff --git a/target/ast10x0/peripherals/hace/hmac.rs b/target/ast10x0/peripherals/hace/hmac.rs index 3a7f736..9cfbcf6 100644 --- a/target/ast10x0/peripherals/hace/hmac.rs +++ b/target/ast10x0/peripherals/hace/hmac.rs
@@ -14,11 +14,10 @@ //! streaming"). Each sub-hash runs through the verified public [`HaceDigest`] //! path (`HaceDevice` → `from_device` → `DigestInit`). //! -//! KNOWN ISSUE (tracked, see plans/goal.md): HMAC-SHA512 with a key longer -//! than the 128-byte block (RFC-4231 #6/#7) currently yields a wrong tag — a -//! deterministic, clean mismatch (no crash). All SHA-2 digests, HMAC-SHA256 -//! (all RFC-4231 cases), HMAC-SHA384 (all cases incl. long-key reduction), and -//! HMAC-SHA512 with keys <= block size are byte-correct against RFC-4231. +//! Key reduction always copies the raw key bytes through a `.ram_nc` staging +//! buffer before DMA, ensuring the HACE engine reads from non-cacheable SRAM +//! regardless of where the caller allocated the `HmacKey` (mirrors +//! aspeed-rust's `hash_key` strategy). //! //! Correctness authority: published RFC-4231 known-answer vectors (§2.1). @@ -41,7 +40,24 @@ /// Largest HMAC message accepted (one-shot, like the authoritative model). pub const HMAC_MSG_CAP: usize = 1024; -const SCRATCH: usize = 128 + HMAC_MSG_CAP; // ipad/opad block + message/inner + +// ----- DMA-safe key staging buffer ------------------------------------ +// +// The HACE engine performs DMA from the SG-list source addresses. When a +// key arrives from an arbitrary call site (e.g. from a `HmacKey` on the +// caller's stack, which is in cacheable SRAM), the DMA engine may observe +// stale zeros from physical RAM instead of the CPU-cached key bytes. +// Aspeed-rust fixes this in `hash_key()` by always copying the key bytes +// into `ctx.buffer` (`.ram_nc`) before hashing. We do the same here: any +// key that requires reduction (len > block_size) is first CPU-copied into +// this `.ram_nc` staging buffer, then `one_shot!` reads it from there. +struct HmacKeyNc(core::cell::UnsafeCell<[u8; HMAC_KEY_CAP]>); +// SAFETY: single-threaded HACE driver; key-reduction calls are serialised by +// the single-instance/non-reentrant `HaceDevice` contract. +unsafe impl Sync for HmacKeyNc {} + +#[unsafe(link_section = ".ram_nc")] +static HMAC_KEY_NC: HmacKeyNc = HmacKeyNc(core::cell::UnsafeCell::new([0u8; HMAC_KEY_CAP])); /// Run one full digest of `$input` via the verified public path, yielding the /// algorithm's `Digest`. Used for all three HMAC sub-hashes so HMAC rides @@ -153,7 +169,20 @@ // RFC-2104-correct threshold: reduce only when len > block_size. let mut k0 = [0u8; 128]; if k.len() > $b { - let kh = one_shot!($inner, $algo, pb, k); + // Copy the raw key into the .ram_nc staging buffer so that + // the HACE DMA engine reads from non-cacheable SRAM. + // Passing a stack pointer directly as the DMA source can + // produce wrong results when the CPU cache has not been + // written back (observed: SHA-512 key reduction reads zeros + // from physical RAM while correct bytes are cache-resident). + // This mirrors aspeed-rust's `hash_key()` which always does + // `ctx.buffer[..key_len].copy_from_slice(key_bytes)` first. + let key_nc: &[u8] = unsafe { + let buf = &mut *HMAC_KEY_NC.0.get(); + buf[..k.len()].copy_from_slice(k); + &buf[..k.len()] + }; + let kh = one_shot!($inner, $algo, pb, key_nc); let hb = kh.as_bytes(); k0[..hb.len()].copy_from_slice(hb); } else { @@ -201,22 +230,35 @@ let b = self.block; let pb = self.poll_budget; - // inner = H(K0^ipad ‖ msg) as one contiguous multi-block hash. - let mut scratch = [0u8; SCRATCH]; - scratch[..b].copy_from_slice(&self.ipad[..b]); - scratch[b..b + self.msg_len].copy_from_slice(&self.msg[..self.msg_len]); - let inner = one_shot!($inner, $algo, pb, &scratch[..b + self.msg_len]); + // inner = H(K0^ipad ‖ msg) + // + // Feed ipad and msg via two update() calls — no contiguous scratch + // buffer needed on the stack. update() copies each slice through + // ctx.buffer (.ram_nc) before DMA, so stack-resident sources + // (ipad, msg) are DMA-safe (D1/D2 guarantee). + let inner = { + // SAFETY: single-threaded HMAC controller upholds non-reentrant contract. + let dev = unsafe { HaceDevice::new_global(|_| core::hint::spin_loop()) }; + let mut dev = dev.with_timeout_polls(pb); + // SAFETY: same single-threaded exclusivity contract. + let mut dd = unsafe { HaceDigest::<$inner>::from_device(&mut dev) }; + let mut op = dd.init($algo)?; + op.update(&self.ipad[..b])?; + op.update(&self.msg[..self.msg_len])?; + op.finalize()? + }; let inner_bytes = inner.as_bytes(); - // outer = H(K0^opad ‖ inner). - scratch[..b].copy_from_slice(&self.opad[..b]); - scratch[b..b + inner_bytes.len()].copy_from_slice(inner_bytes); - Ok(one_shot!( - $inner, - $algo, - pb, - &scratch[..b + inner_bytes.len()] - )) + // outer = H(K0^opad ‖ inner) + // SAFETY: same single-threaded exclusivity contract. + let dev = unsafe { HaceDevice::new_global(|_| core::hint::spin_loop()) }; + let mut dev = dev.with_timeout_polls(pb); + // SAFETY: same contract. + let mut dd = unsafe { HaceDigest::<$inner>::from_device(&mut dev) }; + let mut op = dd.init($algo)?; + op.update(&self.opad[..b])?; + op.update(inner_bytes)?; + op.finalize() } } };
diff --git a/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs b/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs index b20e921..a044933 100644 --- a/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs +++ b/target/ast10x0/tests/peripherals/hace/hace_sha256/target.rs
@@ -351,6 +351,23 @@ HMAC_C6_512 ); + // --- Trace: replicate hmac.rs::init() reduce branch exactly for SHA-512 --- + // hmac.rs copies K6 into HMAC_KEY_NC (.ram_nc) then calls one_shot!(Sha2_512). + // Here we do the same copy via INPUT_BUF (.bss, also DMA-safe after D1 fix) + // and hash it. If this gives K6_512, the reduce one_shot! is correct and the + // bug is in finalize(). If it gives something else, the reduce path is broken. + { + pw_log::info!("case: {}", "dbg sha512 reduce-k6-via-bss" as &str); + let buf = unsafe { &mut *core::ptr::addr_of_mut!(INPUT_BUF) }; + buf[..HMAC_K6.len()].copy_from_slice(&HMAC_K6); + let mut device = unsafe { HaceDevice::new_global(|_| core::hint::spin_loop()) }; + let mut dd = unsafe { HaceDigest::<Sha2_512>::from_device(&mut device) }; + let mut op = dd.init(Sha2_512).map_err(|_| ERR_HASH_FAILED)?; + op.update(&buf[..HMAC_K6.len()]).map_err(|_| ERR_HASH_FAILED)?; + let kh = op.finalize().map_err(|_| ERR_HASH_FAILED)?; + check("dbg sha512 reduce-k6-via-bss", kh.as_bytes(), &K6_512)?; + } + hmac_case!( "hmac-sha512 rfc4231-1", HmacSha2_512,