From a99ab2364eaee70fff76549e7f26580d32e70550 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 --- hw-model/src/lib.rs | 6 + hw-model/src/model_emulated.rs | 8 +- .../tests/rom_integration_tests/helpers.rs | 24 ++- .../tests/runtime_integration_tests/common.rs | 30 +++- sw-emulator/lib/cpu/src/cpu.rs | 156 +++++++++++++++++- sw-emulator/lib/cpu/src/lib.rs | 2 +- 6 files changed, 218 insertions(+), 8 deletions(-) diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs index 2f3fa27fa7..cd221f70ec 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 881bec4001..710aed3dbe 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/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 eb7fce430a..b3417d804b 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, lms_verify: bool) -> DefaultHwMode 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 3a54ed14c8..2306917dac 100644 --- a/sw-emulator/lib/cpu/src/cpu.rs +++ b/sw-emulator/lib/cpu/src/cpu.rs @@ -22,6 +22,124 @@ 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 stack. +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 + /// relevant 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 `true` if the stack will overflow, `false` 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, @@ -159,6 +277,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 @@ -197,9 +329,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 @@ -262,7 +399,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 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;