mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-24 20:28:06 +02:00
[pitboss/grind] deferred session-0002 (20260522T163126Z-7d60)
This commit is contained in:
parent
e4258d63ed
commit
3486056f5e
12 changed files with 1129 additions and 26 deletions
|
|
@ -1437,6 +1437,122 @@ fn main() {{
|
|||
}
|
||||
}
|
||||
|
||||
/// Phase 11 — Track J.9 JSON_PARSE depth-bomb harness for Rust.
|
||||
///
|
||||
/// Stages the fixture at `src/entry.rs`, builds against
|
||||
/// `serde_json = "1"` (added to `Cargo.toml` automatically when
|
||||
/// `Cap::JSON_PARSE` is set — see [`generate_cargo_toml_with_extras`]),
|
||||
/// invokes `entry::<entry_name>(&payload)`, walks the returned
|
||||
/// `serde_json::Value` iteratively, and writes a
|
||||
/// `ProbeKind::JsonParse { depth, excessive_depth }` probe.
|
||||
///
|
||||
/// The fixture's entry is expected to return a `serde_json::Value`
|
||||
/// (parsing `&str` / `&[u8]` via `serde_json::from_str` or
|
||||
/// `serde_json::from_slice` and returning the resulting `Value`).
|
||||
/// `serde_json` is iterative so deeply-nested input never panics the
|
||||
/// parser; the harness reads the observed depth off the returned
|
||||
/// value rather than intercepting the parse call site itself.
|
||||
pub fn emit_json_parse_harness(spec: &HarnessSpec) -> HarnessSource {
|
||||
let shim = probe_shim();
|
||||
let entry_fn = if spec.entry_name.is_empty() {
|
||||
"run".to_owned()
|
||||
} else {
|
||||
spec.entry_name.clone()
|
||||
};
|
||||
let cargo_toml = generate_cargo_toml(Cap::JSON_PARSE);
|
||||
|
||||
let main_rs = format!(
|
||||
r##"//! Nyx dynamic harness — JSON_PARSE depth checks (Phase 11 / Track J.9).
|
||||
mod entry;
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::time::{{SystemTime, UNIX_EPOCH}};
|
||||
|
||||
{shim}
|
||||
|
||||
const NYX_JSON_MAX_WALK: usize = 4096;
|
||||
|
||||
fn nyx_json_count_depth(value: &serde_json::Value) -> u32 {{
|
||||
let mut max_depth: u32 = 0;
|
||||
let mut stack: Vec<(&serde_json::Value, u32)> = Vec::with_capacity(64);
|
||||
stack.push((value, 1));
|
||||
let mut visited: usize = 0;
|
||||
while let Some((cur, depth)) = stack.pop() {{
|
||||
visited += 1;
|
||||
if visited > NYX_JSON_MAX_WALK {{ break; }}
|
||||
if depth > max_depth {{ max_depth = depth; }}
|
||||
match cur {{
|
||||
serde_json::Value::Array(items) => {{
|
||||
for child in items {{
|
||||
stack.push((child, depth + 1));
|
||||
}}
|
||||
}}
|
||||
serde_json::Value::Object(map) => {{
|
||||
for child in map.values() {{
|
||||
stack.push((child, depth + 1));
|
||||
}}
|
||||
}}
|
||||
_ => {{}}
|
||||
}}
|
||||
}}
|
||||
max_depth
|
||||
}}
|
||||
|
||||
fn nyx_json_parse_probe(depth: u32, excessive: bool) {{
|
||||
let p = match env::var("NYX_PROBE_PATH") {{ Ok(s) => s, Err(_) => return }};
|
||||
if p.is_empty() {{ return; }}
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_nanos() as u64)
|
||||
.unwrap_or(0);
|
||||
let payload_id = env::var("NYX_PAYLOAD_ID").unwrap_or_default();
|
||||
let depth_str = depth.to_string();
|
||||
let excessive_str = if excessive {{ "true" }} else {{ "false" }};
|
||||
let mut line = String::with_capacity(256);
|
||||
line.push_str("{{\"sink_callee\":\"serde_json::from_str\",\"args\":[");
|
||||
line.push_str("{{\"kind\":\"Int\",\"value\":");
|
||||
line.push_str(&depth_str);
|
||||
line.push_str("}}],");
|
||||
line.push_str("\"captured_at_ns\":");
|
||||
line.push_str(&now.to_string());
|
||||
line.push_str(",\"payload_id\":\"");
|
||||
let mut esc_pid = String::new();
|
||||
__nyx_esc(&payload_id, &mut esc_pid);
|
||||
line.push_str(&esc_pid);
|
||||
line.push_str("\",\"kind\":{{\"kind\":\"JsonParse\",\"depth\":");
|
||||
line.push_str(&depth_str);
|
||||
line.push_str(",\"excessive_depth\":");
|
||||
line.push_str(excessive_str);
|
||||
line.push_str("}},\"witness\":");
|
||||
line.push_str(&__nyx_witness_json("serde_json::from_str", &[&depth_str]));
|
||||
line.push_str("}}\n");
|
||||
if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(&p) {{
|
||||
let _ = f.write_all(line.as_bytes());
|
||||
}}
|
||||
}}
|
||||
|
||||
fn main() {{
|
||||
let payload = env::var("NYX_PAYLOAD").unwrap_or_default();
|
||||
__nyx_install_crash_guard("serde_json::from_str");
|
||||
let parsed = entry::{entry_fn}(&payload);
|
||||
let depth = nyx_json_count_depth(&parsed);
|
||||
let excessive = depth > 64;
|
||||
nyx_json_parse_probe(depth, excessive);
|
||||
println!("__NYX_SINK_HIT__");
|
||||
println!("{{{{\"depth\":{{depth}}}}}}", depth = depth);
|
||||
}}
|
||||
"##
|
||||
);
|
||||
HarnessSource {
|
||||
source: main_rs,
|
||||
filename: "src/main.rs".into(),
|
||||
command: vec!["target/release/nyx_harness".into()],
|
||||
extra_files: vec![("Cargo.toml".into(), cargo_toml)],
|
||||
entry_subpath: Some("src/entry.rs".into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a Rust harness for `spec`.
|
||||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
// Phase 08 (Track J.6): HEADER_INJECTION-sink short-circuit. The
|
||||
|
|
@ -1469,6 +1585,15 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
return Ok(emit_crypto_harness(spec));
|
||||
}
|
||||
|
||||
// Phase 11 (Track J.9): JSON_PARSE depth-bomb short-circuit. Stages
|
||||
// the fixture at `src/entry.rs`, builds against `serde_json = "1"`,
|
||||
// invokes `entry::<entry_name>(&payload)` which is expected to
|
||||
// return a `serde_json::Value`, walks that value iteratively, and
|
||||
// writes a `ProbeKind::JsonParse { depth, excessive_depth }` record.
|
||||
if spec.expected_cap == crate::labels::Cap::JSON_PARSE {
|
||||
return Ok(emit_json_parse_harness(spec));
|
||||
}
|
||||
|
||||
// Phase 19 (Track M.1): ClassMethod short-circuit. Rust has no
|
||||
// class system — the dispatcher maps `class` to a struct exported
|
||||
// from `entry::`, and `method` to a `&self` method on that
|
||||
|
|
@ -1835,6 +1960,9 @@ pub fn generate_cargo_toml_with_extras(cap: Cap, needs_percent_encoding: bool) -
|
|||
if cap.contains(Cap::CRYPTO) {
|
||||
deps.push_str("rand = \"0.8\"\n");
|
||||
}
|
||||
if cap.contains(Cap::JSON_PARSE) {
|
||||
deps.push_str("serde_json = \"1\"\n");
|
||||
}
|
||||
|
||||
format!(
|
||||
"[package]\n\
|
||||
|
|
@ -3067,4 +3195,106 @@ mod tests {
|
|||
h.source
|
||||
);
|
||||
}
|
||||
|
||||
// ── Phase 11 (Track J.9) Rust JSON_PARSE emitter tests ─────────────────────
|
||||
|
||||
fn make_json_parse_spec(entry_file: &str, entry_name: &str) -> HarnessSpec {
|
||||
let mut spec = make_spec(PayloadSlot::Param(0));
|
||||
spec.expected_cap = Cap::JSON_PARSE;
|
||||
spec.entry_file = entry_file.to_owned();
|
||||
spec.entry_name = entry_name.to_owned();
|
||||
spec
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_dispatches_to_json_parse_harness_when_cap_is_json_parse() {
|
||||
let h = emit(&make_json_parse_spec(
|
||||
"tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs",
|
||||
"run",
|
||||
))
|
||||
.unwrap();
|
||||
assert!(
|
||||
h.source.contains("fn nyx_json_parse_probe"),
|
||||
"dispatcher must short-circuit Cap::JSON_PARSE into emit_json_parse_harness so the depth probe shim is present: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains(r#"\"kind\":\"JsonParse\""#),
|
||||
"Rust JSON_PARSE harness must record probes with kind JsonParse: {}",
|
||||
h.source
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_json_parse_harness_invokes_entry_via_mod_entry() {
|
||||
let h = emit_json_parse_harness(&make_json_parse_spec(
|
||||
"tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs",
|
||||
"run",
|
||||
));
|
||||
assert!(
|
||||
h.source.contains("mod entry;"),
|
||||
"Rust JSON_PARSE harness must declare `mod entry;` so the staged fixture is in scope",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("let parsed = entry::run(&payload);"),
|
||||
"Rust JSON_PARSE harness must invoke the entry function with the payload",
|
||||
);
|
||||
assert_eq!(h.entry_subpath, Some("src/entry.rs".to_string()));
|
||||
assert_eq!(h.filename, "src/main.rs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_json_parse_harness_cargo_toml_pulls_in_serde_json() {
|
||||
let h = emit_json_parse_harness(&make_json_parse_spec(
|
||||
"tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs",
|
||||
"run",
|
||||
));
|
||||
let cargo = h
|
||||
.extra_files
|
||||
.iter()
|
||||
.find(|(n, _)| n == "Cargo.toml")
|
||||
.expect("Cargo.toml must be in extra_files");
|
||||
assert!(
|
||||
cargo.1.contains("serde_json = \"1\""),
|
||||
"Rust JSON_PARSE harness Cargo.toml must depend on serde_json so the fixture's parser resolves: {}",
|
||||
cargo.1
|
||||
);
|
||||
assert!(
|
||||
cargo.1.contains("libc = \"0.2\""),
|
||||
"Rust JSON_PARSE harness Cargo.toml must keep libc dep for the probe shim's sigaction path",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_json_parse_harness_uses_iterative_walker() {
|
||||
let h = emit_json_parse_harness(&make_json_parse_spec(
|
||||
"tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs",
|
||||
"run",
|
||||
));
|
||||
assert!(
|
||||
h.source.contains("fn nyx_json_count_depth"),
|
||||
"Rust JSON_PARSE harness must define the iterative depth walker: {}",
|
||||
h.source
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("serde_json::Value::Array(items)"),
|
||||
"depth walker must dispatch on serde_json::Value::Array",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("serde_json::Value::Object(map)"),
|
||||
"depth walker must dispatch on serde_json::Value::Object",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_json_parse_harness_emits_depth_fields() {
|
||||
let h = emit_json_parse_harness(&make_json_parse_spec(
|
||||
"tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs",
|
||||
"run",
|
||||
));
|
||||
assert!(h.source.contains(r#"\"depth\":"#));
|
||||
assert!(h.source.contains(r#"\"excessive_depth\":"#));
|
||||
assert!(h.source.contains("depth > 64"));
|
||||
assert!(h.source.contains("__NYX_SINK_HIT__"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue