diff --git a/.cargo/config.toml b/.cargo/config.toml index 0e8ce01fb..0e54f6e57 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,3 +8,6 @@ xtask = "run --package xtask --" # overridden within xtask for Hubris programs, so this only affects host tools like # xtask. rustflags = ["-Zallow-features=proc_macro_diagnostic,asm_const,naked_functions,used_with_arg"] + +[unstable] +bindeps = true diff --git a/Cargo.lock b/Cargo.lock index 2d9e1ad66..e7dbf22ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1315,6 +1315,7 @@ dependencies = [ name = "drv-ice40-spi-program" version = "0.1.0" dependencies = [ + "counters", "drv-spi-api", "drv-stm32xx-sys-api", "userlib", @@ -1520,21 +1521,29 @@ name = "drv-lpc55-swd" version = "0.1.0" dependencies = [ "anyhow", + "attest-api", + "bitflags 2.6.0", "build-lpc55pins", "build-util", + "call_rustfmt", "cfg-if", "cortex-m", "drv-lpc55-gpio-api", "drv-lpc55-spi", "drv-lpc55-syscon-api", "drv-sp-ctrl-api", + "endoscope", + "endoscope-abi", + "goblin", "idol", "idol-runtime", "lpc55-pac", "num-traits", "quote", "ringbuf", + "rustc-demangle", "serde", + "static_assertions", "userlib", "zerocopy 0.6.6", ] @@ -2616,6 +2625,27 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "endoscope" +version = "0.1.0" +dependencies = [ + "anyhow", + "cortex-m", + "cortex-m-rt", + "drv-stm32h7-startup", + "endoscope-abi", + "panic-halt", + "sha3", + "stm32h7", +] + +[[package]] +name = "endoscope-abi" +version = "0.1.0" +dependencies = [ + "zerocopy 0.6.6", +] + [[package]] name = "enum-kinds" version = "0.5.1" @@ -3892,6 +3922,12 @@ dependencies = [ "syn 1.0.94", ] +[[package]] +name = "panic-halt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" + [[package]] name = "paste" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index 181ed14df..6fdd71c88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,21 @@ opt-level = "z" # smaller optimizations [profile.dev] opt-level = 1 # no optimizations was just too painful in terms of flash size +[profile.release.package.endoscope] +codegen-units = 1 +debug = 2 +opt-level = "z" + +[profile.dev.package.endoscope] +codegen-units = 1 +debug = 2 +opt-level = "z" + +[profile.bench.package.endoscope] +codegen-units = 1 +debug = 2 +opt-level = "z" + [patch."https://github.com/oxidecomputer/hubris".userlib] path = "sys/userlib" diff --git a/app/oxide-rot-1/app-dev.toml b/app/oxide-rot-1/app-dev.toml index c5b33c1b7..bc1598a0e 100644 --- a/app/oxide-rot-1/app-dev.toml +++ b/app/oxide-rot-1/app-dev.toml @@ -103,20 +103,18 @@ pins = [ { name = "CHIP_SELECT", pin = { port = 1, pin = 1}, alt = 5}, # ROT_IRQ = P0_18 = FUN0 { name = "ROT_IRQ", pin = { port = 0, pin = 18}, alt = 0, direction = "output"}, - # SP_RESET = P0_9 = FUN0 - { name = "SP_RESET", pin = { port = 0, pin = 9}, alt = 0, direction = "input"}, ] [tasks.swd] name = "drv-lpc55-swd" priority = 4 -max-sizes = {flash = 16384, ram = 4096} uses = ["flexcomm5", "iocon"] start = true -stacksize = 1000 -task-slots = ["gpio_driver", "syscon_driver"] -notifications = ["spi-irq", "timer"] -interrupts = {"flexcomm5.irq" = "spi-irq"} +stacksize = 1280 +task-slots = ["gpio_driver", "syscon_driver", "attest"] +notifications = ["spi-irq", "timer", "sp_reset-irq", "jtag_detect-irq"] +interrupts = {"flexcomm5.irq" = "spi-irq", "pint.irq0" = "sp_reset-irq", "pint.irq1" = "jtag_detect-irq" } +features = ["enable_ext_sp_reset"] [tasks.swd.config] # MOSI = PIO0_8 @@ -136,8 +134,10 @@ in_cfg = [ pins = [ # SCK { pin = { port = 0, pin = 7 }, alt = 3 }, - { name = "SP_TO_ROT_JTAG_DETECT_L", pin = { port = 0, pin = 20 }, alt = 0, direction = "input" }, - { name = "ROT_TO_SP_RESET_L", pin = { port = 0, pin = 13 }, alt = 0, value = true, direction = "output", opendrain = "opendrain" }, + { name = "SP_TO_ROT_JTAG_DETECT_L", pin = { port = 0, pin = 20 }, alt = 0, direction = "input", mode = "nopull", pint = 1 }, + # SP NRST has an internal 30-50k pull-up in SP + { name = "ROT_TO_SP_RESET_L_IN", pin = { port = 0, pin = 13 }, alt = 0, direction = "input", mode = "nopull", pint = 0 }, + { name = "ROT_TO_SP_RESET_L_OUT", pin = { port = 0, pin = 13 }, alt = 0, direction = "output", mode = "nopull", opendrain = "opendrain", setup = false }, ] spi_num = 5 @@ -149,26 +149,17 @@ start = true stacksize = 2600 task-slots = ["swd"] -# We intentionally do not start this task to avoid conflicts with the SP -# debug connection. -[tasks.sp_measure] -name = "task-sp-measure" -priority = 6 -max-sizes = {flash = 131072, ram = 8192} -task-slots = ["swd"] -stacksize = 2048 - -[tasks.sp_measure.config] -binary_path = "../../target/gimlet-c/dist/default/final.bin" - [tasks.attest] name = "task-attest" -priority = 5 -max-sizes = {flash = 35072, ram = 16384} +priority = 3 +max-sizes = {flash = 35400, ram = 16384} stacksize = 12304 start = true extern-regions = ["dice_alias", "dice_certs"] +[tasks.attest.config] +permit_log_reset = ["swd", "hiffy"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/app/oxide-rot-1/app.toml b/app/oxide-rot-1/app.toml index c100267a2..795fc6d21 100644 --- a/app/oxide-rot-1/app.toml +++ b/app/oxide-rot-1/app.toml @@ -88,20 +88,17 @@ pins = [ { name = "CHIP_SELECT", pin = { port = 1, pin = 1}, alt = 5}, # ROT_IRQ = P0_18 = FUN0 { name = "ROT_IRQ", pin = { port = 0, pin = 18}, alt = 0, direction = "output"}, - # SP_RESET = P0_9 = FUN0 - { name = "SP_RESET", pin = { port = 0, pin = 9}, alt = 0, direction = "input"}, ] [tasks.swd] name = "drv-lpc55-swd" priority = 4 -max-sizes = {flash = 16384, ram = 4096} uses = ["flexcomm5", "iocon"] start = true -stacksize = 1000 -task-slots = ["gpio_driver", "syscon_driver"] -notifications = ["spi-irq", "timer"] -interrupts = {"flexcomm5.irq" = "spi-irq"} +stacksize = 1536 +task-slots = ["gpio_driver", "syscon_driver", "attest"] +notifications = ["spi-irq", "timer", "sp_reset-irq", "jtag_detect-irq"] +interrupts = {"flexcomm5.irq" = "spi-irq", "pint.irq0" = "sp_reset-irq", "pint.irq1" = "jtag_detect-irq" } [tasks.swd.config] # MOSI = PIO0_8 @@ -121,8 +118,10 @@ in_cfg = [ pins = [ # SCK { pin = { port = 0, pin = 7 }, alt = 3 }, - { name = "SP_TO_ROT_JTAG_DETECT_L", pin = { port = 0, pin = 20 }, alt = 0, direction = "input" }, - { name = "ROT_TO_SP_RESET_L", pin = { port = 0, pin = 13 }, alt = 0, value = true, direction = "output", opendrain = "opendrain" }, + { name = "SP_TO_ROT_JTAG_DETECT_L", pin = { port = 0, pin = 20 }, alt = 0, direction = "input", mode = "nopull", pint = 1 }, + # SP NRST has an internal 30-50k pull-up in SP + { name = "ROT_TO_SP_RESET_L_IN", pin = { port = 0, pin = 13 }, alt = 0, direction = "input", mode = "nopull", pint = 0 }, + { name = "ROT_TO_SP_RESET_L_OUT", pin = { port = 0, pin = 13 }, alt = 0, direction = "output", mode = "nopull", opendrain = "opendrain", setup = false }, ] spi_num = 5 @@ -136,12 +135,15 @@ task-slots = ["swd"] [tasks.attest] name = "task-attest" -priority = 5 +priority = 3 max-sizes = {flash = 35400, ram = 16384} stacksize = 12304 start = true extern-regions = ["dice_alias", "dice_certs"] +[tasks.attest.config] +permit_log_reset = ["swd"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/drv/lpc55-swd/Cargo.toml b/drv/lpc55-swd/Cargo.toml index cc04b506b..375a1f0cb 100644 --- a/drv/lpc55-swd/Cargo.toml +++ b/drv/lpc55-swd/Cargo.toml @@ -10,24 +10,34 @@ idol-runtime = { workspace = true } lpc55-pac = { workspace = true } num-traits = { workspace = true } zerocopy = { workspace = true } +bitflags = { workspace = true } +static_assertions = { workspace = true } +attest-api = { path = "../../task/attest-api" } drv-lpc55-gpio-api = { path = "../lpc55-gpio-api" } drv-lpc55-spi = { path = "../lpc55-spi" } drv-lpc55-syscon-api = { path = "../lpc55-syscon-api" } drv-sp-ctrl-api = { path = "../sp-ctrl-api" } ringbuf = { path = "../../lib/ringbuf" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +endoscope-abi = { path = "../../lib/endoscope-abi" } [build-dependencies] +anyhow = { workspace = true } build-lpc55pins = { path = "../../build/lpc55pins" } build-util = { path = "../../build/util" } -anyhow = { workspace = true } +call_rustfmt = { path = "../../build/call_rustfmt" } +endoscope-abi = { path = "../../lib/endoscope-abi" } +endoscope = { path = "../../lib/endoscope", artifact="bin:endoscope", target = "thumbv7em-none-eabihf", features = ["soc_stm32h753"]} +goblin = { workspace = true } idol = { workspace = true } quote = { workspace = true } +rustc-demangle = { workspace = true } serde = { workspace = true } [features] no-ipc-counters = ["idol/no-counters"] +enable_ext_sp_reset = [] # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. diff --git a/drv/lpc55-swd/build.rs b/drv/lpc55-swd/build.rs index 1b0339115..01b17a813 100644 --- a/drv/lpc55-swd/build.rs +++ b/drv/lpc55-swd/build.rs @@ -2,10 +2,25 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::Result; +use anyhow::{bail, Context, Result}; use build_lpc55pins::PinConfig; +use call_rustfmt::rustfmt; +use goblin::container::Container; +use goblin::elf::section_header::{SectionHeader, SHF_ALLOC, SHT_PROGBITS}; +use goblin::elf::Elf; use serde::Deserialize; +use std::fs::OpenOptions; use std::io::Write; +use std::io::{Read, Seek, SeekFrom}; +use std::path::PathBuf; + +// Symbols relied on in the endoscope.elf file. +// The image load address. +pub const LOAD_SYMBOL: &str = "__vector_table"; +// An instance of struct Shared is expected at this address +pub const SHARED_STRUCT_SYMBOL: &str = "SHARED"; +// The reset vector found in the image should match this symbol value. +pub const RESET_VECTOR_SYMBOL: &str = "Reset"; #[derive(Deserialize)] #[serde(deny_unknown_fields)] @@ -88,6 +103,161 @@ fn generate_swd_functions(config: &TaskConfig) -> Result<()> { Ok(()) } +fn prepare_endoscope() -> Result<(), anyhow::Error> { + let key = "CARGO_BIN_FILE_ENDOSCOPE"; + let elf_path = PathBuf::from( + std::env::var(key) + .with_context(|| format!("Cannot read env var '{}'", key))?, + ); + let data = std::fs::read(&elf_path).context("could not open ELF file")?; + let elf = Elf::parse(&data).context("cannot parse ELF file")?; + + let out_dir = build_util::out_dir(); + let dest_path = out_dir.join("endoscope.rs"); + let bin_path = out_dir.join("endoscope.bin"); + let mut file = std::fs::File::create(&dest_path) + .context("Cannot create output file")?; + + writeln!(&mut file, "mod endoscope {{")?; + + // Get entry point + if elf.header.container()? != Container::Little + || elf.header.e_machine != goblin::elf::header::EM_ARM + { + bail!("Not a little endian ARM ELF file"); + } + + let interesting = std::collections::BTreeMap::from([ + // Load address of image + (LOAD_SYMBOL, "LOAD"), + // Address of endoscope output struct + (SHARED_STRUCT_SYMBOL, "SHARED"), + ]); + + for sym in elf.syms.iter() { + if let Some(name) = elf.strtab.get_at(sym.st_name) { + // Note: If we were using Rust instead of linker symbols + // we would want to use rustc_demangle::demangle(str) + if let Some(myname) = interesting.get(name) { + writeln!( + &mut file, + "pub const {}: u32 = {:#x};", + myname, sym.st_value + )?; + } + } + } + writeln!(&mut file, "}}")?; + + // Extract image bits from the ELF file. + let elf_reader = OpenOptions::new().read(true).open(&elf_path).unwrap(); + let bin = get_elf(elf_reader).with_context(|| { + format!("cannot extract bin from elf {}", elf_path.display()) + })?; + std::fs::write(&bin_path, bin).context("cannot write to {&bin_path}")?; + + writeln!( + &mut file, + "// Bytes extracted from target/{}", + elf_path.to_str().unwrap().split("target/").last().unwrap() + )?; + writeln!( + &mut file, + "const ENDOSCOPE_BYTES: &[u8] = include_bytes!(r#\"{}\"#);", + bin_path.to_str().unwrap() + )?; + drop(file); + rustfmt(&dest_path).context("cannot call_rustfmt")?; + + Ok(()) +} + +/// Given a reader on an ELF file return the executable image. +pub fn get_elf(mut reader: R) -> Result, anyhow::Error> +where + R: Read + Seek, +{ + let mut data = vec![]; + let _len = reader + .read_to_end(&mut data) + .context("cannot read to end")?; + let elf = Elf::parse(&data).context("cannot parse data as ELF")?; + + if elf.header.container()? != Container::Little + || elf.header.e_machine != goblin::elf::header::EM_ARM + { + bail!("not a little-endian ARM ELF file"); + } + + // Extract the bytes to load + + // Find all of the sections that compose our '.bin' file. + // These criteria were inferred from the produced artifact + // and the result of `arm-none-eabi-objcopy -O binary $INPUT_ELF $OUTPUT_BIN` + // + // See also: + // https://refspecs.linuxfoundation.org/LSB_2.1.0/LSB-Embedded/LSB-Embedded/elftypes.html + let sections: Vec<&SectionHeader> = elf + .section_headers + .iter() + .filter(|sh| (sh.sh_type & SHT_PROGBITS == SHT_PROGBITS)) + .filter(|sh| (sh.sh_flags as u32 & SHF_ALLOC == SHF_ALLOC)) + .filter(|sh| !sh.vm_range().is_empty()) + .collect(); + let bin_size: usize = sections.iter().map(|sh| sh.vm_range().len()).sum(); + + // Do a sanity check on the size of the blob based on what we've seen. + // + // Notes: + // + // Clippy ignores the profile (or equivalent) when building the + // endoscope blob which results in an enormous binary. Since we're not + // going to actually build the swd task when running clippy, the large + // size can be ignored. + // + // The artifact-specific profiles do not allow `lto` to be + // specified. That would save at least 500 bytes of executable. + // In Cargo.toml, see [profile.*.package.endoscope] + // + // TODO: Add to or create relevant cargo bug(s): + // e.g. https://github.com/rust-lang/cargo/issues/11680 + // + #[cfg(not(clippy))] + if bin_size > 6 * 1024 { + bail!("bin_size of {bin_size} is over 6KiB. Was it built with the wrong profile?"); + } + + // Test our assumptions that the sections are in order and contiguous. + if sections.len() > 1 { + for index in 0..(sections.len() - 1) { + if sections[index].vm_range().end + != sections[index + 1].vm_range().start + { + bail!("discontiguous sections {} and {}", index, index + 1); + } + } + } + + // We're also assuming that `vm_range`s go from 0 to bin_size. + // So, expect a panic if we now try to read outside those limits. + let mut bin = vec![0u8; bin_size]; + + for section in sections { + let file_range = section.file_range().unwrap(); + let vm_range = section.vm_range(); + reader.seek(SeekFrom::Start(file_range.start as u64))?; + reader + .read_exact(&mut bin[vm_range.start..vm_range.end]) + .with_context(|| { + format!( + "cannot read elf[{}..] into bin[{}..{}]", + file_range.start, vm_range.start, vm_range.end + ) + })?; + } + Ok(bin) +} + fn main() -> Result<(), Box> { idol::Generator::new() .with_counters( @@ -107,5 +277,7 @@ fn main() -> Result<(), Box> { generate_swd_functions(&task_config)?; build_lpc55pins::codegen(task_config.pins)?; + prepare_endoscope()?; + Ok(()) } diff --git a/drv/lpc55-swd/src/armv7debug.rs b/drv/lpc55-swd/src/armv7debug.rs new file mode 100644 index 000000000..c03551582 --- /dev/null +++ b/drv/lpc55-swd/src/armv7debug.rs @@ -0,0 +1,179 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// +// For the STM32H7xx, see ARMv7-M Architecture Reference Manual +// Part 3 Debug Arch. +// https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture?lang=en + +use bitflags::bitflags; + +pub trait DpAddressable { + /// Address for accessing a DP Register + const ADDRESS: u32; +} + +// For keeping track of unwinding debug actions +bitflags! { + #[derive(PartialEq, Eq, Copy, Clone)] + pub struct Undo: u8 { + // Need self.swd_finish() + const SWD = 1 << 0; + // Need self.sp_reset_leave(true) + const RESET = 1 << 1; + // Need DEMCR = 0 + const VC_CORERESET = 1 << 2; + // Need to clear debug enable. + const DEBUGEN = 1 << 3; + } +} + +// RW 0x00000000 Debug Halting Control and Status Register +// Some DHCSR bits have different read vs. write meanings +// Specifically, the MAGIC value enables writing other control bits +// but some of those bits are status bits when read. +bitflags! { + #[derive(PartialEq, Eq, Copy, Clone)] + pub struct Dhcsr: u32 { + // At least one reset since last DHCSR read. clear on read. + const S_RESET_ST = 1 << 25; + const S_RETIRE_ST = 1 << 24; + const S_LOCKUP = 1 << 19; + const S_SLEEP = 1 << 18; + const S_HALT = 1 << 17; + const S_REGRDY = 1 << 16; + + // Magic number allows setting C_* bits. + const DBGKEY = 0xA05F << 16; + + const C_SNAPSTALL = 1 << 5; + const C_MASKINTS = 1 << 3; + const C_STEP = 1 << 2; + const C_HALT = 1 << 1; + const C_DEBUGEN = 1 << 0; + const _ = !0; + } +} + +impl From for Dhcsr { + fn from(v: u32) -> Self { + Self::from_bits_retain(v) + } +} + +impl DpAddressable for Dhcsr { + const ADDRESS: u32 = 0xE000EDF0; +} + +impl Dhcsr { + pub fn halt() -> Self { + Self::DBGKEY | Self::C_HALT | Self::C_DEBUGEN + } + pub fn resume() -> Self { + Self::DBGKEY | Self::C_DEBUGEN + } + pub fn end_debug() -> Self { + Self::DBGKEY + } + pub fn is_halted(self) -> bool { + self & Self::S_HALT == Self::S_HALT + } + pub fn is_regrdy(self) -> bool { + self & Self::S_REGRDY == Self::S_REGRDY + } + // Just to document the remaining bit: + // pub fn is_lockup(self) -> bool { + // self & Self::S_LOCKUP == Self::S_LOCKUP + // } +} + +// Debug Core Register Selector Register +pub const DCRSR: u32 = 0xE000EDF4; +// Debug Core Register Data Register +pub const DCRDR: u32 = 0xE000EDF8; + +// DEMCR RW 0x00000000 Debug Exception and Monitor Control Register +bitflags! { + #[derive(PartialEq, Eq, Copy, Clone)] + pub struct Demcr: u32 { + const MON_EN = 1 << 16; + const VC_HARDERR = 1 << 10; + const VC_INTERR = 1 << 9; + const VC_BUSERR = 1 << 8; + const VC_STATERR = 1 << 7; + const VC_CHKERR = 1 << 6; + const VC_NOCPERR = 1 << 5; + const VC_MMERR = 1 << 4; + const VC_CORERESET = 1 << 0; + } +} + +impl DpAddressable for Demcr { + const ADDRESS: u32 = 0xE000EDFC; +} + +// Armv7-M Arch. Ref. Manual - C1.6.1 Debug Fault Status Register +// RW Init: 0x00000000[on power-on reset only] +bitflags! { + #[derive(PartialEq, Eq, Copy, Clone)] + pub struct Dfsr: u32 { + // Assertion of an external debug request + const EXTERNAL = 1 << 4; + // Vector catch triggered + const VCATCH = 1 << 3; + // At least one DWT event + const DWTTRAP = 1 << 2; + // Breakpoint + const BKPT = 1 << 1; + // Halt request debug event. + // • A C_HALT or C_STEP request, triggered by a write to the DHCSR, + // see Debug Halting Control and Status Register, DHCSR. + // • A step request triggered by setting DEMCR.MON_STEP to 1, + // see Debug monitor stepping on page C1-696. + const HALTED = 1 << 0; + const _ = !0; + } +} + +impl DpAddressable for Dfsr { + const ADDRESS: u32 = 0xE000ED30; +} + +impl Dfsr { + pub fn _is_faulted(self) -> bool { + (self + & (Self::EXTERNAL + | Self::VCATCH + | Self::DWTTRAP + | Self::BKPT + | Self::HALTED)) + .bits() + != 0 + } + pub fn is_vcatch(self) -> bool { + self & Self::VCATCH == Self::VCATCH + } +} + +#[derive(PartialEq, Copy, Clone)] +#[repr(u16)] +pub enum Reg { + Sp = 0b0001101, + Lr = 0b0001110, + Dr = 0b0001111, // DebugReturnAddress, see C1-704 + Xpsr = 0b0010000, + Msp = 0b0010001, + Psp = 0b0010010, + Cfbp = 0b0010100, // [31:24] CONTROL, [23:15] FAULTMASK, [15:8] BASEPRI, [7:0] PRIMASK + Fpscr = 0b0100001, +} + +impl From for u16 { + fn from(r: Reg) -> u16 { + r as u16 + } +} + +pub const _ICSR: u32 = 0xE000ED04; +pub const VTOR: u32 = 0xE000ED08; diff --git a/drv/lpc55-swd/src/main.rs b/drv/lpc55-swd/src/main.rs index 13e912f0e..1e12415b3 100644 --- a/drv/lpc55-swd/src/main.rs +++ b/drv/lpc55-swd/src/main.rs @@ -61,44 +61,127 @@ #![no_std] #![no_main] +use attest_api::{Attest, AttestError, HashAlgorithm}; +use drv_lpc55_gpio_api::{Direction, Pins, Value}; use drv_lpc55_spi as spi_core; use drv_lpc55_syscon_api::{Peripheral, Syscon}; use drv_sp_ctrl_api::SpCtrlError; +use endoscope_abi::{Shared, State}; +#[cfg(not(feature = "enable_ext_sp_reset"))] +use idol_runtime::ClientError; use idol_runtime::{ LeaseBufReader, LeaseBufWriter, Leased, LenLimit, NotificationHandler, RequestError, R, W, }; use lpc55_pac as device; use ringbuf::*; +use static_assertions::const_assert; use userlib::{ - hl, set_timer_relative, sys_set_timer, task_slot, RecvMessage, TaskId, - UnwrapLite, + hl, set_timer_relative, sys_get_timer, sys_irq_control, + sys_irq_control_clear_pending, sys_set_timer, task_slot, RecvMessage, + TaskId, }; +use zerocopy::AsBytes; #[derive(Copy, Clone, PartialEq)] enum Trace { Idcode(u32), Idr(u32), MemVal(u32), - SwdRead(Port, RawSwdReg), - SwdWrite(Port, RawSwdReg, u32), ReadCmd, WriteCmd, None, AckErr(Ack), DongleDetected, - Dhcsr(u32), - ParityFail { data: u32, received_parity: u16 }, + Dhcsr(Dhcsr), + ParityFail { + data: u32, + received_parity: u16, + }, EnabledWatchdog, DisabledWatchdog, WatchdogFired, WatchdogSwap(Result<(), Ack>), + AttestError(AttestError), + BadDemcr(Demcr), + BadLen, + CannotWriteVtor, + Data { + addr: u32, + data: u32, + src: u32, + }, + DemcrReadError, + // Demcr(Demcr), + DemcrWriteError, + DfsrReadError, + Dfsr(Dfsr), + DhcsrWriteError, + DidNotHalt, + DoHalt, + DoSetup, + EndOfNotificationHandler, + Halted { + delta_t: u32, + }, + HaltFail(u32), + HaltWait { + timeout: u32, + }, + IncompleteUndo(Undo), + Injected { + start: u32, + length: usize, + delta_t: u32, + }, + InjectionFailed, + InvalidatedSpMeasurement, + InvalidateSpMeasurement, + InvalidRegisterWrite(u16, u32), + LimitRemaining(u32), + // Lockup(Dhcsr), + MeasuredSp { + success: bool, + delta_t: u32, + }, + MeasureFailed, + Never, + SharedState(u32), + Digest0([u8; 8]), + Digest1([u8; 8]), + Digest2([u8; 8]), + Digest3([u8; 8]), + PinSetupDefaults, + ReadbackFailure, + ReadBufFail, + ReadX { + start: u32, + len: u32, + }, + RecordedMeasurement, + RecordMeasurementFailed, + Resumed, + ResumeFail, + SetupSwdOk, + SpJtagDetectFired, + SpResetAsserted, + SpResetFired, + SpResetNotAsserted, + SwdSetupFail, + SwdSetupOk, + Timeout, + TimerHandlerError(SpCtrlError), + VcCoreReset(bool), + VcCoreResetNotCaught, + WrotePcRegisterFail, + WroteSpRegisterFail, } ringbuf!(Trace, 128, Trace::None); task_slot!(SYSCON, syscon_driver); task_slot!(GPIO, gpio_driver); +task_slot!(ATTEST, attest); #[derive(Copy, Clone, PartialEq)] enum Ack { @@ -121,7 +204,18 @@ const CSW_SADDRINC: u32 = 0x00000010; // AP access enabled const CSW_DBGSTAT: u32 = 0x00000040; // Cacheable + privileged + data access -const CSW_HPROT: u32 = 0x0b000000; +// const CSW_HPROT: u32 = 0x0b << 24; +const CSW_HPROT: u32 = CSW_HPROT_0INSTRUCTION_FETCH_1DATA_ACCESS + | CSW_HPROT_0MODE_USER_1PRIVILEGED + | CSW_HPROT_0NONCACHE_1CACHEABLE; + +const _CSW_SPROT: u32 = 1 << 30; + +const CSW_HPROT_0INSTRUCTION_FETCH_1DATA_ACCESS: u32 = 0b00001 << 24; +const CSW_HPROT_0MODE_USER_1PRIVILEGED: u32 = 0b00010 << 24; +const _CSW_HPROT_0NONBUF_1BUFFERABLE: u32 = 0b00100 << 24; +const CSW_HPROT_0NONCACHE_1CACHEABLE: u32 = 0b01000 << 24; +const _CSW_HPROT_0NONEXCL_1EXCLUSIVE: u32 = 0b10000 << 24; const DP_CTRL_CDBGPWRUPREQ: u32 = 1 << 28; const DP_CTRL_CDBGPWRUPACK: u32 = 1 << 29; @@ -139,14 +233,12 @@ const PARK_BIT: u8 = 0; const START_VAL: u8 = 1 << START_BIT; const PARK_VAL: u8 = 1 << PARK_BIT; -const DHCSR: u32 = 0xE000EDF0; -const DHCSR_HALT_MAGIC: u32 = 0xa05f_0003; -const DHCSR_RESUME_MAGIC: u32 = 0xa05f_0000; -const DHCSR_S_HALT: u32 = 1 << 17; -const DHCSR_S_REGRDY: u32 = 1 << 16; +// Debug Interface from Armv7 Architecture Manual chapter C-1 +mod armv7debug; -const DCRSR: u32 = 0xE000EDF4; -const DCRDR: u32 = 0xE000EDF8; +use armv7debug::{ + Demcr, Dfsr, Dhcsr, DpAddressable, Reg, Undo, DCRDR, DCRSR, VTOR, +}; #[derive(Copy, Clone, PartialEq)] enum Port { @@ -265,8 +357,10 @@ struct MemTransaction { struct ServerImpl { spi: spi_core::Spi, gpio: TaskId, + attest: Attest, init: bool, transaction: Option, + watchdog_ms: Option, } impl idl::InOrderSpCtrlImpl for ServerImpl { @@ -279,6 +373,10 @@ impl idl::InOrderSpCtrlImpl for ServerImpl { if !self.init { return Err(SpCtrlError::NeedInit.into()); } + ringbuf_entry!(Trace::ReadX { + start, + len: end - start + }); self.start_read_transaction(start, ((end - start) as usize) / 4) .map_err(|_| SpCtrlError::Fault.into()) } @@ -392,55 +490,24 @@ impl idl::InOrderSpCtrlImpl for ServerImpl { &mut self, _: &RecvMessage, ) -> Result<(), RequestError> { - if !self.init { - self.pin_setup(); - } - - if self.swd_dongle_detected() { - ringbuf_entry!(Trace::DongleDetected); - return Err(SpCtrlError::DongleDetected.into()); - } - - match self.swd_setup() { - Ok(_) => { - self.init = true; - Ok(()) - } - Err(_) => Err(SpCtrlError::Fault.into()), - } + self.do_setup_swd()?; + Ok(()) } fn halt( &mut self, _: &RecvMessage, ) -> Result<(), RequestError> { - match self.write_single_target_addr(DHCSR, DHCSR_HALT_MAGIC) { - Ok(_) => loop { - match self.read_single_target_addr(DHCSR) { - Ok(dhcsr) => { - ringbuf_entry!(Trace::Dhcsr(dhcsr)); - - if dhcsr & DHCSR_S_HALT != 0 { - return Ok(()); - } - } - Err(_) => { - return Err(SpCtrlError::Fault.into()); - } - } - }, - Err(_) => Err(SpCtrlError::Fault.into()), - } + self.do_halt()?; + Ok(()) } fn resume( &mut self, _: &RecvMessage, ) -> Result<(), RequestError> { - match self.write_single_target_addr(DHCSR, DHCSR_RESUME_MAGIC) { - Ok(_) => Ok(()), - Err(_) => Err(SpCtrlError::Fault.into()), - } + self.do_resume()?; + Ok(()) } fn read_core_register( @@ -472,11 +539,11 @@ impl idl::InOrderSpCtrlImpl for ServerImpl { } loop { - match self.read_single_target_addr(DHCSR) { + match self.dp_read_bitflags::() { Ok(dhcsr) => { ringbuf_entry!(Trace::Dhcsr(dhcsr)); - if dhcsr & DHCSR_S_REGRDY != 0 { + if dhcsr.is_regrdy() { break; } } @@ -486,7 +553,7 @@ impl idl::InOrderSpCtrlImpl for ServerImpl { } } - match self.read_single_target_addr(DCRDR) { + match self.read_dcrdr() { Ok(val) => Ok(val), Err(_) => Err(SpCtrlError::Fault.into()), } @@ -499,11 +566,27 @@ impl idl::InOrderSpCtrlImpl for ServerImpl { ) -> Result<(), RequestError> { ringbuf_entry!(Trace::EnabledWatchdog); if !self.init { + // The init will fail if there is an active debug dongle on the SP + // SWD interface. + // If there was an active dongle, then the SWD interface would not + // be usable to the RoT when when needed. + // This is a clue to the SP's update_server client that they should + // not proceed. return Err(SpCtrlError::NeedInit.into()); } // This function is idempotent(ish), so we don't care if the timer was // already running; set the new deadline based on current time. set_timer_relative(time_ms, notifications::TIMER_MASK); + + // The common case is that there will be a RESET before the watchdog + // can fire. + // That will kick off an SP image measurement which takes under a second. + // + // At the time of writing this comment, the watchdog timer value used + // in omicron is 2000ms. With different startup times for the various + // SP Hubris applications, it is important to test for watchdog failuew + // cases. + self.watchdog_ms = Some(time_ms); Ok(()) } @@ -512,28 +595,135 @@ impl idl::InOrderSpCtrlImpl for ServerImpl { _msg: &userlib::RecvMessage, ) -> Result<(), RequestError> { ringbuf_entry!(Trace::DisabledWatchdog); + self.watchdog_ms = None; sys_set_timer(None, notifications::TIMER_MASK); Ok(()) } + + /// Remote debugging support. + /// Yet another way to reset the SP. This one is known to finish before + /// any other code in this task runs. + #[cfg(feature = "enable_ext_sp_reset")] + fn db_reset_sp( + &mut self, + _msg: &userlib::RecvMessage, + delay: u32, + ) -> Result<(), RequestError> { + self.sp_reset_enter(); // XXX may need to clear self.transaction if dumper is active. + hl::sleep_for(delay.into()); + // Don't clean up interrupt state, we want to trigger a measurement. + let _ = self.swd_setup(); + let _ = self.dp_write_bitflags::(Demcr::from_bits_retain(0)); + let _ = self.dp_write_bitflags::(Dhcsr::end_debug()); + self.swd_finish(); + self.sp_reset_leave(false); + self.init = false; + Ok(()) + } + + #[cfg(not(feature = "enable_ext_sp_reset"))] + fn db_reset_sp( + &mut self, + _msg: &userlib::RecvMessage, + _delay: u32, + ) -> Result<(), RequestError> { + Err(ClientError::AccessViolation.fail()) + } } impl NotificationHandler for ServerImpl { fn current_notification_mask(&self) -> u32 { notifications::TIMER_MASK + + notifications::SP_RESET_IRQ_MASK + + notifications::JTAG_DETECT_IRQ_MASK } - fn handle_notification(&mut self, _bits: u32) { - ringbuf_entry!(Trace::WatchdogFired); + fn handle_notification(&mut self, bits: u32) { + // If JTAG_DETECT fires: + // - invalidate any SP measurement + // - if still asserted, then the other handlers will fail on + // their calls to do_setup_swd(); + // - We could try extending the Watchdog timer if it is active in hopes + // that the SP dongle is removed or its power is removed, but if + // there is a dongle attached to the SP, then let the humans figure it out + // and don't complicate the behavior here. + // - // Disable the watchdog timer - sys_set_timer(None, notifications::TIMER_MASK); + let mut invalidate = false; + let gpio = Pins::from(self.gpio); - // Attempt to do the swap - let r = self.swap_sp_slot(); - ringbuf_entry!(Trace::WatchdogSwap(r)); + if (bits & notifications::JTAG_DETECT_IRQ_MASK) != 0 { + ringbuf_entry!(Trace::SpJtagDetectFired); + const SLOT: PintSlot = SP_TO_ROT_JTAG_DETECT_L_PINT_SLOT; + if let Ok(Some(detected)) = + gpio.pint_op(SLOT, PintOp::Detected, PintCondition::Falling) + { + if detected { + ringbuf_entry!(Trace::InvalidateSpMeasurement); + invalidate = true; + self.init = false; + } + } else { + // The pint_op parameters are for a configured PINT slot for one of + // our configured GPIO pins. We're testing a status bit in a register. + unreachable!(); + } + let _ = gpio.pint_op(SLOT, PintOp::Clear, PintCondition::Status); + sys_irq_control(notifications::JTAG_DETECT_IRQ_MASK, true); + } - // Force reinitialization - self.init = false; + if (bits & notifications::TIMER_MASK) != 0 { + ringbuf_entry!(Trace::WatchdogFired); + + match self.do_setup_swd() { + Ok(()) => { + // Disable the watchdog timer + sys_set_timer(None, notifications::TIMER_MASK); + // Attempt to do the swap + let r = self.swap_sp_slot(); + ringbuf_entry!(Trace::WatchdogSwap(r)); + + // Force reinitialization + self.init = false; + } + Err(e) => { + // This should only fail if JTAG_DETECT or SP_RESET are currently asserted. + ringbuf_entry!(Trace::TimerHandlerError(e)); + } + } + self.watchdog_ms = None; + } + + if (bits & notifications::SP_RESET_IRQ_MASK) != 0 { + ringbuf_entry!(Trace::SpResetFired); + if !invalidate && !self.do_handle_sp_reset() { + ringbuf_entry!(Trace::InvalidateSpMeasurement); + invalidate = true; + } + // else something something { + // We are not going to try to measure/trust the SP + // when there is a glitch on the JTAG_DETECT signal. + // + // e.g. JTAG_DETECT fired but before the handler was called, it + // deasserted so that the SP_RESET that also fired could be + // handled successfully. + //} + + const SLOT: PintSlot = ROT_TO_SP_RESET_L_IN_PINT_SLOT; + let _ = gpio.pint_op(SLOT, PintOp::Clear, PintCondition::Status); + // Get rid of spurious interrupts cause by do_handle_sp_reset() + // toggling SP_RESET. + sys_irq_control_clear_pending( + notifications::SP_RESET_IRQ_MASK, + true, + ); + ringbuf_entry!(Trace::EndOfNotificationHandler); + } + + if invalidate { + // invalidate_sp_measurement() logs to Ringbuf + let _ = self.invalidate_sp_measurement(); + } } } @@ -747,7 +937,6 @@ impl ServerImpl { } fn swd_read(&mut self, port: Port, reg: RawSwdReg) -> Result { - ringbuf_entry!(Trace::SwdRead(port, reg)); loop { let result = self.swd_transfer_cmd(port, reg); @@ -777,8 +966,6 @@ impl ServerImpl { } fn swd_dongle_detected(&self) -> bool { - use drv_lpc55_gpio_api::*; - let gpio = Pins::from(self.gpio); gpio.read_val(SP_TO_ROT_JTAG_DETECT_L) == Value::Zero } @@ -818,7 +1005,6 @@ impl ServerImpl { reg: RawSwdReg, val: u32, ) -> Result<(), Ack> { - ringbuf_entry!(Trace::SwdWrite(port, reg, val)); loop { let result = self.swd_transfer_cmd(port, reg); @@ -919,6 +1105,46 @@ impl ServerImpl { Ok(()) } + /// Write an arbitrary number of words via SWD + fn swd_bulk_write(&mut self, addr: u32, data: &[u8]) -> Result<(), Ack> { + // TODO: Performance could be improved here. This is using + // single_read/single_write to write and verify every u32. + // It is consuming about 0.25 seconds to inject the `endoscope` code. + let mut addr = addr; + const U32_SIZE: usize = core::mem::size_of::(); + if data.len() % U32_SIZE != 0 { + ringbuf_entry!(Trace::BadLen); + return Err(Ack::Fault); + } + for slice in data.chunks_exact(U32_SIZE) { + if let Some(word) = slice_to_le_u32(slice) { + let mut limit = 2; + loop { + self.write_single_target_addr(addr, word)?; + let readback = self.read_single_target_addr(addr)?; + if readback == word { + break; + } + ringbuf_entry!(Trace::Data { + addr, + data: readback, + src: word + }); + if limit == 0 { + ringbuf_entry!(Trace::ReadbackFailure); + return Err(Ack::Fault); + } + limit -= 1; + } + addr += U32_SIZE as u32; + } else { + // Using chunks_exact means that the conversion to u32 will succeed. + unreachable!(); + } + } + Ok(()) + } + fn read_transaction_word(&mut self) -> Result, Ack> { if let Some(mut transaction) = &self.transaction { let val = self.swd_read_ap_reg(ApAddr(0, ApReg::DRW), true)?; @@ -954,6 +1180,27 @@ impl ServerImpl { Ok(val) } + /// Read the DP's 32-bit data register + fn read_dcrdr(&mut self) -> Result { + self.read_single_target_addr(DCRDR) + } + + /// Read a DP register as a known bitflag. + fn dp_read_bitflags(&mut self) -> Result + where + T: bitflags::Flags + DpAddressable, + { + self.read_single_target_addr(T::ADDRESS) + .map(|r| T::from_bits_retain(r)) + } + + fn dp_write_bitflags(&mut self, val: T) -> Result<(), Ack> + where + T: bitflags::Flags + DpAddressable, + { + self.write_single_target_addr(T::ADDRESS, val.bits()) + } + fn write_single_target_addr( &mut self, addr: u32, @@ -1002,7 +1249,9 @@ impl ServerImpl { } fn pin_setup(&mut self) { - setup_pins(self.gpio).unwrap_lite(); + // setup_pins is generated at compile time + // and has configuration sanity checks. + _ = setup_pins(self.gpio); } /// Swaps the currently-active SP slot @@ -1013,7 +1262,7 @@ impl ServerImpl { // RM0433 Table 8 const BASE: u32 = 0x52002000; - // RM0433 Section 4 + // RM0433 Section 4.9.7 const OPTCR: u32 = BASE + 0x018; const OPTCR_OPTLOCK_BIT: u32 = 1 << 0; const OPTCR_OPTSTART_BIT: u32 = 1 << 1; @@ -1058,14 +1307,636 @@ impl ServerImpl { } // Reset the STM32, causing it to reboot into the newly-set slot - use drv_lpc55_gpio_api::{Pins, Value}; - let gpio = Pins::from(self.gpio); - gpio.set_val(ROT_TO_SP_RESET_L, Value::Zero); + self.sp_reset(false); + Ok(()) + } + + fn sp_reset(&mut self, squelch_notify: bool) { + self.sp_reset_enter(); hl::sleep_for(10); - gpio.set_val(ROT_TO_SP_RESET_L, Value::One); + self.sp_reset_leave(squelch_notify); + } + + fn sp_reset_enter(&mut self) { + setup_rot_to_sp_reset_l_out(self.gpio); + let gpio = Pins::from(self.gpio); + gpio.set_val(ROT_TO_SP_RESET_L_OUT, Value::Zero); + } + + /// Cleanup is true if we don't want to be notified of this reset + fn sp_reset_leave(&mut self, squelch_notify: bool) { + let gpio = Pins::from(self.gpio); + setup_rot_to_sp_reset_l_in(self.gpio); + gpio.set_val(ROT_TO_SP_RESET_L_IN, Value::One); // should be a no-op + if squelch_notify { + let _ = gpio.pint_op( + ROT_TO_SP_RESET_L_IN_PINT_SLOT, + PintOp::Clear, + PintCondition::Rising, + ); + let _ = gpio.pint_op( + ROT_TO_SP_RESET_L_IN_PINT_SLOT, + PintOp::Clear, + PintCondition::Falling, + ); + } + } + + // The SP is halted at its reset vector. + fn sp_measure_fast(&mut self) -> Result<[u8; 256 / 8], ()> { + // write program entry address to PC (R15) + // write top-of-stack address to MSP (R13) + // write start of image/vector table address(0x20000000) to VTOR (at 0xe000ed08) + // write the program to RAM + // write DHCSR to RUN (MATIC + C_DEBUGEN) + // poll for S_HALT or timeout + + // Search st.com for document "PM0253". Section 2.4.4 describes + // the STM32H753 vector table. + // + // The `endoscope` image has the vector table at offset 0. When loaded + // into the SP, that table will be at `endoscope::LOAD` and the SP's + // `VTOR` register will be set to that address prior to the SP being + // released from reset. Addresses in the vector table are absolute + // runtime values. + // + // The first u32 in the `endoscope` image is the initial stack pointer + // value. The second u32 is the initial program counter, a.k.a. the + // reset vector. + + const_assert!(ENDOSCOPE_BYTES.len() > 2 * 1024); + + // Set SP's Program Counter + if let Some(sp_reset_vector) = slice_to_le_u32(&ENDOSCOPE_BYTES[4..=7]) + { + if self + .do_write_core_register(Reg::Dr.into(), sp_reset_vector) + .is_err() + { + ringbuf_entry!(Trace::WrotePcRegisterFail); + return Err(()); + } + } else { + // There are sufficient bytes to read this word out. + unreachable!(); + } + + // Set SP's Stack Pointer + if let Some(sp_initial_sp) = slice_to_le_u32(&ENDOSCOPE_BYTES[0..=3]) { + if self + .do_write_core_register(Reg::Sp.into(), sp_initial_sp) + .is_err() + { + ringbuf_entry!(Trace::WroteSpRegisterFail); + return Err(()); + } + } else { + // There are sufficient bytes to read this word out. + unreachable!(); + } + + // Set VTOR - Set vector table base address + if self + .write_single_target_addr(VTOR, endoscope::LOAD) + .is_err() + { + ringbuf_entry!(Trace::CannotWriteVtor); + return Err(()); + } + + // Write the endoscope program into the SP RAM + let start = sys_get_timer().now; + if self + .swd_bulk_write(endoscope::LOAD, ENDOSCOPE_BYTES) + .is_err() + { + ringbuf_entry!(Trace::InjectionFailed); + return Err(()); + } + // log the injection time which can still be improved. + let now = sys_get_timer().now; + ringbuf_entry!(Trace::Injected { + start: endoscope::LOAD, + length: ENDOSCOPE_BYTES.len(), + delta_t: (now - start) as u32 + }); + + // Resume execution by turning off DHCSR_C_HALT + if let Err(e) = self.dp_write_bitflags::(Dhcsr::resume()) { + ringbuf_entry!(Trace::AckErr(e)); + return Err(()); + } + + // It takes about 0.25 seconds (236 RoT systicks) for `endoscope` to run. + // Allow about twice that time for the measurement to complete. + // endoscope executes a BKPT instruction on completion. + // We observe an S_HALT state if all goes well. + // Otherwise, time out due to not halting as expected, + // or faulting before setting the proper shared state + // result in returning failures. + + if self.wait_for_sp_halt(512).is_err() { + ringbuf_entry!(Trace::DidNotHalt); + return Err(()); + }; + + let mut shared = Shared { + state: State::Preboot as u32, + digest: [0u8; 32], + }; + + if self + .read_buf_from_addr(endoscope::SHARED, shared.as_bytes_mut()) + .is_err() + { + ringbuf_entry!(Trace::ReadBufFail); + return Err(()); + } + + ringbuf_entry!(Trace::SharedState(shared.state)); + if shared.state != (State::Done as u32) { + return Err(()); + } + + if let Ok(d) = shared.digest[0x00..=0x07].try_into() { + ringbuf_entry!(Trace::Digest0(d)); + } + if let Ok(d) = shared.digest[0x08..=0x0f].try_into() { + ringbuf_entry!(Trace::Digest1(d)); + } + if let Ok(d) = shared.digest[0x10..=0x17].try_into() { + ringbuf_entry!(Trace::Digest2(d)); + } + if let Ok(d) = shared.digest[0x18..=0x1f].try_into() { + ringbuf_entry!(Trace::Digest3(d)); + } + + Ok(shared.digest) + } + + fn do_write_core_register( + &mut self, + register: u16, + value: u32, + ) -> Result<(), SpCtrlError> { + // C1.6 Debug system registers + let r = match register { + // R0-R12 + 0b0000000..=0b0001100 + + // LR - PSP + | 0b0001101..=0b0010010 + + // CONTROL/FAULTMASK/BASEPRI/PRIMASK + | 0b0010100 + + // FPCSR + | 0b0100001 + + // S0-S31 + | 0b1000000..=0b1011111 => Ok::(register), + _ => { + ringbuf_entry!(Trace::InvalidRegisterWrite(register, value)); + Err(SpCtrlError::InvalidCoreRegister) + } + }?; + + self.write_single_target_addr(DCRDR, value) + .map_err(|_| SpCtrlError::Fault)?; + self.write_single_target_addr(DCRSR, r as u32 | (1u32 << 16)) + .map_err(|_| SpCtrlError::Fault)?; + + const RETRY_LIMIT: u32 = 10; + let mut limit = RETRY_LIMIT; + loop { + match self.dp_read_bitflags::() { + Ok(dhcsr) => { + if dhcsr.is_regrdy() { + // Trace retries used + if limit != RETRY_LIMIT { + ringbuf_entry!(Trace::LimitRemaining(limit)); + } + return Ok(()); + } + if limit == 0 { + ringbuf_entry!(Trace::LimitRemaining(limit)); + return Err(SpCtrlError::Fault); + } + limit -= 1; + hl::sleep_for(1); + } + Err(_) => return Err(SpCtrlError::Fault), + } + } + } + + /// Measure the current SP Hubris Image. + /// The SP reset vector has been trapped and + /// the SP is halted. + fn do_measure_sp(&mut self) -> Result<[u8; 256 / 8], ()> { + // For Hubris on the STM32H7, The FWID includes 0xff padding to + // the end of the flash bank. + + // Time the code injection injection, calculation, + // and readout of the FWID. + let start = sys_get_timer().now; + let r = self.sp_measure_fast(); + let now = sys_get_timer().now; + + ringbuf_entry!(Trace::MeasuredSp { + success: r.is_ok(), + delta_t: (now - start) as u32 + }); + + r + } + + fn read_buf_from_addr( + &mut self, + addr: u32, + buf: &mut [u8], + ) -> Result<(), SpCtrlError> { + let start = addr; + let end = addr + buf.len() as u32; + self.start_read_transaction(start, ((end - start) as usize) / 4) + .map_err(|_| SpCtrlError::Fault)?; + + let cnt = buf.len(); + if cnt % 4 != 0 { + return Err(SpCtrlError::BadLen); + } + + let mut i = 0usize; + for _ in 0..cnt / 4 { + match self.read_transaction_word() { + Ok(r) => { + if let Some(w) = r { + for b in w.to_le_bytes() { + buf[i] = b; + i += 1; + } + } + } + Err(_) => return Err(SpCtrlError::Fault), + } + } Ok(()) } + + fn do_setup_swd(&mut self) -> Result<(), SpCtrlError> { + ringbuf_entry!(Trace::DoSetup); + + // TODO: clients interferring with each other and SP_RESET handling. + // + // A client can call into this SWD task using multiple API calls. + // While that session is active, inbetween calls, an SP_INTERRUPT + // can happen that will change the state of the SWD interface and the + // SP itself. If there were multiple external clients (hiffy vs dump), + // they could interfere with each other's work. + // + // The dump client is trying to be non-intrusive and will try to resume + // the SP after finishing its work, the last call being `resume` or, if + // that fails, `setup` followed by `resume`. + if !self.init { + ringbuf_entry!(Trace::PinSetupDefaults); + // This should be redundant since setup_pins needs to be + // called in order for SP_RESET interrupt to be plumbed. + // The only exception is if SP_RESET is driven as an output + // that that should be a non-interruptable transient state. + self.pin_setup(); + } + + if self.swd_dongle_detected() { + ringbuf_entry!(Trace::DongleDetected); + return Err(SpCtrlError::DongleDetected); + } + + match self.swd_setup() { + Ok(_) => { + ringbuf_entry!(Trace::SwdSetupOk); + self.init = true; + Ok(()) + } + Err(_) => { + ringbuf_entry!(Trace::SwdSetupFail); + Err(SpCtrlError::Fault) + } + } + } + + fn do_halt(&mut self) -> Result<(), SpCtrlError> { + ringbuf_entry!(Trace::DoHalt); + self.dp_write_bitflags::(Dhcsr::halt()) + .map_err(|_| SpCtrlError::Fault)?; + self.wait_for_sp_halt(5000) + } + + fn wait_for_sp_halt(&mut self, timeout: u64) -> Result<(), SpCtrlError> { + ringbuf_entry!(Trace::HaltWait { + timeout: timeout as u32 + }); + let start = sys_get_timer().now; + let deadline = start.wrapping_add(timeout); + loop { + if let Ok(dhcsr) = self.dp_read_bitflags::() { + if dhcsr.is_halted() { + ringbuf_entry!(Trace::Halted { + delta_t: (sys_get_timer().now - start) as u32 + }); + return Ok(()); + } + } else { + ringbuf_entry!(Trace::HaltFail( + (sys_get_timer().now - start) as u32 + )); + return Err(SpCtrlError::Fault); + } + if deadline <= sys_get_timer().now { + ringbuf_entry!(Trace::Timeout); + break Err(SpCtrlError::Timeout); + } + hl::sleep_for(1); + } + } + + fn do_resume(&mut self) -> Result<(), SpCtrlError> { + if self.dp_write_bitflags::(Dhcsr::resume()).is_ok() { + ringbuf_entry!(Trace::Resumed); + Ok(()) + } else { + ringbuf_entry!(Trace::ResumeFail); + Err(SpCtrlError::Fault) + } + } + + fn invalidate_sp_measurement(&mut self) -> Result<(), AttestError> { + match self.attest.reset() { + Ok(()) => { + ringbuf_entry!(Trace::InvalidatedSpMeasurement); + Ok(()) + } + Err(e) => { + ringbuf_entry!(Trace::AttestError(e)); + Err(e) + } + } + } + + // Return true if necessary work was done. + // Return false if any current SP measurement should be invalidated. + fn do_handle_sp_reset(&mut self) -> bool { + let start = sys_get_timer().now; + let gpio = Pins::from(self.gpio); + const SLOT: PintSlot = ROT_TO_SP_RESET_L_IN_PINT_SLOT; + let mut need_undo = Undo::from_bits_retain(0); + + // Did SP_RESET transition to Zero? + if let Ok(Some(detected)) = + gpio.pint_op(SLOT, PintOp::Detected, PintCondition::Falling) + { + if !detected { + // Use of sys_irq_control_clear_pending(...) should avoid + // appearance of a "spurious" intrerrupt. + // Otherwise, cases where we assert SP_RESET then clean-up the PINT + // condition will have a pending notification. + ringbuf_entry!(Trace::SpResetNotAsserted); + return true; // no work required. + } + } else { + // The pint_op parameters are for a configured PINT slot for one of + // our configured GPIO pins. We're testing a status bit in a register. + unreachable!(); + } + + // Not ok yet: A reset happened. If we don't get a measurement then + // make sure that the old one is invalidated. + let mut error = false; + + ringbuf_entry!(Trace::SpResetAsserted); + + // This notification handler should be compatible with watchdog but + // will result in the invalidation of any dumps held in the SP if successful. + + // TODO: confirm that bank-flipping SP update watchdog is working. + + self.init = true; // we don't want GPIO pin reconfiguration. + if self.do_setup_swd().is_ok() { + ringbuf_entry!(Trace::SetupSwdOk); + need_undo |= Undo::SWD; + } else { + // We may have interrupted dumper or watchdog activity. + return false; // Cannot make the required measurement. + } + + // Armv7-M Arch Ref: + // C1.4.1 Entering Debug state on leaving reset state + // + // To force the processor to enter Debug state as soon as it + // comes out of reset, a debugger sets DHCSR.C_DEBUGEN to 1, to + // enable Halting debug, and sets DEMCR.VC_CORERESET to 1 to + // enable vector catch on the Reset exception. When the + // processor comes out of reset it sets DHCSR.C_HALT to 1, + // and enters Debug state. + + // If we are late to the SP_RESET party, we're still not that late. + // In any case, keep/force the SP into a reset condition. + // Though AIRCR::SYSRESETREQ can be used to effect a local reset. + // that does not affect the whole SP SoC. + // So, use the SP_RESET GPIO. + // + + // Setting up to inject the measurement program into the SP + // has several potential failures. Use this `prep` closure + // and `need_undo` state to keep from indenting too much. + let mut prep = |undo: &mut Undo| -> Result<(), ()> { + self.sp_reset_enter(); + *undo |= Undo::RESET; + + // Asserting SP_RESET for >1ms here works. + hl::sleep_for(1); + + if self.dp_write_bitflags::(Dhcsr::resume()).is_err() { + ringbuf_entry!(Trace::DemcrWriteError); + *undo |= Undo::DEBUGEN; + return Err(()); + } + *undo |= Undo::DEBUGEN; + + if self + .dp_write_bitflags::(Demcr::VC_CORERESET) + .is_err() + { + ringbuf_entry!(Trace::DemcrWriteError); + *undo |= Undo::VC_CORERESET; + return Err(()); + } + *undo |= Undo::VC_CORERESET; + + self.sp_reset_leave(true); + *undo &= !Undo::RESET; + + // 100ms max wait. Typical wait looks to be 5ms. + self.wait_for_sp_halt(100).map_err(|_| ())?; + + // Check that RESET was caught + if let Ok(dfsr) = self.dp_read_bitflags::() { + if !dfsr.is_vcatch() { + ringbuf_entry!(Trace::Dfsr(dfsr)); + ringbuf_entry!(Trace::VcCoreResetNotCaught); + return Err(()); + } + } else { + ringbuf_entry!(Trace::DfsrReadError); + } + + // We don't want to catch the next reset. + if self + .dp_write_bitflags::(Demcr::from_bits_retain(0)) + .is_err() + { + ringbuf_entry!(Trace::DemcrWriteError); + return Err(()); + } + + // need_undo was set appropriately + if let Ok(demcr) = self.dp_read_bitflags::() { + if demcr & Demcr::VC_CORERESET != Demcr::VC_CORERESET { + ringbuf_entry!(Trace::VcCoreReset(false)); + *undo &= !Undo::VC_CORERESET; + } else { + ringbuf_entry!(Trace::VcCoreReset(true)); + return Err(()); + } + } else { + ringbuf_entry!(Trace::DemcrReadError); + return Err(()); + } + Ok(()) + }; + + // To ensures that any cleanup is done and the SP hardware is left + // running properly, there can only be one return at the end. + + let digest = if prep(&mut need_undo).is_ok() { + self.do_measure_sp() + } else { + Err(()) + }; + + // From here on, we're cleaning up and restarting the SP. + + // It is very unlikely that an attached SP debug dongle would go + // active just as we are taking a measurement. + // If that happened, then JTAG DETECT will have its own notification + // and this task will perform an explicit attestation log reset. + // If there was any SWD problem for us, we may need to clean up + // ore or more of the steps that `prep` performed. + + // If anything deviates from the happy-path, we will not record a valid measurement. + + if need_undo & Undo::VC_CORERESET == Undo::VC_CORERESET { + // This should never happen. Don't believe any measurement we + // may have recorded. + ringbuf_entry!(Trace::IncompleteUndo(need_undo)); + error = true; + + if self + .dp_write_bitflags::(Demcr::from_bits_retain(0)) + .is_ok() + { + need_undo &= !Undo::VC_CORERESET; + ringbuf_entry!(Trace::VcCoreReset(false)); + } else { + ringbuf_entry!(Trace::DemcrWriteError); + } + if let Ok(r) = self.dp_read_bitflags::() { + ringbuf_entry!(Trace::Dhcsr(r)); + } + } + + // This read of DHCSR should never be useful given the code above. + // Read back to make sure that VC_CORERESET is clear. + // XXX remove + if let Ok(demcr) = self.dp_read_bitflags::() { + if (demcr & Demcr::VC_CORERESET) == Demcr::VC_CORERESET { + error = true; + need_undo |= Undo::VC_CORERESET; + ringbuf_entry!(Trace::BadDemcr(demcr)); + } + } else { + ringbuf_entry!(Trace::DemcrReadError); + } + + // Unless `prep` failed, this will always be needed. + if need_undo & Undo::DEBUGEN == Undo::DEBUGEN { + if self.dp_write_bitflags::(Dhcsr::end_debug()).is_ok() { + need_undo &= !Undo::DEBUGEN; + } else { + ringbuf_entry!(Trace::DhcsrWriteError); + } + } else { + ringbuf_entry!(Trace::Never); + } + + // This should always be needed + if need_undo & Undo::SWD == Undo::SWD { + self.swd_finish(); + need_undo &= !Undo::SWD; + } else { + ringbuf_entry!(Trace::Never); + error = true; + } + + if !need_undo.is_empty() { + ringbuf_entry!(Trace::IncompleteUndo(need_undo)); + error = true; + } + + // The SP is still halted. + // Get it running again by toggling its RESET pin. + self.sp_reset_enter(); + + // Record a successful measurement before releasing the SP from reset. + let success = if let Ok(digest) = digest { + // SP resets the attestation log and record the new measuremen. + if !error + && self + .attest + .reset_and_record(HashAlgorithm::Sha3_256, &digest) + .is_ok() + { + ringbuf_entry!(Trace::RecordedMeasurement); + true + } else { + ringbuf_entry!(Trace::RecordMeasurementFailed); + false + } + } else { + ringbuf_entry!(Trace::MeasureFailed); + false + }; + + hl::sleep_for(1); + self.sp_reset_leave(true); + + let now = sys_get_timer().now; + ringbuf_entry!(Trace::MeasuredSp { + success, + delta_t: (now - start) as u32 + }); + + success + } +} + +fn slice_to_le_u32(slice: &[u8]) -> Option { + if slice.len() == core::mem::size_of::() { + if let Ok(data) = core::convert::TryInto::<[u8; 4]>::try_into(slice) { + return Some(u32::from_le_bytes(data)); + } + } + None } #[export_name = "main"] @@ -1073,6 +1944,7 @@ fn main() -> ! { let syscon = SYSCON.get_task_id(); let gpio = GPIO.get_task_id(); + let attest = Attest::from(ATTEST.get_task_id()); let mut spi = setup_spi(syscon); @@ -1091,11 +1963,66 @@ fn main() -> ! { let mut server = ServerImpl { spi, gpio, + attest, init: false, transaction: None, + watchdog_ms: None, }; + // Setup GPIO pins so that we can receive interrupts. + server.pin_setup(); + + // Detect SP entering reset + let _ = Pins::from(server.gpio).pint_op( + ROT_TO_SP_RESET_L_IN_PINT_SLOT, + PintOp::Enable, + PintCondition::Falling, + ); + sys_irq_control(notifications::SP_RESET_IRQ_MASK, true); + + // JTAG active will block SWD operations. + // We also need to detect JTAG going active so that if we've made a + // measurement it can be invalidated. + let _ = Pins::from(server.gpio).pint_op( + SP_TO_ROT_JTAG_DETECT_L_PINT_SLOT, + PintOp::Enable, + PintCondition::Falling, + ); + sys_irq_control(notifications::JTAG_DETECT_IRQ_MASK, true); + let mut incoming = [0; idl::INCOMING_SIZE]; + + // TODO: If this is a power-on situation and SP and RoT are booting + // at nearly the same time, can that be detected? That may be a + // case where it is ok for the RoT to reset the SP and measure it. + // + // System power could be sequenced to allow the RoT + // power to be on for 3-4 seconds before SP power on but that creates + // a special case that complicates testing and building confidence + + // TODO: If SP is halted, then reset it. + // It's conceivable that the RoT or this task could restart while the + // SP is halted. Without code here to check for that case, it will be + // necessary for an external party to take action through `ignition` or + // other means to restart this system. + // - If this a whole RoT reboot + // - this is the normal no-worries case. + // - the attestation log will already be empty + // - else + // - this should never happen. + // - it would be good to surface this event so that it can be fixed. + // - attestation log should probably be reset. + // - attach to SP via SWD, + // - check for SP running/halted + // - if SP is halted or otherwise faulted + // - toggle SP RESET line. + // - RoT will be notified and will measure the SP + // - else + // - normal, no worries + // - We should get a clue to the SP or control plane that the SP needs to be + // measured. The control plane can trigger a measurement by asking the SP to + // reset itself. RoT detects that and takes a measurement. + loop { idol_runtime::dispatch(&mut incoming, &mut server); } @@ -1110,3 +2037,4 @@ mod idl { include!(concat!(env!("OUT_DIR"), "/pin_config.rs")); include!(concat!(env!("OUT_DIR"), "/swd.rs")); include!(concat!(env!("OUT_DIR"), "/notifications.rs")); +include!(concat!(env!("OUT_DIR"), "/endoscope.rs")); diff --git a/drv/sp-ctrl-api/src/lib.rs b/drv/sp-ctrl-api/src/lib.rs index 9b948dc8b..bc54a7ec5 100644 --- a/drv/sp-ctrl-api/src/lib.rs +++ b/drv/sp-ctrl-api/src/lib.rs @@ -21,6 +21,7 @@ pub enum SpCtrlError { #[idol(server_death)] ServerRestarted, + Timeout, } include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/idl/attest.idol b/idl/attest.idol index c459bf874..2f1b7eb77 100644 --- a/idl/attest.idol +++ b/idl/attest.idol @@ -163,6 +163,28 @@ Interface( encoding: Hubpack, idempotent: true, ), + "reset": ( + doc: "Reset the attestation log", + reply: Result( + ok: "()", + err: Complex("AttestError"), + ), + encoding: Hubpack, + ), + "reset_and_record": ( + doc: "Reset the attestation log and then record a measurment", + args: { + "algorithm": "HashAlgorithm", + }, + leases: { + "data": (type: "[u8]", read: true), + }, + reply: Result( + ok: "()", + err: Complex("AttestError"), + ), + encoding: Hubpack, + ), } ) diff --git a/idl/sp-ctrl.idol b/idl/sp-ctrl.idol index 075be7e10..a3294707d 100644 --- a/idl/sp-ctrl.idol +++ b/idl/sp-ctrl.idol @@ -104,5 +104,13 @@ Interface( reply: Simple("()"), idempotent: true, ), + "db_reset_sp": ( + doc: "Debugging: Assert SP RESET with specified delay", + args: { + "delay_ms": "u32", + }, + reply: Simple("()"), + idempotent: true, + ) } ) diff --git a/lib/endoscope-abi/Cargo.toml b/lib/endoscope-abi/Cargo.toml new file mode 100644 index 000000000..b22e2efab --- /dev/null +++ b/lib/endoscope-abi/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "endoscope-abi" +version = "0.1.0" +edition = "2021" + +[lib] +test = false +doctest = false +bench = false + +[dependencies] +zerocopy = { workspace = true } + +[lints] +workspace = true diff --git a/lib/endoscope-abi/src/lib.rs b/lib/endoscope-abi/src/lib.rs new file mode 100644 index 000000000..737aa5e26 --- /dev/null +++ b/lib/endoscope-abi/src/lib.rs @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! endoscope-abi +//! +//! This crate documents the interface of the program that the RoT injects into +//! the SP that measures the SP flash contents. +//! +//! When SP reset is detected, the RoT halts the SP, injects the endoscope program +//! into the SP RAM and runs it. The endoscope program will measure the entire +//! active flash bank and deposit the resulting Sha3-256 hash into the Shared +//! structure. The RoT polls the STM32 debug module waiting for a halt or timeout. +//! The valid measurement is retrieved and recorded if availalble. +//! + +#![no_std] + +use zerocopy::*; + +#[derive(Copy, Clone)] +#[repr(u32)] +pub enum State { + #[allow(dead_code)] + Preboot = 0, + Running = 0x1de6060, + Done = 0x1dec1a0, +} + +#[derive(FromBytes, AsBytes, Copy, Clone)] +#[repr(C, packed)] +pub struct Shared { + pub state: u32, + pub digest: [u8; 256 / 8], +} + +impl Shared { + pub fn parse(bytes: &[u8]) -> Option<&Self> { + if let Some((layout, _)) = + LayoutVerified::<&[u8], Self>::new_from_prefix(bytes) + { + let shared: &Shared = layout.into_ref(); + Some(shared) + } else { + None + } + } +} diff --git a/lib/endoscope/Cargo.toml b/lib/endoscope/Cargo.toml new file mode 100644 index 000000000..df3ae7e82 --- /dev/null +++ b/lib/endoscope/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "endoscope" +version = "0.1.0" +edition = "2021" +readme = "README.md" +resolver = "2" + +#[profile.release] +#codegen-units = 1 +#debug = 2 +#lto = true +#opt-level = "z" +#panic = "abort" + +#[profile.dev] +#codegen-units = 1 +#debug = 2 +#lto = true +#opt-level = 1 +#panic = "abort" + +[dependencies] +cortex-m = { workspace = true, default-features = false } +cortex-m-rt = { workspace = true, default-features = false, features = ["device"] } +panic-halt = { workspace = true, default-features = false } +sha3 = { workspace = true, default-features = false } +endoscope-abi = { path = "../endoscope-abi" } +stm32h7 = { workspace = true } +drv-stm32h7-startup = { path = "../../drv/stm32h7-startup" } + +[build-dependencies] +anyhow = { workspace = true } + +[[bin]] +name = "endoscope" +test = false +doctest = false +bench = false + +[features] +# running in SRAM with default clocking takes about 6 seconds +# running in ITCM/DTCM with tuned clocking takes about 0.5 seconds +soc_stm32h753 = ["stm32h7/stm32h753", "drv-stm32h7-startup/h753"] + +#[build] +#target = "thumbv7em-none-eabihf" +#rustflags = [ +# "-C", "overflow-checks=y", +# "-C", "link-arg=-Tendoscope.x", +#] + diff --git a/lib/endoscope/README.md b/lib/endoscope/README.md new file mode 100644 index 000000000..45514d157 --- /dev/null +++ b/lib/endoscope/README.md @@ -0,0 +1,200 @@ +# Endoscope for the STM32H753 + +This is a small RAM-resident program that computes the Sha3-256 digest +of the STM32H753 active flash bank. It is run under the control of the +STM32's debug port which in turn is controlled by a debugger or the Root +of Trust (LPC55S69) in an Oxide Computer system. + +The program's results are placed in a known memory location +which is read out when the program terminates through execution of +a breakpoint instruction. + +The program can be tested by itself using gdb/openocd or probe-rs via +an ST-LINK. + +## RoT Execution method + +Two symbols are needed from the program's symbol table: + - `__vector_table: [u32]` contains the initial_sp at [0] and initial_pc + at [1] and is also the load address of the program. + - "SHARED": The address of the shared data structure. + +The steps for execution are along these lines: + + - (RoT) Detect an SP RESET signal + - Assert the SP RESET signal to keep the SP + - Initialize the SWD interface + - Set VC_CORERESET to trap on the STM32 reset condition. + - Deassert the SP RESET signal + - ensure that the STM32 halted due to VC_CORERESET + - Inject the program into the STM32's RAM at the address `__vector_table`. + - Set The STM32 core register PC/DR to the value in `__vector_table[1]`. + - Set The STM32 core register MSP to the value in `__vector_table[0]`. + - Set The STM32 VTOR to the value of the symbol `__vector_table`. + - Continue execution + - Poll DHCSR for S_HALT state or timeout. + - The attestation log will be reset. + - If halted && `SHARED.magic == Shared::MAGIC` && `SHARED.state == State::Done` + - then read out the `digest` field. + - Clean up any STM32 debug state. + - Tear down the SWD session. + - Assert and deassert the SP RESET signal to boot the SP from FLASH. + +On failure, including the presence of an active ST-LINK dongle, the RoT will +invalidate any previous measurements that have been recorded. + +## Size and Performance + +```bash +# The program is about 5KiB and takes less than 0.5 seconds to inject and run. +$ find target/thumbv8m.main-none-eabihf/release/build \ + -name endoscope.bin -print -exec stat -c '%s' '{}' ';' +target/thumbv8m.main-none-eabihf/release/build/drv-lpc55-swd-da6462ef675419cb/out/endoscope.bin +5740 +``` + +Building as a cargo `bindeps` artifact allows the source to be maintained +in the Hubris repo and removes any concern that it is out of date with +respect to the RoT firmware. + +However, as a `bindeps` artifact, the profile that it is built with is not +allowed to specify `lto` or `panic`. + +Not being able to use the desired profile costs something around an extra 462 bytes at +the time of writing. + +Rust "code golf" opportunities for space and time include: + - Fix the build profile `lto` and `panic` prohibition described above, + - Use an FFI SHA3 library, if a more compact or faster implementation can be found. + - On the RoT side, inject the code more efficiently. + +```bash +# Building it as a stand-alone bin results in a smaller executable (4660 bytes) +$ arm-none-eabi-size target/thumbv7em-none-eabihf/release/endoscope + text data bss dec hex filename + 4608 48 4 4660 1234 target/thumbv7em-none-eabihf/release/endoscope +``` + +## Testing + +The program can be tested in isolation using gdb. But, it is simpler to +use the probe-rs-tools. + +### Setup + +In this case, the probe is an ST-LINK attached to an STM32H753xi. +Since there is only one ST-LINK on this system, the probe's serial number does +not need to be specified. + +The smaller isolated build is useful for development: + +```sh +$ cargo build --release --manifest-path lib/endoscope/Cargo.toml --target thumbv7em-none-eabihf --bin endoscope --features soc_stm32h753 +$ ELF=${PWD}/target/thumbv7em-none-eabihf/release/endoscope +$ size $ELF + text data bss dec hex filename + 4424 48 4 4476 117c ${PWD}/target/thumbv7em-none-eabihf/release/endoscope +``` + +The blob produced during the build of the RoT swd task is what will be used +in production: + +```sh +$ cargo clean # So that there is only one version available. +$ cargo xtask build $APP swd +$ ELF=$(ls -d ${PWD}/target/thumbv7em-none-eabihf/release/deps/artifact/endoscope-*/bin/endoscope-????????????????) +$ size $ELF + text data bss dec hex filename + 4920 48 4 4972 136c ${PWD}/target/thumbv7em-none-eabihf/release/deps/artifact/endoscope-05d6ebcdad662d34/bin/endoscope-05d6ebcdad662d34 +``` + +```sh +PROBE="0483:3754" # 0483:3754:${ST_LINK_SERIALNO} to disambiguate +CHIP=stm32h753xi +# See above to set the environment variable "ELF" +cargo install probe-rs-tools +``` + +### Get symbol values from the ELF file + +```sh +VTABLE=0x$(arm-none-eabi-nm -C "${ELF}" | awk '/__vector_table/ {print $1}' -) +SHARED=0x$(arm-none-eabi-nm -C "${ELF}" | awk '/SHARED/ {print $1}' -) +``` + +### Runing + +```sh +$ time probe-rs run --probe ${PROBE} --chip ${CHIP} "${ELF}" + Finished in 0.02s +Frame 0: breakpoint @ 0x24000356 + ${REPO}/src/main.rs:69:13 +Frame 1: DefaultHandler @ 0x2400034a + ${REPO}/src/main.rs:59:1 +Error: CPU halted unexpectedly. + +real 0m5.677s +user 0m0.017s +sys 0m0.122s +``` + +The unexpected halt mentioned above is actually expected. + +That run was using the STM32H753's SRAM with the default clocking after reset. + +It takes about 0.5 seconds with the correct clocks set and using +the ITCM/DTCM memories. + +### Read the Results + +The results are in a `struct Shared`. +Given that `endoscope` and the `swd` task are compiled together, no structure magic +number or versioning is required and compile- and link-time constants are trustworthy. + +```rust +#[repr(u32)] +pub enum State { + #[allow(dead_code)] + Preboot = 0, + Running = 0x1de6060, + Done = 0x1dec1a0, +} + +#[repr(C)] +pub struct Shared { + pub state: State, + pub digest: [u8; 32], +} + +impl Shared { + const MAGIC: u32 = 0x1de2019; +} +``` + +We'll dump the four 32-bit words and then the 32-byte SHA3-256 digest. + +```sh +$ probe-rs read --probe ${PROBE} --chip ${CHIP} b32 ${SHARED} 4 +01de2019 01dec1a0 08000000 00100000 +# That is the magic = 0x1de_2019, state: State = 0x1de_c1a0 /* Done */ +# Flash start address 0x0800_0000, and Flash length 0x0010_0000 + +$ probe-rs read --probe ${PROBE} --chip ${CHIP} b8 $(( ${SHARED} + 16 )) 32 | + sed -e 's/ //g' +036f35ccafba2a6e09d755db33a5be73dbc2eed0335eb21e3b78be4e15b8f754 +# That is the Sha3-256 digest of the entire active flash bank. +``` + +Or, as the struct being used: + +```rust +Shared { + state: State::Done, + digest: [ + 03, 6f, 35, cc, af, ba, 2a, 6e, + 09, d7, 55, db, 33, a5, be, 73, + db, c2, ee, d0, 33, 5e, b2, 1e, + 3b, 78, be, 4e, 15, b8, f7, 54, + ], +} +``` diff --git a/lib/endoscope/build.rs b/lib/endoscope/build.rs new file mode 100644 index 000000000..d45573aa6 --- /dev/null +++ b/lib/endoscope/build.rs @@ -0,0 +1,34 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::Result; + +fn main() -> Result<(), Box> { + // + // Set link flags when building bins + let mut soc = None; + for (key, _value) in std::env::vars() { + if key.starts_with("CARGO_FEATURE_SOC_") { + let soc_name = key + .strip_prefix("CARGO_FEATURE_SOC_") + .unwrap() + .to_lowercase(); + if soc.is_some() { + println!( + "cargo::error=Multiple 'soc_*' features enabled {}, {}", + soc.as_ref().unwrap(), + soc_name + ); + } else { + let cwd = std::env::current_dir().unwrap().join("scripts"); + + println!("cargo::rustc-link-arg=--verbose"); + println!("cargo::rustc-link-arg-bins=-T{}.x", &soc_name); + println!("cargo::rustc-link-search={}", cwd.to_str().unwrap()); + soc = Some(soc_name); + } + } + } + Ok(()) +} diff --git a/lib/endoscope/scripts/endoscope.x b/lib/endoscope/scripts/endoscope.x new file mode 100644 index 000000000..6ec522544 --- /dev/null +++ b/lib/endoscope/scripts/endoscope.x @@ -0,0 +1,289 @@ +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +/* Cribbed from hubris file build/kernel-link.x */ + +/* # Entry point = reset vector */ +ENTRY(Reset); +EXTERN(__RESET_VECTOR); /* depends on the `Reset` symbol */ + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +/* # Sections */ +SECTIONS +{ + PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); + + /* ## Sections in FLASH */ + /* ### Vector table */ + .vector_table ORIGIN(RAM) : + { + __start_vector = .; + __vector_table = .; + /* Initial Stack Pointer (SP) value */ + LONG(_stack_start); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + __reset_vector = .; + + /* Exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; + + /* Device specific interrupts */ + /* This binary uses no devices and runs panic!() on termination. */ + /* KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > RAM + + __vector_size = SIZEOF(.vector_table); + + /* Explicitly place text after output struct, deliberately ignoring + section alignment. This is important because the RoT SP measurement code + assumes that the Shared struct immediately follows the vector table; if + something changes to cause that to not be true, this expression will cause + the text and header sections to overlap, causing a difficult to understand + linker failure, which will hopefully be somewhat improved by this comment. + */ + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + /* place these 2 close to each other or the `b` instruction will fail to link */ + *(.PreResetTrampoline); + *(.Reset); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + *(.text.HardFault.*); + *(.rot_shared); + + *(.text .text.*); + + . = ALIGN(4); + __etext = .; + } > RAM + + /* ### .rodata */ + .rodata __etext : ALIGN(4) + { + __srodata = .; + *(.rodata .rodata.*); + /* We move this into a special section so we can ensure it is always + included in the build */ + KEEP(*(.hubris_id)); + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + __erodata = .; + } > RAM + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM /* AT>FLASH */ + /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __edata = .; + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the FLASH usage to 32 byte alignment. */ + .gnu.sgstubs : { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + __veneer_limit = .; + } > RAM + + /* + * Fill the remaining flash space with a known value + */ + /* + .fill : ALIGN(1) { + . = (ORIGIN(FLASH) + LENGTH(FLASH)); + } > FLASH =0xffffffff + */ + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* ## .got */ + /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in + the input files and raise an error if relocatable code is found */ + .got (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* No external interrupts are being used */ + *(.vector_table.interrupts); + /* Unused exception related info that only wastes space */ + *(.__INTERRUPTS); + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* No devices used (XXX Is default clocking ok?, Could we go faster?) */ +/* INCLUDE device.x */ + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +/* +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(cortex-m-rt): the start of the FLASH region must be 4-byte aligned"); +*/ + +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG(cortex-m-rt): .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG(cortex-m-rt): .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); + +/* # Position checks */ + +/* ## .vector_table */ +ASSERT(__reset_vector == ADDR(.vector_table) + 0x8, " +BUG(cortex-m-rt): the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG(cortex-m-rt): the exception vectors are missing"); + +/* XXX We want interrupt vectors to be missing. We are not going to enable any of them. */ +/* +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR(cortex-m-rt): The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); +*/ + +/* XXX We're going to allow this. No interrupts will be enabled. */ +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +/* +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(cortex-m-rt): The .text section must be placed inside the FLASH memory. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); +*/ + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR(cortex-m-rt): .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); + + /* So let's talk about vector table alignment: + * ARMv8m section B3.30 + * In a PE with a configurable vector table base, the vector table is naturally-aligned to a power of two, with an + * alignment value that is: + * - A minimum of 128 bytes. + * - Greater than or equal to (Number of Exceptions supported x4) + * ARMv7m section B1.5.3 + * The Vector table must be naturally aligned to a power of two whose alignment value is greater than or equal to + * (Number of Exceptions supported x 4), with a minimum alignmentof 128 bytes. + * + * Annoyingly this means that vector table alignment is device specific. + * This is also a nice catch-22: we need to know the size of the vector + * table to calculate the alignment but we need to know the alignment + * before we know the size. + * + * The best we can do right now is use a specified alignment and + * indicate if it is wrong + */ +ASSERT(ADDR(.vector_table) % (1 << LOG2CEIL(SIZEOF(.vector_table))) == 0, " +Vector table alignment too small for number of exception entires. Increase +the alignment to the next power of two"); + + +/* Do not exceed this mark in the error messages above | */ diff --git a/lib/endoscope/scripts/stm32h753.x b/lib/endoscope/scripts/stm32h753.x new file mode 100644 index 000000000..fc8464fe1 --- /dev/null +++ b/lib/endoscope/scripts/stm32h753.x @@ -0,0 +1,22 @@ + +/* Provides information about the memory layout of the device */ +MEMORY +{ +FLASH (rwx) : ORIGIN = 0x08000000, LENGTH = 0x00100000 +/* RAM is artifically reduced to catch program becoming too large */ +/* RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 0x00004000 */ +/* STACK (rw) : ORIGIN = 0x24004000, LENGTH = 0x00001000 */ +/* Use Instruction Tightly Coupled Memory (ITCM) for RAM */ +RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 0x00010000 +/* Use Data Tightly Coupled Memory (ITCM) for STACK */ +STACK (rw) : ORIGIN = 0x20000000, LENGTH = 0x00020000 +} + +__eheap = ORIGIN(RAM) + LENGTH(RAM); +_stack_base = ORIGIN(STACK); +_stack_start = ORIGIN(STACK) + LENGTH(STACK); + +FLASH_BASE = 0x08000000; +FLASH_SIZE = 0x00100000; + +INCLUDE "endoscope.x" diff --git a/lib/endoscope/src/main.rs b/lib/endoscope/src/main.rs new file mode 100644 index 000000000..e0c03e5aa --- /dev/null +++ b/lib/endoscope/src/main.rs @@ -0,0 +1,117 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#![no_std] +#![no_main] + +// use panic_halt as _; +use core::arch::{self}; +use core::panic::PanicInfo; +use cortex_m_rt::entry; +use drv_stm32h7_startup::system_init; +use drv_stm32h7_startup::ClockConfig; +use stm32h7::stm32h753 as device; + +use sha3::{Digest, Sha3_256}; + +// The clock settings for Gimlet, PSC, Sidecar, and Grapefruit, as well as the +// STM32H753 Nucleo board, are all the same. Refactor when new incompatible boards are added. +const CLOCK_CONFIG: ClockConfig = ClockConfig { + source: drv_stm32h7_startup::ClockSource::ExternalCrystal, + // 8MHz HSE freq is within VCO input range of 2-16, so, DIVM=1 to bypass + // the prescaler. + divm: 1, + // VCO must tolerate an 8MHz input range: + vcosel: device::rcc::pllcfgr::PLL1VCOSEL_A::WIDEVCO, + pllrange: device::rcc::pllcfgr::PLL1RGE_A::RANGE8, + // DIVN governs the multiplication of the VCO input frequency to produce + // the intermediate frequency. We want an IF of 800MHz, or a + // multiplication of 100x. + // + // We subtract 1 to get the DIVN value because the PLL effectively adds + // one to what we write. + divn: 100 - 1, + // P is the divisor from the VCO IF to the system frequency. We want + // 400MHz, so: + divp: device::rcc::pll1divr::DIVP1_A::DIV2, + // Q produces kernel clocks; we set it to 200MHz: + divq: 4 - 1, + // R is mostly used by the trace unit and we leave it fast: + divr: 2 - 1, + + // We run the CPU at the full core rate of 400MHz: + cpu_div: device::rcc::d1cfgr::D1CPRE_A::DIV1, + // We down-shift the AHB by a factor of 2, to 200MHz, to meet its + // constraints: + ahb_div: device::rcc::d1cfgr::HPRE_A::DIV2, + // We configure all APB for 100MHz. These are relative to the AHB + // frequency. + apb1_div: device::rcc::d2cfgr::D2PPRE1_A::DIV2, + apb2_div: device::rcc::d2cfgr::D2PPRE2_A::DIV2, + apb3_div: device::rcc::d1cfgr::D1PPRE_A::DIV2, + apb4_div: device::rcc::d3cfgr::D3PPRE_A::DIV2, + + // Flash runs at 200MHz: 2WS, 2 programming cycles. See reference manual + // Table 13. + flash_latency: 2, + flash_write_delay: 2, +}; + +mod shared; +use shared::{State, SHARED}; + +extern "C" { + static FLASH_BASE: [u8; 0]; + static FLASH_SIZE: [u32; 0]; +} + +#[entry] +fn main() -> ! { + // Note: The RoT does not examine results until the SP is halted. + SHARED.set_state(State::Running); + let _p = system_init(CLOCK_CONFIG); + let mut hash = Sha3_256::new(); + + // Safety: The bounds of the device's flash area are link-time constants. + let image = unsafe { + core::slice::from_raw_parts( + FLASH_BASE.as_ptr() as u32 as *const u8, + FLASH_SIZE.as_ptr() as u32 as usize, + ) + }; + + hash.update(image[..].as_ref()); + let digest: [u8; 256 / 8] = hash.finalize().into(); + SHARED.set_digest(&digest); + SHARED.set_state(State::Done); + panic!(); // Need to trap so that RoT can intercept. +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + breakpoint(); +} + +#[allow(non_snake_case)] +#[no_mangle] +/// # Safety +/// Required by the architecture and linker. +pub unsafe extern "C" fn DefaultHandler() { + breakpoint(); +} + +#[no_mangle] +extern "C" fn breakpoint() -> ! { + loop { + unsafe { + arch::asm!( + " + .globl break + bkpt + " + ); + // noreturn + } + } +} diff --git a/lib/endoscope/src/shared.rs b/lib/endoscope/src/shared.rs new file mode 100644 index 000000000..1ffc2225b --- /dev/null +++ b/lib/endoscope/src/shared.rs @@ -0,0 +1,59 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use core::cell::UnsafeCell; +use core::marker::Sync; +pub use endoscope_abi::{Shared, State}; + +#[repr(C)] +pub struct SharedWrapper { + shared: UnsafeCell, +} + +unsafe impl Sync for SharedWrapper {} + +impl SharedWrapper { + pub const fn new() -> Self { + SharedWrapper { + shared: UnsafeCell::new(Shared { + state: State::Preboot as u32, + digest: [0xff_u8; 32], + }), + } + } + + pub fn set_state(&self, state: State) { + unsafe { + (*self.shared.get()).state = state as u32; + } + } + + pub fn set_digest(&self, digest: &[u8; 32]) { + unsafe { + (*self.shared.get()).digest.copy_from_slice(digest); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_shared_wrapper() { + let shared_wrapper = SharedWrapper::new(); + shared_wrapper.set_state(42); + let digest = shared_wrapper.get_digest_mut(); + digest[0] = 1; + + unsafe { + assert_eq!((*shared_wrapper.shared.get()).state, 42); + assert_eq!((*shared_wrapper.shared.get()).digest[0], 1); + } + } +} + +// Mark as used so that symbol remains in symbol table +#[no_mangle] +pub static SHARED: SharedWrapper = SharedWrapper::new(); diff --git a/task/attest/build.rs b/task/attest/build.rs index a5c938c66..fe248bf57 100644 --- a/task/attest/build.rs +++ b/task/attest/build.rs @@ -4,12 +4,19 @@ use anyhow::{Context, Result}; use idol::{server::ServerStyle, CounterSettings}; +use serde::Deserialize; use std::{fs::File, io::Write}; mod config { include!("src/config.rs"); } +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct TaskConfig { + permit_log_reset: Vec, +} + use config::DataRegion; const CFG_SRC: &str = "attest-config.rs"; @@ -26,8 +33,8 @@ fn main() -> Result<()> { let out_dir = build_util::out_dir(); let dest_path = out_dir.join(CFG_SRC); - let mut out = - File::create(dest_path).context(format!("creating {}", CFG_SRC))?; + let mut out = File::create(dest_path) + .with_context(|| format!("creating {}", CFG_SRC))?; let data_regions = build_util::task_extern_regions::()?; if data_regions.is_empty() { @@ -60,5 +67,24 @@ pub const ALIAS_DATA: DataRegion = DataRegion {{ region.address, region.size )?; + // Get list of those tasks allowed to reset the attestation log + if let Ok(task_config) = build_util::task_config::() { + writeln!( + out, + "pub const PERMIT_LOG_RESET: [u16; {}] = [", + task_config.permit_log_reset.len() + )?; + let tasks = build_util::task_ids(); + for task_name in task_config.permit_log_reset { + let id = tasks.get(task_name.as_str()).with_context(|| { + format!("attest: allow_reset_task '{task_name}' is not present") + })?; + writeln!(out, "{id}, // Allow {task_name}")?; + } + writeln!(out, "];")?; + } else { + writeln!(out, "pub const PERMIT_LOG_RESET: [u16; 0] = [];")?; + } + Ok(()) } diff --git a/task/attest/src/main.rs b/task/attest/src/main.rs index d7546821a..722dc8df2 100644 --- a/task/attest/src/main.rs +++ b/task/attest/src/main.rs @@ -36,7 +36,7 @@ mod build { include!(concat!(env!("OUT_DIR"), "/attest-config.rs")); } -use build::{ALIAS_DATA, CERT_DATA}; +use build::{ALIAS_DATA, CERT_DATA, PERMIT_LOG_RESET}; #[derive(Copy, Clone, PartialEq)] enum Trace { @@ -49,6 +49,8 @@ enum Trace { Index(u32), Offset(u32), Startup, + ResetLog, + ResetRecord(HashAlgorithm), Record(HashAlgorithm), BadLease(usize), LogLen(u32), @@ -272,6 +274,54 @@ impl idl::InOrderAttestImpl for AttestServer { Ok(()) } + fn reset( + &mut self, + msg: &userlib::RecvMessage, + ) -> Result<(), RequestError> { + if !PERMIT_LOG_RESET.iter().any(|x| *x == msg.sender.0) { + return Err(ClientError::AccessViolation.fail()); + } + + // Reset the attestation log + ringbuf_entry!(Trace::ResetLog); + self.measurements = Log::default(); + Ok(()) + } + + fn reset_and_record( + &mut self, + msg: &userlib::RecvMessage, + algorithm: HashAlgorithm, + data: idol_runtime::Leased, + ) -> Result<(), RequestError> { + ringbuf_entry!(Trace::ResetRecord(algorithm)); + + if !PERMIT_LOG_RESET.iter().any(|x| *x == msg.sender.0) { + return Err(ClientError::AccessViolation.fail()); + } + ringbuf_entry!(Trace::ResetLog); + self.measurements = Log::default(); + + let measurement = match algorithm { + HashAlgorithm::Sha3_256 => { + if data.len() != Sha3_256Digest::LENGTH { + ringbuf_entry!(Trace::BadLease(data.len())); + return Err(AttestError::BadLease.into()); + } + + let mut digest = Sha3_256Digest::default(); + data.read_range(0..digest.0.len(), &mut digest.0) + .map_err(|_| RequestError::went_away())?; + + Measurement::Sha3_256(digest) + } + }; + + self.measurements.push(measurement); + + Ok(()) + } + fn log( &mut self, _: &userlib::RecvMessage, diff --git a/task/hiffy/src/lpc55.rs b/task/hiffy/src/lpc55.rs index 45d6e02f2..90cdb7b89 100644 --- a/task/hiffy/src/lpc55.rs +++ b/task/hiffy/src/lpc55.rs @@ -75,6 +75,8 @@ pub enum Functions { ReadFromSp((u32, u32), drv_sp_ctrl_api::SpCtrlError), #[cfg(feature = "spctrl")] SpCtrlInit((), drv_sp_ctrl_api::SpCtrlError), + #[cfg(feature = "spctrl")] + MeasureSp((), drv_sp_ctrl_api::SpCtrlError), } #[cfg(feature = "spctrl")] @@ -159,6 +161,30 @@ pub(crate) fn read_from_sp( } } +#[cfg(feature = "spctrl")] +pub(crate) fn db_reset_sp( + stack: &[Option], + _data: &[u8], + _rval: &mut [u8], +) -> Result { + if stack.is_empty() { + return Err(Failure::Fault(Fault::MissingParameters)); + } + let fp = stack.len() - 1; + let delay = match stack[fp + 0] { + Some(delay) => delay, + None => { + return Err(Failure::Fault(Fault::EmptyParameter(0))); + } + }; + + let task = SP_CTRL.get_task_id(); + let sp_ctrl = drv_sp_ctrl_api::SpCtrl::from(task); + + sp_ctrl.db_reset_sp(delay); + Ok(0) +} + #[cfg(feature = "gpio")] fn gpio_args( stack: &[Option], @@ -374,6 +400,8 @@ pub(crate) static HIFFY_FUNCS: &[Function] = &[ read_from_sp, #[cfg(feature = "spctrl")] sp_ctrl_init, + #[cfg(feature = "spctrl")] + db_reset_sp, ]; //