From 639941fa2ac077472184f21b975f166d98f6190c Mon Sep 17 00:00:00 2001 From: Carl Lundin Date: Fri, 1 Nov 2024 11:16:41 -0700 Subject: [PATCH] Added stack monitoring and overflow detection to the SW emulator (cherry picked from commit 2f8f19be70f2971af818b8d35b74c878212aff72) --- drivers/src/memory_layout.rs | 2 +- hw-model/src/lib.rs | 6 + hw-model/src/model_emulated.rs | 8 +- rom/dev/src/rom.ld | 4 +- .../tests/rom_integration_tests/helpers.rs | 24 ++- .../tests/runtime_integration_tests/common.rs | 30 +++- sw-emulator/lib/cpu/src/cpu.rs | 158 +++++++++++++++++- sw-emulator/lib/cpu/src/lib.rs | 2 +- 8 files changed, 223 insertions(+), 11 deletions(-) diff --git a/drivers/src/memory_layout.rs b/drivers/src/memory_layout.rs index 34f810c5af..70b9b9774a 100644 --- a/drivers/src/memory_layout.rs +++ b/drivers/src/memory_layout.rs @@ -81,7 +81,7 @@ pub const AUTH_MAN_IMAGE_METADATA_MAX_SIZE: u32 = 7 * 1024; pub const IDEVID_CSR_SIZE: u32 = 1024; pub const DATA_SIZE: u32 = 130 * 1024; pub const STACK_SIZE: u32 = 64 * 1024; -pub const ROM_STACK_SIZE: u32 = 14 * 1024; +pub const ROM_STACK_SIZE: u32 = 40 * 1024; pub const ESTACK_SIZE: u32 = 1024; pub const ROM_ESTACK_SIZE: u32 = 1024; pub const NSTACK_SIZE: u32 = 1024; diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs index ee642945d0..7467662b42 100644 --- a/hw-model/src/lib.rs +++ b/hw-model/src/lib.rs @@ -45,6 +45,7 @@ mod rv32_builder; pub use api::mailbox::mbox_write_fifo; pub use api_types::{DeviceLifecycle, Fuses, SecurityState, U4}; pub use caliptra_emu_bus::BusMmio; +pub use caliptra_emu_cpu::{CodeRange, ImageInfo, StackInfo, StackRange}; use output::ExitStatus; pub use output::Output; @@ -176,6 +177,10 @@ pub struct InitParams<'a> { // A trace path to use. If None, the CPTRA_TRACE_PATH environment variable // will be used pub trace_path: Option, + + // Information about the stack Caliptra is using. When set the emulator will check if the stack + // overflows. + pub stack_info: Option, } impl<'a> Default for InitParams<'a> { fn default() -> Self { @@ -210,6 +215,7 @@ impl<'a> Default for InitParams<'a> { }), random_sram_puf: true, trace_path: None, + stack_info: None, } } } diff --git a/hw-model/src/model_emulated.rs b/hw-model/src/model_emulated.rs index c2515ee735..f94c22e4f4 100644 --- a/hw-model/src/model_emulated.rs +++ b/hw-model/src/model_emulated.rs @@ -187,7 +187,13 @@ impl HwModel for ModelEmulated { dccm_dest.copy_from_slice(params.dccm); } let soc_to_caliptra_bus = root_bus.soc_to_caliptra_bus(); - let cpu = Cpu::new(BusLogger::new(root_bus), clock); + let cpu = { + let mut cpu = Cpu::new(BusLogger::new(root_bus), clock); + if let Some(stack_info) = params.stack_info { + cpu.with_stack_info(stack_info); + } + cpu + }; let mut hasher = DefaultHasher::new(); std::hash::Hash::hash_slice(params.rom, &mut hasher); diff --git a/rom/dev/src/rom.ld b/rom/dev/src/rom.ld index 1e7fc8d241..ddda7b425e 100644 --- a/rom/dev/src/rom.ld +++ b/rom/dev/src/rom.ld @@ -20,7 +20,7 @@ ROM_ORG = 0x00000000; ICCM_ORG = 0x40000000; DCCM_ORG = 0x50000000; DATA_ORG = 0x50000000; -STACK_ORG = 0x5003C000; +STACK_ORG = 0x50035800; ESTACK_ORG = 0x5003F800; NSTACK_ORG = 0x5003FC00; @@ -36,7 +36,7 @@ ROM_SIZE = 96K; ICCM_SIZE = 128K; DCCM_SIZE = 256K; DATA_SIZE = 996; -STACK_SIZE = 14K; +STACK_SIZE = 40K; ESTACK_SIZE = 1K; NSTACK_SIZE = 1K; diff --git a/rom/dev/tests/rom_integration_tests/helpers.rs b/rom/dev/tests/rom_integration_tests/helpers.rs index f960726370..998dca3853 100644 --- a/rom/dev/tests/rom_integration_tests/helpers.rs +++ b/rom/dev/tests/rom_integration_tests/helpers.rs @@ -4,7 +4,14 @@ use std::mem; use caliptra_api::SocManager; use caliptra_builder::{firmware, ImageOptions}; -use caliptra_hw_model::{BootParams, Fuses, HwModel, InitParams, SecurityState}; +use caliptra_common::{ + memory_layout::{ROM_ORG, ROM_SIZE, ROM_STACK_ORG, ROM_STACK_SIZE, STACK_ORG, STACK_SIZE}, + FMC_ORG, FMC_SIZE, RUNTIME_ORG, RUNTIME_SIZE, +}; +use caliptra_hw_model::{ + BootParams, CodeRange, Fuses, HwModel, ImageInfo, InitParams, SecurityState, StackInfo, + StackRange, +}; use caliptra_hw_model::{DefaultHwModel, ModelError}; use caliptra_image_types::ImageBundle; @@ -18,10 +25,25 @@ pub fn build_hw_model_and_image_bundle( pub fn build_hw_model(fuses: Fuses) -> DefaultHwModel { let rom = caliptra_builder::build_firmware_rom(firmware::rom_from_env()).unwrap(); + let image_info = vec![ + ImageInfo::new( + StackRange::new(ROM_STACK_ORG + ROM_STACK_SIZE, ROM_STACK_ORG), + CodeRange::new(ROM_ORG, ROM_ORG + ROM_SIZE), + ), + ImageInfo::new( + StackRange::new(STACK_ORG + STACK_SIZE, STACK_ORG), + CodeRange::new(FMC_ORG, FMC_ORG + FMC_SIZE), + ), + ImageInfo::new( + StackRange::new(STACK_ORG + STACK_SIZE, STACK_ORG), + CodeRange::new(RUNTIME_ORG, RUNTIME_ORG + RUNTIME_SIZE), + ), + ]; caliptra_hw_model::new( InitParams { rom: &rom, security_state: SecurityState::from(fuses.life_cycle as u32), + stack_info: Some(StackInfo::new(image_info)), ..Default::default() }, BootParams { diff --git a/runtime/tests/runtime_integration_tests/common.rs b/runtime/tests/runtime_integration_tests/common.rs index 2f10a2c900..399542e723 100644 --- a/runtime/tests/runtime_integration_tests/common.rs +++ b/runtime/tests/runtime_integration_tests/common.rs @@ -5,13 +5,20 @@ use caliptra_builder::{ firmware::{APP_WITH_UART, APP_WITH_UART_FPGA, FMC_WITH_UART}, FwId, ImageOptions, }; -use caliptra_common::mailbox_api::{ - CommandId, GetFmcAliasCertResp, GetRtAliasCertResp, InvokeDpeReq, InvokeDpeResp, MailboxReq, - MailboxReqHeader, +use caliptra_common::{ + mailbox_api::{ + CommandId, GetFmcAliasCertResp, GetRtAliasCertResp, InvokeDpeReq, InvokeDpeResp, + MailboxReq, MailboxReqHeader, + }, + memory_layout::{ROM_ORG, ROM_SIZE, ROM_STACK_ORG, ROM_STACK_SIZE, STACK_ORG, STACK_SIZE}, + FMC_ORG, FMC_SIZE, RUNTIME_ORG, RUNTIME_SIZE, }; use caliptra_drivers::MfgFlags; use caliptra_error::CaliptraError; -use caliptra_hw_model::{BootParams, DefaultHwModel, Fuses, HwModel, InitParams, ModelError}; +use caliptra_hw_model::{ + BootParams, CodeRange, DefaultHwModel, Fuses, HwModel, ImageInfo, InitParams, ModelError, + StackInfo, StackRange, +}; use dpe::{ commands::{Command, CommandHdr}, response::{ @@ -65,11 +72,26 @@ pub fn run_rt_test_lms(args: RuntimeTestArgs) -> DefaultHwModel { opts }); + let image_info = vec![ + ImageInfo::new( + StackRange::new(ROM_STACK_ORG + ROM_STACK_SIZE, ROM_STACK_ORG), + CodeRange::new(ROM_ORG, ROM_ORG + ROM_SIZE), + ), + ImageInfo::new( + StackRange::new(STACK_ORG + STACK_SIZE, STACK_ORG), + CodeRange::new(FMC_ORG, FMC_ORG + FMC_SIZE), + ), + ImageInfo::new( + StackRange::new(STACK_ORG + STACK_SIZE, STACK_ORG), + CodeRange::new(RUNTIME_ORG, RUNTIME_ORG + RUNTIME_SIZE), + ), + ]; let rom = caliptra_builder::rom_for_fw_integration_tests().unwrap(); let init_params = match args.init_params { Some(init_params) => init_params, None => InitParams { rom: &rom, + stack_info: Some(StackInfo::new(image_info)), ..Default::default() }, }; diff --git a/sw-emulator/lib/cpu/src/cpu.rs b/sw-emulator/lib/cpu/src/cpu.rs index 78c1680f99..9e032e7595 100644 --- a/sw-emulator/lib/cpu/src/cpu.rs +++ b/sw-emulator/lib/cpu/src/cpu.rs @@ -22,6 +22,126 @@ use caliptra_emu_types::{RvAddr, RvData, RvException, RvSize}; pub type InstrTracer<'a> = dyn FnMut(u32, RvInstr) + 'a; +/// Describes a Caliptra stack memory region +pub struct StackRange(u32, u32); +impl StackRange { + /// **Note:** `stack_start` MUST be greater than `stack_end`. Caliptra's stack grows + /// to a lower address. + pub fn new(stack_start: u32, stack_end: u32) -> Self { + if stack_start < stack_end { + panic!("Caliptra's stack grows to a lower address"); + } + Self(stack_start, stack_end) + } +} + +/// Describes a Caliptra code region +pub struct CodeRange(u32, u32); +impl CodeRange { + pub fn new(code_start: u32, code_end: u32) -> Self { + if code_start > code_end { + panic!("code grows to a higher address"); + } + Self(code_start, code_end) + } +} + +/// Contains metadata describing a Caliptra image +pub struct ImageInfo { + stack_range: StackRange, + code_range: CodeRange, +} + +impl ImageInfo { + pub fn new(stack_range: StackRange, code_range: CodeRange) -> Self { + Self { + stack_range, + code_range, + } + } + + /// Checks if the program counter is contained in `self` + /// + /// returns `true` if the pc is in the image. `false` otherwise. + fn contains_pc(&self, pc: u32) -> bool { + self.code_range.0 <= pc && pc <= self.code_range.1 + } + + /// Checks if the stack pointer has overflowed. + /// + /// Returns `Some(u32)` if an overflow has occurred. The `u32` is set + /// to the overflow amount. + /// + /// Returns `None` if no overflow has occurred. + fn check_overflow(&self, sp: u32) -> Option { + let stack_end = self.stack_range.1; + + // Stack grows to a lower address + if sp < stack_end { + let current_overflow = stack_end - sp; + Some(current_overflow) + } else { + None + } + } +} + +/// Describes the shape of Caliptra's stacks. +/// +/// Used to monitor stack usage and check for overflows. +pub struct StackInfo { + images: Vec, + max_stack_overflow: u32, + has_overflowed: bool, +} + +impl StackInfo { + /// Create a new `StackInfo` by describing the start and end of the stack and code segment for each + /// Caliptra image. + pub fn new(images: Vec) -> Self { + Self { + images, + max_stack_overflow: 0, + has_overflowed: false, + } + } +} + +impl StackInfo { + /// Fetch the largest stack overflow. + /// + /// If the stack never overflowed, returns `None`. + fn max_stack_overflow(&self) -> Option { + if self.has_overflowed { + Some(self.max_stack_overflow) + } else { + None + } + } + + /// Checks if the stack will overflow when pushed to `stack_address`. + /// + /// Returns `Some(u32)` if the stack will overflow and by how much, `None` if it will not overflow. + fn check_overflow(&mut self, pc: u32, stack_address: u32) -> Option { + if stack_address == 0 { + // sp is initialized to 0. + return None; + } + + for image in self.images.iter() { + if image.contains_pc(pc) { + if let Some(overflow_amount) = image.check_overflow(stack_address) { + self.max_stack_overflow = self.max_stack_overflow.max(overflow_amount); + self.has_overflowed = true; + return Some(overflow_amount); + } + } + } + + None + } +} + #[derive(Clone)] pub struct CodeCoverage { rom_bit_vec: BitVec, @@ -162,6 +282,20 @@ pub struct Cpu { pub(crate) watch_ptr_cfg: WatchPtrCfg, pub code_coverage: CodeCoverage, + stack_info: Option, +} + +impl Drop for Cpu { + fn drop(&mut self) { + if let Some(stack_info) = &self.stack_info { + if stack_info.has_overflowed { + panic!( + "[EMU] Fatal: Caliptra's stack overflowed by {} bytes!", + stack_info.max_stack_overflow().unwrap() + ) + } + } + } } /// Cpu instruction step action @@ -201,9 +335,14 @@ impl Cpu { // TODO: Pass in code_coverage from the outside (as caliptra-emu-cpu // isn't supposed to know anything about the caliptra memory map) code_coverage: CodeCoverage::new(ROM_SIZE, ICCM_SIZE), + stack_info: None, } } + pub fn with_stack_info(&mut self, stack_info: StackInfo) { + self.stack_info = Some(stack_info); + } + /// Read the RISCV CPU Program counter /// /// # Return @@ -266,7 +405,24 @@ impl Cpu { /// /// * `RvException` - Exception with cause `RvExceptionCause::IllegalRegister` pub fn write_xreg(&mut self, reg: XReg, val: RvData) -> Result<(), RvException> { - self.xregs.write(reg, val) + // XReg::X2 is the sp register. + if reg == XReg::X2 { + self.check_stack(val); + } + self.xregs.write(reg, val)?; + Ok(()) + } + + // Check if the stack overflows at the requested address. + fn check_stack(&mut self, val: RvData) { + if let Some(stack_info) = &mut self.stack_info { + if let Some(overflow_amount) = stack_info.check_overflow(self.pc, val) { + eprintln!( + "[EMU] Caliptra's stack overflowed by {} bytes at pc 0x{:x}.", + overflow_amount, self.pc + ); + } + } } /// Read the specified configuration status register with the current privilege mode diff --git a/sw-emulator/lib/cpu/src/lib.rs b/sw-emulator/lib/cpu/src/lib.rs index 5d0a73f57e..87c88d75f2 100644 --- a/sw-emulator/lib/cpu/src/lib.rs +++ b/sw-emulator/lib/cpu/src/lib.rs @@ -22,7 +22,7 @@ pub mod xreg_file; pub use cpu::StepAction; pub use cpu::WatchPtrHit; pub use cpu::WatchPtrKind; -pub use cpu::{CoverageBitmaps, Cpu, InstrTracer}; +pub use cpu::{CodeRange, CoverageBitmaps, Cpu, ImageInfo, InstrTracer, StackInfo, StackRange}; pub use csr_file::CsrFile; pub use pic::{IntSource, Irq, Pic, PicMmioRegisters}; pub use types::RvInstr;