Skip to content

Commit

Permalink
Added stack monitoring and overflow detection to the SW emulator
Browse files Browse the repository at this point in the history
(cherry picked from commit 2f8f19b)
  • Loading branch information
clundin25 authored and jhand2 committed Dec 17, 2024
1 parent c334dc6 commit 639941f
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 11 deletions.
2 changes: 1 addition & 1 deletion drivers/src/memory_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions hw-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<PathBuf>,

// Information about the stack Caliptra is using. When set the emulator will check if the stack
// overflows.
pub stack_info: Option<StackInfo>,
}
impl<'a> Default for InitParams<'a> {
fn default() -> Self {
Expand Down Expand Up @@ -210,6 +215,7 @@ impl<'a> Default for InitParams<'a> {
}),
random_sram_puf: true,
trace_path: None,
stack_info: None,
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion hw-model/src/model_emulated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions rom/dev/src/rom.ld
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down
24 changes: 23 additions & 1 deletion rom/dev/tests/rom_integration_tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 {
Expand Down
30 changes: 26 additions & 4 deletions runtime/tests/runtime_integration_tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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()
},
};
Expand Down
158 changes: 157 additions & 1 deletion sw-emulator/lib/cpu/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32> {
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<ImageInfo>,
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<ImageInfo>) -> 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<u32> {
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<u32> {
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,
Expand Down Expand Up @@ -162,6 +282,20 @@ pub struct Cpu<TBus: Bus> {
pub(crate) watch_ptr_cfg: WatchPtrCfg,

pub code_coverage: CodeCoverage,
stack_info: Option<StackInfo>,
}

impl<TBus: Bus> Drop for Cpu<TBus> {
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
Expand Down Expand Up @@ -201,9 +335,14 @@ impl<TBus: Bus> Cpu<TBus> {
// 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
Expand Down Expand Up @@ -266,7 +405,24 @@ impl<TBus: Bus> Cpu<TBus> {
///
/// * `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
Expand Down
2 changes: 1 addition & 1 deletion sw-emulator/lib/cpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit 639941f

Please sign in to comment.