use std::time::Instant;
use probe_rs::{Core, MemoryInterface, Session};
use crate::{registers::PC, Elf, TargetInfo, TIMEOUT};
const CANARY_VALUE: u8 = 0xAA;
#[derive(Clone, Copy)]
pub(crate) struct Canary {
address: u32,
size: usize,
stack_available: u32,
data_below_stack: bool,
measure_stack: bool,
}
impl Canary {
pub(crate) 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 {
1024.min(stack_available / 10) as usize
};
log::debug!(
"{} bytes of stack available ({:#010X} ..= {:#010X}), using {} byte canary",
stack_available,
stack_info.range.start(),
stack_info.range.end(),
size,
);
let size_kb = size as f64 / 1024.0;
if measure_stack {
log::info!(
"painting {:.2} KiB of RAM for stack usage estimation",
size_kb
);
}
let start = Instant::now();
paint_stack(&mut core, stack_start, stack_start + size as u32)?;
let seconds = start.elapsed().as_secs_f64();
log::trace!(
"setting up canary took {:.3}s ({:.2} KiB/s)",
seconds,
size_kb / seconds
);
Ok(Some(Canary {
address: stack_start,
size,
stack_available,
data_below_stack: stack_info.data_below_stack,
measure_stack,
}))
}
pub(crate) 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 {:.2} KiB of RAM for stack usage estimation",
size_kb,
);
}
let mut canary = vec![0; self.size];
let start = Instant::now();
core.read_8(self.address, &mut canary)?;
let seconds = start.elapsed().as_secs_f64();
log::trace!(
"reading canary took {:.3}s ({:.2} KiB/s)",
seconds,
size_kb / seconds
);
let min_stack_usage = match canary.iter().position(|b| *b != CANARY_VALUE) {
Some(pos) => {
let touched_address = self.address + pos as u32;
log::debug!("canary was touched at {:#010X}", touched_address);
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 {:.2}/{:.2} KiB ({:.1}%) of stack space",
used_kb,
avail_kb,
pct,
);
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 {:.2}/{:.2} KiB ({:.1}%) of stack space",
used_kb,
avail_kb,
pct,
);
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 paint_stack(core: &mut Core, start: u32, end: u32) -> Result<(), probe_rs::Error> {
assert!(start < end, "start needs to be smaller than end address");
assert_eq!(start % 4, 0, "`start` needs to be 4-byte-aligned");
assert_eq!(end % 4, 0, "`end` needs to be 4-byte-aligned");
let stack_size = (end - start) as usize;
assert!(
SUBROUTINE_LENGTH < stack_size,
"subroutine doesn't fit inside stack"
);
core.write_8(start, &subroutine(start + SUBROUTINE_LENGTH as u32, end))?;
let previous_pc = core.read_core_reg(PC)?;
core.write_core_reg(PC, start)?;
core.run()?;
core.wait_for_core_halted(TIMEOUT)?;
core.write_8(start, &[CANARY_VALUE; SUBROUTINE_LENGTH])?;
core.write_core_reg(PC, previous_pc)?;
Ok(())
}
const SUBROUTINE_LENGTH: usize = 28;
fn subroutine(start: u32, end: u32) -> [u8; SUBROUTINE_LENGTH] {
let [s1, s2, s3, s4] = start.to_le_bytes();
let [e1, e2, e3, e4] = end.to_le_bytes();
const CV: u8 = CANARY_VALUE;
[
0x03, 0x48, 0x04, 0x49, 0x04, 0x4a, 0x81, 0x42, 0x01, 0xD0, 0x04, 0xC0, 0xFB, 0xE7, 0x00, 0xBE, s1, s2, s3, s4, e1, e2, e3, e4, CV, CV, CV, CV, ]
}