#![cfg(test)]
use crate::analysis::{audit, driver, engine, hardening, ir, sinks};
use crate::db::Db;
use crate::listing;
use crate::model::Binary;
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
fn byte(&mut self) -> u8 {
(self.next() >> 33) as u8
}
fn upto(&mut self, n: usize) -> usize {
if n == 0 {
0
} else {
(self.next() >> 33) as usize % n
}
}
}
fn exercise(bytes: &[u8]) {
let Ok(bin) = crate::formats::analyze("torture", bytes) else {
return; };
run_analyzers(&bin, bytes);
}
fn run_analyzers(bin: &Binary, bytes: &[u8]) {
let _ = hardening::run(bin);
let _ = sinks::cluster(&[]);
let an = engine::analyze(bin, bytes, 4_000, &Db::default());
let _ = sinks::find(&an);
let _ = audit::run(&an, bin, bytes);
let base = engine::display_base(bin);
let strings = listing::string_map(bin, bytes, base);
for f in an.functions.iter().take(6) {
let _ = listing::function(&an, f, &Db::default(), base, &strings, None);
}
for f in an.functions.iter().take(6) {
let _ = ir::decompile(&an, bin, f, &strings, &Db::default());
}
let _ = driver::report(bin, bytes, &an, &strings);
for a in [bin.entry, 0, u64::MAX, bin.image_base] {
let _ = listing::data_view(bin, base, bytes, a);
}
}
#[test]
fn random_buffers_never_panic() {
let mut rng = Lcg(0x1234_5678_9abc_def0);
for _ in 0..2000 {
let len = rng.upto(512);
let buf: Vec<u8> = (0..len).map(|_| rng.byte()).collect();
exercise(&buf);
}
}
#[test]
fn tiny_buffers_never_panic() {
for len in 0..80usize {
exercise(&vec![0u8; len]);
exercise(&vec![0xffu8; len]);
}
}
#[test]
fn plausible_headers_over_garbage_never_panic() {
let magics: &[&[u8]] = &[
b"MZ",
b"\x7fELF",
&[0xfe, 0xed, 0xfa, 0xce], &[0xfe, 0xed, 0xfa, 0xcf], &[0xcf, 0xfa, 0xed, 0xfe], &[0xca, 0xfe, 0xba, 0xbe], b"!<arch>\n",
];
let mut rng = Lcg(0xdead_beef_cafe_f00d);
for m in magics {
for _ in 0..200 {
let len = rng.upto(1024);
let mut buf = m.to_vec();
buf.extend((0..len).map(|_| rng.byte()));
exercise(&buf);
}
}
}
fn exercise_parse(bytes: &[u8]) {
let _ = crate::formats::analyze("torture", bytes);
}
const STRUCTURED_PREFIX: usize = 4096;
const TAIL_STRIDE: usize = 16;
fn deep(off: usize) -> bool {
off < STRUCTURED_PREFIX || off.is_multiple_of(TAIL_STRIDE)
}
fn truncations(good: &[u8]) {
for n in 0..good.len() {
if deep(n) {
exercise(&good[..n]);
} else {
exercise_parse(&good[..n]);
}
}
}
fn bit_flips(good: &[u8], seed: u64) {
let mut rng = Lcg(seed);
let mut buf = good.to_vec();
for off in 0..good.len() {
let original = buf[off];
buf[off] = original ^ (1 << rng.upto(8));
if deep(off) {
exercise(&buf);
} else {
exercise_parse(&buf);
}
buf[off] = 0xff;
if deep(off) {
exercise(&buf);
} else {
exercise_parse(&buf);
}
buf[off] = original;
}
}
macro_rules! mutation_walks {
($($fixture:ident => $seed:expr),* $(,)?) => {
$(
mod $fixture {
use super::*;
#[test]
fn truncations_never_panic() {
truncations(&crate::formats::fixture::$fixture());
}
#[test]
fn bit_flips_never_panic() {
bit_flips(&crate::formats::fixture::$fixture(), $seed);
}
}
)*
};
}
mutation_walks! {
elf_with_plt_call => 0x0badf00d_12345678,
elf_aarch64_call => 0x0badf00d_23456781,
elf_aarch64_plt_call => 0x0badf00d_34567812,
elf_with_eh_frame_hdr => 0x0badf00d_45678123,
pe_with_iat_call => 0x0badf00d_56781234,
pe_with_driver => 0x0badf00d_67812345,
macho_with_bind => 0x0badf00d_78123456,
}
#[test]
fn signing_walkers_never_panic_on_junk() {
use crate::analysis::signing;
use crate::model::{Arch, Format};
let mut rng = Lcg(0xdecafbad_01234567);
for _ in 0..400 {
let len = rng.upto(1536);
let buf: Vec<u8> = (0..len).map(|_| rng.byte()).collect();
let mut sbin = Binary::stub(Format::Pe, Arch::X86_64);
sbin.sig_region = Some((0, len as u64));
let _ = signing::summarize(&sbin, &buf);
if len >= 8 {
sbin.sig_region = Some(((len / 2) as u64, 4096));
let _ = signing::summarize(&sbin, &buf);
}
sbin.sig_region = Some((len as u64 + 64, 64));
let _ = signing::summarize(&sbin, &buf);
}
}