use std::path::Path;
use std::process::Command;
const RSLEIGH_BIN: &str = env!("CARGO_BIN_EXE_rsleigh");
fn fixture(name: &str) -> Option<String> {
for rel in [
format!("test-harness/fixtures/smt/bin/{name}"),
format!("../test-harness/fixtures/smt/bin/{name}"),
] {
if Path::new(&rel).exists() {
return Some(rel);
}
}
eprintln!("[skip] {name} fixture missing — run test-harness/fixtures/smt/build.sh");
None
}
fn run_explore(bin: &str, func: &str) -> String {
let out = Command::new(RSLEIGH_BIN)
.args([bin, "--smt-explore", func])
.output()
.expect("rsleigh invocation");
assert!(
out.status.success(),
"rsleigh failed:\n{}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).expect("utf-8")
}
fn assert_reachable_or_skip_no_smt(stdout: &str, label: &str) {
if stdout.contains("smt feature not enabled at build time") {
eprintln!("[skip-no-smt] {label}: rebuild with --features smt to exercise solver");
return;
}
assert!(
stdout.contains("REACHABLE"),
"{label} did not produce REACHABLE verdict:\n{stdout}"
);
}
#[test]
fn recv_strcpy_stack_buffer_reachable() {
let Some(bin) = fixture("recv_strcpy") else { return };
let out = run_explore(&bin, "vuln_recv_strcpy");
assert!(
out.contains("recv -> strcpy"),
"missing recv->strcpy path label:\n{out}"
);
assert!(out.contains("StackBuffer"), "missing StackBuffer kind:\n{out}");
assert_reachable_or_skip_no_smt(&out, "recv_strcpy");
}
#[test]
fn read_system_command_injection_reachable() {
let Some(bin) = fixture("read_system") else { return };
let out = run_explore(&bin, "vuln_read_system");
assert!(out.contains("read -> system"), "missing read->system path:\n{out}");
assert!(out.contains("Command"), "missing Command kind:\n{out}");
assert_reachable_or_skip_no_smt(&out, "read_system");
}
#[test]
fn heartbleed_shape_lengtharg_reachable() {
let Some(bin) = fixture("heartbleed_shape") else { return };
let out = run_explore(&bin, "vuln_heartbleed");
assert!(out.contains("recv -> memcpy"), "missing recv->memcpy path:\n{out}");
assert!(out.contains("LengthArg"), "missing LengthArg kind:\n{out}");
assert_reachable_or_skip_no_smt(&out, "heartbleed_shape");
}
#[test]
fn inter_proc_heartbleed_lengtharg_reachable() {
let Some(bin) = fixture("inter_proc_heartbleed") else { return };
let out = Command::new(RSLEIGH_BIN)
.args([&bin, "--smt-explore", "handler", "--smt-summaries"])
.output()
.expect("rsleigh invocation");
let stdout = String::from_utf8(out.stdout).expect("utf-8");
if stdout.contains("smt feature not enabled at build time") {
eprintln!("[skip-no-smt] inter_proc_heartbleed");
return;
}
assert!(stdout.contains("recv -> memcpy"),
"missing recv->memcpy path:\n{stdout}");
assert!(stdout.contains("LengthArg"), "missing LengthArg kind:\n{stdout}");
assert!(stdout.contains("REACHABLE"),
"inter-proc Heartbleed should be REACHABLE via summary lift:\n{stdout}");
}
#[test]
fn tainted_store_loop_reachable_via_summary() {
let Some(bin) = fixture("tainted_store_loop") else { return };
let out = Command::new(RSLEIGH_BIN)
.args([&bin, "--smt-explore", "vuln_loop_store", "--smt-summaries"])
.output()
.expect("rsleigh invocation");
let stdout = String::from_utf8(out.stdout).expect("utf-8");
if stdout.contains("smt feature not enabled at build time") {
eprintln!("[skip-no-smt] tainted_store_loop");
return;
}
assert!(
stdout.contains("TaintedStore"),
"missing TaintedStore kind:\n{stdout}"
);
assert!(
stdout.contains("recv -> <tainted_store>"),
"missing recv->store path label:\n{stdout}"
);
assert!(
stdout.contains("REACHABLE"),
"v10 should produce REACHABLE for tainted-store loop:\n{stdout}"
);
}
#[test]
fn bounded_loop_not_reachable_v11a() {
let Some(bin) = fixture("bounded_loop") else { return };
let out = Command::new(RSLEIGH_BIN)
.args([&bin, "--smt-explore", "safe_handler", "--smt-summaries"])
.output()
.expect("rsleigh invocation");
let stdout = String::from_utf8(out.stdout).expect("utf-8");
if stdout.contains("smt feature not enabled at build time") {
eprintln!("[skip-no-smt] bounded_loop");
return;
}
assert!(
!stdout.contains("REACHABLE"),
"bounded_loop should NOT be REACHABLE under v11.A null-term filter:\n{stdout}"
);
}
#[test]
fn fgets_printf_format_string_reachable() {
let Some(bin) = fixture("fgets_printf") else { return };
let out = run_explore(&bin, "vuln_fgets_printf");
assert!(out.contains("fgets -> printf"), "missing fgets->printf path:\n{out}");
assert!(out.contains("FormatArg"), "missing FormatArg kind:\n{out}");
assert_reachable_or_skip_no_smt(&out, "fgets_printf");
}
#[test]
fn v10_inter_procedural_reachable_via_summaries() {
let Some(bin) = fixture("wrapped_recv_strcpy") else { return };
let v1 = Command::new(RSLEIGH_BIN)
.args([&bin, "--smt-explore", "outer"])
.output()
.expect("rsleigh invocation");
let v1_out = String::from_utf8(v1.stdout).expect("utf-8");
if v1_out.contains("smt feature not enabled at build time") {
eprintln!("[skip-no-smt] V10 wrapped: rebuild with --features smt");
return;
}
assert!(
v1_out.contains("NoSinkFound"),
"v1 (no --smt-summaries) should not see the wrapped sink:\n{v1_out}"
);
let v2 = Command::new(RSLEIGH_BIN)
.args([&bin, "--smt-explore", "outer", "--smt-summaries"])
.output()
.expect("rsleigh invocation");
let v2_out = String::from_utf8(v2.stdout).expect("utf-8");
assert!(
v2_out.contains("recv -> strcpy"),
"v2 missing recv->strcpy:\n{v2_out}"
);
assert!(
v2_out.contains("REACHABLE"),
"v2 should be REACHABLE via summary synthesis:\n{v2_out}"
);
assert!(
v2_out.contains("via ["),
"v2 should render call_chain trace:\n{v2_out}"
);
}