use std::time::Instant;
use probe_rs::{Core, MemoryInterface, RegisterId, Session};
use crate::{registers::PC, Elf, TargetInfo, TIMEOUT};
const CANARY_U8: u8 = 0xAA;
const CANARY_U32: u32 = u32::from_le_bytes([CANARY_U8, CANARY_U8, CANARY_U8, CANARY_U8]);
#[derive(Clone, Copy)]
pub struct Canary {
address: u32,
size: usize,
stack_available: u32,
data_below_stack: bool,
measure_stack: bool,
}
impl Canary {
pub fn install(
sess: &mut Session,
target_info: &TargetInfo,
elf: &Elf,
measure_stack: bool,
) -> Result<Option<Self>, anyhow::Error> {
let mut core = sess.core(0)?;
core.reset_and_halt(TIMEOUT)?;
let stack_info = match &target_info.stack_info {
Some(stack_info) => stack_info,
None => {
log::debug!("couldn't find valid stack range, not placing stack canary");
return Ok(None);
}
};
if elf.program_uses_heap() {
log::debug!("heap in use, not placing stack canary");
return Ok(None);
}
let stack_start = *stack_info.range.start();
let stack_available = *stack_info.range.end() - stack_start;
let size = if measure_stack {
stack_available as usize
} else {
round_up(1024.min(stack_available / 10), 4) as usize
};
log::debug!(
"{stack_available} bytes of stack available ({:#010X} ..= {:#010X}), using {size} byte canary",
stack_info.range.start(),
stack_info.range.end(),
);
let size_kb = size as f64 / 1024.0;
if measure_stack {
log::info!("painting {size_kb:.2} KiB of RAM for stack usage estimation");
}
let start = Instant::now();
paint_subroutine::execute(&mut core, stack_start, size as u32)?;
let seconds = start.elapsed().as_secs_f64();
log::trace!(
"setting up canary took {seconds:.3}s ({:.2} KiB/s)",
size_kb / seconds
);
Ok(Some(Canary {
address: stack_start,
size,
stack_available,
data_below_stack: stack_info.data_below_stack,
measure_stack,
}))
}
pub fn touched(self, core: &mut probe_rs::Core, elf: &Elf) -> anyhow::Result<bool> {
let size_kb = self.size as f64 / 1024.0;
if self.measure_stack {
log::info!("reading {size_kb:.2} KiB of RAM for stack usage estimation");
}
let start = Instant::now();
let touched_address = measure_subroutine::execute(core, self.address, self.size as u32)?;
let seconds = start.elapsed().as_secs_f64();
log::trace!(
"reading canary took {seconds:.3}s ({:.2} KiB/s)",
size_kb / seconds
);
let min_stack_usage = match touched_address {
Some(touched_address) => {
log::debug!("canary was touched at {touched_address:#010X}");
Some(elf.vector_table.initial_stack_pointer - touched_address)
}
None => None,
};
if self.measure_stack {
let min_stack_usage = min_stack_usage.unwrap_or(0);
let used_kb = min_stack_usage as f64 / 1024.0;
let avail_kb = self.stack_available as f64 / 1024.0;
let pct = used_kb / avail_kb * 100.0;
log::info!(
"program has used at least {used_kb:.2}/{avail_kb:.2} KiB ({pct:.1}%) of stack space"
);
Ok(false)
} else {
match min_stack_usage {
Some(min_stack_usage) => {
let used_kb = min_stack_usage as f64 / 1024.0;
let avail_kb = self.stack_available as f64 / 1024.0;
let pct = used_kb / avail_kb * 100.0;
log::warn!(
"program has used at least {used_kb:.2}/{avail_kb:.2} KiB ({pct:.1}%) of stack space",
);
if self.data_below_stack {
log::warn!("data segments might be corrupted due to stack overflow");
}
Ok(true)
}
None => {
log::debug!("stack canary intact");
Ok(false)
}
}
}
}
}
fn round_up(n: u32, k: u32) -> u32 {
match n % k {
0 => n,
rem => n + k - rem,
}
}
macro_rules! assert_subroutine {
($low_addr:expr, $stack_size:expr, $subroutine_size:expr) => {
assert_eq!($low_addr % 4, 0, "low_addr needs to be 4-byte-aligned");
assert_eq!($stack_size % 4, 0, "stack_size needs to be 4-byte-aligned");
assert_eq!(
$subroutine_size % 4,
0,
"subroutine needs to be 4-byte-aligned"
);
assert!(
$subroutine_size < $stack_size,
"subroutine does not fit inside stack"
);
};
}
mod paint_subroutine {
use super::*;
pub fn execute(core: &mut Core, low_addr: u32, stack_size: u32) -> Result<(), probe_rs::Error> {
assert_subroutine!(low_addr, stack_size, self::SUBROUTINE.len() as u32);
super::execute_subroutine(core, low_addr, stack_size, self::SUBROUTINE)?;
self::overwrite_subroutine(core, low_addr)?;
Ok(())
}
fn overwrite_subroutine(core: &mut Core, low_addr: u32) -> Result<(), probe_rs::Error> {
core.write_8(low_addr as u64, &[CANARY_U8; self::SUBROUTINE.len()])
}
const SUBROUTINE: [u8; 12] = [
0x88, 0x42, 0x01, 0xd8, 0x04, 0xc0, 0xfb, 0xe7, 0x00, 0xbe, 0x00, 0xbe, ];
}
mod measure_subroutine {
use super::*;
pub fn execute(
core: &mut Core,
low_addr: u32,
stack_size: u32,
) -> Result<Option<u32>, probe_rs::Error> {
assert_subroutine!(low_addr, stack_size, self::SUBROUTINE.len() as u32);
match self::search_with_probe(core, low_addr)? {
addr @ Some(_) => return Ok(addr), None => {} }
super::execute_subroutine(core, low_addr, stack_size, self::SUBROUTINE)?;
self::get_result(core)
}
fn search_with_probe(core: &mut Core, low_addr: u32) -> Result<Option<u32>, probe_rs::Error> {
let mut buf = [0; self::SUBROUTINE.len()];
core.read_8(low_addr as u64, &mut buf)?;
match buf.into_iter().position(|b| b != CANARY_U8) {
Some(pos) => Ok(Some(low_addr + pos as u32)),
None => Ok(None),
}
}
fn get_result(core: &mut Core) -> Result<Option<u32>, probe_rs::Error> {
let word_addr = match core.read_core_reg(RegisterId(0))? {
0 => return Ok(None),
n => n,
};
let offset = core
.read_word_32(word_addr as u64)?
.to_le_bytes()
.into_iter()
.position(|b| b != CANARY_U8)
.unwrap();
Ok(Some(word_addr + offset as u32))
}
const SUBROUTINE: [u8; 20] = [
0x88, 0x42, 0x04, 0xd2, 0x03, 0x68, 0x93, 0x42, 0x02, 0xd1, 0x00, 0x1d, 0xf8, 0xe7, 0x00, 0x20, 0x00, 0xbe, 0x00, 0xbe, ];
}
fn execute_subroutine<const N: usize>(
core: &mut Core,
low_addr: u32,
stack_size: u32,
subroutine: [u8; N],
) -> Result<(), probe_rs::Error> {
let subroutine_size = N as u32;
let high_addr = low_addr + stack_size;
core.write_core_reg(RegisterId(0), low_addr + subroutine_size)?;
core.write_core_reg(RegisterId(1), high_addr)?;
core.write_core_reg(RegisterId(2), CANARY_U32)?;
core.write_8(low_addr as u64, &subroutine)?;
let previous_pc = core.read_core_reg(PC)?;
core.write_core_reg(PC, low_addr)?;
core.run()?;
core.wait_for_core_halted(TIMEOUT)?;
core.write_core_reg::<u32>(PC, previous_pc)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(2, 4, 4)]
#[case(4, 4, 4)]
#[case(6, 4, 8)]
#[case(8, 4, 8)]
#[case::odd(5, 3, 6)]
#[should_panic]
#[case::div_zero(4, 0, 0)]
fn test_round_up(#[case] n: u32, #[case] k: u32, #[case] res: u32) {
assert_eq!(round_up(n, k), res);
}
}