refactor(dynamic): replace reflective invocation with route replay logic for Micronaut and Quarkus, remove annotation stubs, and enhance runtime path binding

This commit is contained in:
elipeter 2026-05-26 11:38:12 -05:00
parent 61bfc0cf96
commit 41c7b73575
26 changed files with 1256 additions and 224 deletions

View file

@ -1578,6 +1578,101 @@ fn c_source_to_sink() {
);
}
#[test]
fn c_fgets_condition_to_execvp_argv_fires() {
let src = br#"#include <stdio.h>
#include <unistd.h>
int main(void) {
char url_buf[256];
if (!fgets(url_buf, sizeof url_buf, stdin)) return 1;
const char *args[3];
args[0] = "ssh";
args[1] = url_buf;
args[2] = 0;
return execvp(args[0], (char *const *)args);
}
"#;
let lang = tree_sitter::Language::from(tree_sitter_c::LANGUAGE);
let file_cfg = parse_lang(src, "c", lang);
let findings = analyse_file(
&file_cfg,
&file_cfg.summaries,
None,
Lang::C,
"test.c",
&[],
None,
);
assert!(
findings
.iter()
.any(|f| f.source_kind == crate::labels::SourceKind::UserInput),
"C: fgets stdin should reach execvp argv, got {findings:#?}"
);
}
#[test]
fn c_execvp_ignores_env_config_executable_path() {
let src = br#"#include <stdlib.h>
#include <unistd.h>
int main(void) {
const char *ssh = getenv("GIT_SSH");
const char *args[2];
args[0] = ssh;
args[1] = 0;
return execvp(args[0], (char *const *)args);
}
"#;
let lang = tree_sitter::Language::from(tree_sitter_c::LANGUAGE);
let file_cfg = parse_lang(src, "c", lang);
let findings = analyse_file(
&file_cfg,
&file_cfg.summaries,
None,
Lang::C,
"test.c",
&[],
None,
);
assert!(
findings.is_empty(),
"C: env-config executable path should not be treated as argv injection"
);
}
#[test]
fn c_dash_prefix_guard_suppresses_execvp_argv_injection() {
let src = br#"#include <stdio.h>
#include <unistd.h>
int main(void) {
char url_buf[256];
if (!fgets(url_buf, sizeof url_buf, stdin)) return 1;
char *ssh_host = url_buf;
if (ssh_host[0] == '-') return 1;
const char *args[3];
args[0] = "ssh";
args[1] = ssh_host;
args[2] = 0;
return execvp(args[0], (char *const *)args);
}
"#;
let lang = tree_sitter::Language::from(tree_sitter_c::LANGUAGE);
let file_cfg = parse_lang(src, "c", lang);
let findings = analyse_file(
&file_cfg,
&file_cfg.summaries,
None,
Lang::C,
"test.c",
&[],
None,
);
assert!(
findings.is_empty(),
"C: dash-prefix rejection should clear argv-injection taint, got {findings:#?}"
);
}
#[test]
fn cpp_source_to_sink() {
let src = b"void main() {\n char* x = getenv(\"SECRET\");\n system(x);\n}\n";
@ -4548,6 +4643,248 @@ fn ssa_summary_param_to_sink() {
}
}
#[test]
fn c_summary_param_to_execvp_argv_sink() {
use crate::state::symbol::SymbolInterner;
let src = br#"#include <unistd.h>
int do_ssh_connect(char *url) {
const char *ssh;
char *ssh_host = url;
const char *port = 0;
get_host_and_port_min(&ssh_host, &port);
if (!port) port = "22";
ssh = getenv("GIT_SSH");
if (!ssh) ssh = "ssh";
const char *args[8];
int nargs = 0;
args[nargs++] = ssh;
if (port) {
args[nargs++] = "-p";
args[nargs++] = port;
}
args[nargs++] = ssh_host;
args[nargs++] = "git-upload-pack";
args[nargs++] = 0;
return execvp(args[0], (char *const *)args);
}
"#;
let file_cfg = parse_lang(
src,
"c",
tree_sitter::Language::from(tree_sitter_c::LANGUAGE),
);
for body in &file_cfg.bodies {
if body.meta.name.as_deref() != Some("do_ssh_connect") {
continue;
}
let interner = SymbolInterner::from_cfg(&body.graph);
let ssa = crate::ssa::lower_to_ssa_with_params(
&body.graph,
body.entry,
Some("do_ssh_connect"),
false,
&body.meta.params,
)
.expect("C function should lower to SSA");
let param_count = body.meta.params.len();
let summary = ssa_transfer::extract_ssa_func_summary(
&ssa,
&body.graph,
&file_cfg.summaries,
None,
Lang::C,
"test.c",
&interner,
param_count,
None,
None,
None,
None,
None,
);
assert!(
summary
.param_to_sink_caps()
.iter()
.any(|(idx, caps)| *idx == 0 && caps.contains(Cap::SHELL_ESCAPE)),
"C summary should record url param reaching execvp argv, got {:?}",
summary.param_to_sink_caps()
);
return;
}
panic!("do_ssh_connect function not found");
}
#[test]
fn c_summary_dash_prefix_guard_suppresses_execvp_argv_sink() {
use crate::state::symbol::SymbolInterner;
let src = br#"#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int do_ssh_connect(char *url) {
const char *ssh;
char *ssh_host = url;
const char *port = 0;
if (!port) port = "22";
if (ssh_host[0] == '-') {
fprintf(stderr, "strange hostname '%s' blocked\n", ssh_host);
exit(1);
}
ssh = getenv("GIT_SSH");
if (!ssh) ssh = "ssh";
const char *args[8];
int nargs = 0;
args[nargs++] = ssh;
if (port) {
args[nargs++] = "-p";
args[nargs++] = port;
}
args[nargs++] = ssh_host;
args[nargs++] = "git-upload-pack";
args[nargs++] = 0;
return execvp(args[0], (char *const *)args);
}
"#;
let file_cfg = parse_lang(
src,
"c",
tree_sitter::Language::from(tree_sitter_c::LANGUAGE),
);
for body in &file_cfg.bodies {
if body.meta.name.as_deref() != Some("do_ssh_connect") {
continue;
}
let interner = SymbolInterner::from_cfg(&body.graph);
let ssa = crate::ssa::lower_to_ssa_with_params(
&body.graph,
body.entry,
Some("do_ssh_connect"),
false,
&body.meta.params,
)
.expect("C function should lower to SSA");
let summary = ssa_transfer::extract_ssa_func_summary(
&ssa,
&body.graph,
&file_cfg.summaries,
None,
Lang::C,
"test.c",
&interner,
body.meta.params.len(),
None,
None,
None,
None,
None,
);
assert!(
!summary
.param_to_sink_caps()
.iter()
.any(|(idx, caps)| *idx == 0 && caps.contains(Cap::SHELL_ESCAPE)),
"dash-prefix guard should suppress argv-injection summary, got {:?}",
summary.param_to_sink_caps()
);
return;
}
panic!("do_ssh_connect function not found");
}
#[test]
fn c_fgets_reaches_execvp_argv_through_summary() {
let src = br#"#include <stdio.h>
#include <unistd.h>
int do_ssh_connect(char *url) {
char *ssh_host = url;
const char *args[3];
args[0] = "ssh";
args[1] = ssh_host;
args[2] = 0;
return execvp(args[0], (char *const *)args);
}
int main(void) {
char url_buf[256];
if (!fgets(url_buf, sizeof url_buf, stdin)) return 1;
return do_ssh_connect(url_buf);
}
"#;
let file_cfg = parse_lang(
src,
"c",
tree_sitter::Language::from(tree_sitter_c::LANGUAGE),
);
let findings = analyse_file(
&file_cfg,
&file_cfg.summaries,
None,
Lang::C,
"test.c",
&[],
None,
);
assert!(
findings
.iter()
.any(|f| f.source_kind == crate::labels::SourceKind::UserInput),
"C: fgets source should flow through do_ssh_connect summary, got {findings:#?}"
);
}
#[test]
fn cve_2017_1000117_vulnerable_fixture_fires() {
let src = include_bytes!("../../tests/benchmark/cve_corpus/c/CVE-2017-1000117/vulnerable.c");
let file_cfg = parse_lang(
src,
"c",
tree_sitter::Language::from(tree_sitter_c::LANGUAGE),
);
let findings = analyse_file(
&file_cfg,
&file_cfg.summaries,
None,
Lang::C,
"vulnerable.c",
&[],
None,
);
assert!(
findings
.iter()
.any(|f| f.source_kind == crate::labels::SourceKind::UserInput),
"CVE-2017-1000117 vulnerable fixture should fire, got {findings:#?}"
);
}
#[test]
fn cve_2017_1000117_patched_fixture_suppresses_dash_guard() {
let src = include_bytes!("../../tests/benchmark/cve_corpus/c/CVE-2017-1000117/patched.c");
let file_cfg = parse_lang(
src,
"c",
tree_sitter::Language::from(tree_sitter_c::LANGUAGE),
);
let findings = analyse_file(
&file_cfg,
&file_cfg.summaries,
None,
Lang::C,
"patched.c",
&[],
None,
);
assert!(
findings
.iter()
.all(|f| f.source_kind != crate::labels::SourceKind::UserInput),
"CVE-2017-1000117 patched fixture should suppress argv injection, got {findings:#?}"
);
}
#[test]
fn ssa_cross_function_taint_with_sanitizer_wrapper() {
// Cross-function: caller passes tainted data through sanitizer wrapper