mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
[pitboss/grind] deferred session-0007 (20260516T052512Z-20f8)
This commit is contained in:
parent
92e90f05cc
commit
f053665a83
3 changed files with 197 additions and 10 deletions
|
|
@ -365,6 +365,8 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
fn generate_main_c(spec: &HarnessSpec, shape: CShape) -> String {
|
||||
let invocation = invoke_for_shape(spec, shape);
|
||||
let (entry_open, entry_close) = entry_include_guards(spec);
|
||||
let shim = probe_shim();
|
||||
let crash_callee = entry_symbol_for_spec(spec);
|
||||
|
||||
format!(
|
||||
r#"/* Nyx dynamic harness — auto-generated, do not edit (Phase 16 — CShape::{shape:?}). */
|
||||
|
|
@ -373,7 +375,7 @@ fn generate_main_c(spec: &HarnessSpec, shape: CShape) -> String {
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
{shim}
|
||||
/* Forward declarations: the entry file is appended below via `#include`
|
||||
* so the harness can call user-defined functions without a separate
|
||||
* compilation unit. */
|
||||
|
|
@ -386,6 +388,13 @@ int main(int argc, char *argv[]) {{
|
|||
char *payload = nyx_payload();
|
||||
if (!payload) payload = (char*)"";
|
||||
|
||||
/* Phase 08 sink-site signal handler: install AFTER payload decode so a
|
||||
* crash inside `nyx_payload`/`nyx_b64_decode` (harness setup) writes no
|
||||
* Crash probe, routing the verifier to `Inconclusive(UnrelatedCrash)`.
|
||||
* A crash inside the entry call below DOES fire the handler and writes
|
||||
* a Crash probe to `NYX_PROBE_PATH`, lifting an `Oracle::SinkCrash`
|
||||
* payload to `Confirmed`. */
|
||||
__nyx_install_crash_guard("{crash_callee}");
|
||||
{invocation}
|
||||
/* Intentionally no free(payload): payload is either a strdup/b64_decode
|
||||
* heap pointer or a string literal substituted above when allocation
|
||||
|
|
@ -460,12 +469,21 @@ fn entry_include_guards(spec: &HarnessSpec) -> (&'static str, &'static str) {
|
|||
}
|
||||
}
|
||||
|
||||
fn invoke_for_shape(spec: &HarnessSpec, shape: CShape) -> String {
|
||||
let entry_fn: &str = if spec.entry_name == "main" {
|
||||
/// Effective C symbol used to invoke the entry from the harness `main`.
|
||||
/// Mirrors the rename inserted by [`entry_include_guards`]: when the user's
|
||||
/// entry function IS named `main` it is renamed to `__nyx_entry_main` via
|
||||
/// the preprocessor wrap, so both the call site in [`invoke_for_shape`] and
|
||||
/// the `__nyx_install_crash_guard` callee label use this helper.
|
||||
fn entry_symbol_for_spec(spec: &HarnessSpec) -> &str {
|
||||
if spec.entry_name == "main" {
|
||||
"__nyx_entry_main"
|
||||
} else {
|
||||
spec.entry_name.as_str()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn invoke_for_shape(spec: &HarnessSpec, shape: CShape) -> String {
|
||||
let entry_fn: &str = entry_symbol_for_spec(spec);
|
||||
match shape {
|
||||
CShape::FreeFn => match &spec.payload_slot {
|
||||
PayloadSlot::EnvVar(name) => format!(
|
||||
|
|
@ -673,6 +691,60 @@ mod tests {
|
|||
assert!(fh.source.contains("nyx_entry_main(new_argc, new_argv)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_splices_probe_shim_and_installs_crash_guard_for_free_fn() {
|
||||
// Phase 16 follow-up: the C emitter now splices probe_shim() into the
|
||||
// generated harness AND installs the sink-site signal handler around
|
||||
// the entry invocation. This is the joint unblock for Phase 08
|
||||
// (a) / (b) — a SIGSEGV inside the entry writes a Crash probe to
|
||||
// `NYX_PROBE_PATH`; a SIGSEGV during `nyx_payload` setup (before the
|
||||
// install) writes nothing, routing to `Inconclusive(UnrelatedCrash)`.
|
||||
let spec = make_spec(PayloadSlot::Param(0));
|
||||
let h = emit(&spec).unwrap();
|
||||
// The shim text is identified by its banner comment.
|
||||
assert!(
|
||||
h.source.contains("__nyx_probe shim (Phase 06 — Track C.1"),
|
||||
"probe_shim banner missing from generated main.c — splicing regressed",
|
||||
);
|
||||
// The signal-handler installer is callable from the harness body.
|
||||
assert!(
|
||||
h.source.contains("static void __nyx_install_crash_guard("),
|
||||
"install_crash_guard definition missing from generated main.c",
|
||||
);
|
||||
// The install call references the entry symbol (here `run`, since
|
||||
// `make_spec` sets `entry_name = "run"`).
|
||||
assert!(
|
||||
h.source.contains("__nyx_install_crash_guard(\"run\");"),
|
||||
"install_crash_guard call site missing or wrong callee in main()",
|
||||
);
|
||||
// The install must come after `nyx_payload()` returns and before the
|
||||
// entry invocation — otherwise a crash inside payload decode would
|
||||
// be misattributed to the sink (would defeat Phase 08(b)).
|
||||
let install_pos = h.source.find("__nyx_install_crash_guard(\"run\");").unwrap();
|
||||
let payload_pos = h.source.find("char *payload = nyx_payload();").unwrap();
|
||||
let invoke_pos = h.source.find("run(payload, strlen(payload));").unwrap();
|
||||
assert!(
|
||||
payload_pos < install_pos && install_pos < invoke_pos,
|
||||
"install_crash_guard ordering wrong: payload_pos={payload_pos} install_pos={install_pos} invoke_pos={invoke_pos}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_install_crash_guard_targets_renamed_main_entry() {
|
||||
// Real-world Track B CLI vuln: spec.entry_name == "main" → the entry
|
||||
// is renamed to __nyx_entry_main by entry_include_guards, and the
|
||||
// install call must reference the renamed symbol so the Crash probe
|
||||
// attributes correctly.
|
||||
let mut spec = make_spec(PayloadSlot::Argv(0));
|
||||
spec.entry_kind = EntryKind::CliSubcommand;
|
||||
spec.entry_name = "main".into();
|
||||
let h = emit(&spec).unwrap();
|
||||
assert!(
|
||||
h.source.contains("__nyx_install_crash_guard(\"__nyx_entry_main\");"),
|
||||
"install_crash_guard must use the post-rename symbol when entry_name == 'main'",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_libfuzzer_shape_passes_bytes() {
|
||||
let mut spec = make_spec(PayloadSlot::Param(0));
|
||||
|
|
|
|||
|
|
@ -336,6 +336,8 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
fn generate_main_cpp(spec: &HarnessSpec, shape: CppShape) -> String {
|
||||
let invocation = invoke_for_shape(spec, shape);
|
||||
let (entry_open, entry_close) = entry_include_guards(spec);
|
||||
let shim = probe_shim();
|
||||
let crash_callee = entry_symbol_for_spec(spec);
|
||||
|
||||
format!(
|
||||
r#"// Nyx dynamic harness — auto-generated, do not edit (Phase 16 — CppShape::{shape:?}).
|
||||
|
|
@ -346,7 +348,7 @@ fn generate_main_cpp(spec: &HarnessSpec, shape: CppShape) -> String {
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
{shim}
|
||||
static std::string nyx_payload();
|
||||
|
||||
{entry_open}#include "entry.cpp"
|
||||
|
|
@ -355,6 +357,11 @@ int main(int argc, char *argv[]) {{
|
|||
(void)argc; (void)argv;
|
||||
std::string payload = nyx_payload();
|
||||
|
||||
// Phase 08 sink-site signal handler: install AFTER payload decode so a
|
||||
// crash in nyx_payload / nyx_b64_decode (harness setup) writes no Crash
|
||||
// probe. A crash inside the entry call below fires the handler and
|
||||
// writes a Crash probe to NYX_PROBE_PATH for `Oracle::SinkCrash`.
|
||||
__nyx_install_crash_guard("{crash_callee}");
|
||||
{invocation}
|
||||
return 0;
|
||||
}}
|
||||
|
|
@ -415,12 +422,19 @@ fn entry_include_guards(spec: &HarnessSpec) -> (&'static str, &'static str) {
|
|||
}
|
||||
}
|
||||
|
||||
fn invoke_for_shape(spec: &HarnessSpec, shape: CppShape) -> String {
|
||||
let entry_fn: &str = if spec.entry_name == "main" {
|
||||
/// Effective C++ symbol used to invoke the entry from the harness `main`,
|
||||
/// after [`entry_include_guards`] has rewritten an entry-side `main` to
|
||||
/// `__nyx_entry_main`.
|
||||
fn entry_symbol_for_spec(spec: &HarnessSpec) -> &str {
|
||||
if spec.entry_name == "main" {
|
||||
"__nyx_entry_main"
|
||||
} else {
|
||||
spec.entry_name.as_str()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn invoke_for_shape(spec: &HarnessSpec, shape: CppShape) -> String {
|
||||
let entry_fn: &str = entry_symbol_for_spec(spec);
|
||||
match shape {
|
||||
CppShape::FreeFn => match &spec.payload_slot {
|
||||
PayloadSlot::EnvVar(name) => format!(
|
||||
|
|
@ -594,6 +608,46 @@ mod tests {
|
|||
assert!(fh.source.contains("nyx_entry_main(static_cast<int>(argv_storage.size()), new_argv.data())"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_splices_probe_shim_and_installs_crash_guard_for_free_fn() {
|
||||
// Phase 16 follow-up: C++ emitter now splices probe_shim() and
|
||||
// installs the sink-site signal handler around the entry call.
|
||||
// Mirrors the C-side splicing tests.
|
||||
let spec = make_spec(PayloadSlot::Param(0));
|
||||
let h = emit(&spec).unwrap();
|
||||
assert!(
|
||||
h.source.contains("__nyx_probe shim (Phase 06 — Track C.1"),
|
||||
"probe_shim banner missing from generated main.cpp",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("inline void __nyx_install_crash_guard("),
|
||||
"install_crash_guard definition missing from generated main.cpp",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("__nyx_install_crash_guard(\"run\");"),
|
||||
"install_crash_guard call site missing or wrong callee",
|
||||
);
|
||||
let install_pos = h.source.find("__nyx_install_crash_guard(\"run\");").unwrap();
|
||||
let payload_pos = h.source.find("std::string payload = nyx_payload();").unwrap();
|
||||
let invoke_pos = h.source.find("run(payload.c_str(), payload.size());").unwrap();
|
||||
assert!(
|
||||
payload_pos < install_pos && install_pos < invoke_pos,
|
||||
"install_crash_guard ordering wrong: payload_pos={payload_pos} install_pos={install_pos} invoke_pos={invoke_pos}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_install_crash_guard_targets_renamed_main_entry() {
|
||||
let mut spec = make_spec(PayloadSlot::Argv(0));
|
||||
spec.entry_kind = EntryKind::CliSubcommand;
|
||||
spec.entry_name = "main".into();
|
||||
let h = emit(&spec).unwrap();
|
||||
assert!(
|
||||
h.source.contains("__nyx_install_crash_guard(\"__nyx_entry_main\");"),
|
||||
"install_crash_guard must use post-rename symbol when entry_name == 'main'",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_cmake_in_extra_files() {
|
||||
let spec = make_spec(PayloadSlot::Param(0));
|
||||
|
|
|
|||
|
|
@ -473,9 +473,15 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
/// Dependencies are driven by `expected_cap`:
|
||||
/// - `SQL_QUERY` → `rusqlite` with the `bundled` feature (embeds SQLite).
|
||||
/// - Other caps use only std (no extra deps).
|
||||
///
|
||||
/// `libc` is always pinned because the Phase 16 probe shim (spliced into
|
||||
/// `src/main.rs` by [`generate_main_rs`]) calls `libc::sigaction` from
|
||||
/// `__nyx_install_crash_guard`. The shim is unconditionally compiled so
|
||||
/// the dep must be unconditional too.
|
||||
pub fn generate_cargo_toml(cap: Cap) -> String {
|
||||
let mut deps = String::new();
|
||||
|
||||
deps.push_str("libc = \"0.2\"\n");
|
||||
if cap.contains(Cap::SQL_QUERY) {
|
||||
deps.push_str("rusqlite = { version = \"0.39\", features = [\"bundled\"] }\n");
|
||||
}
|
||||
|
|
@ -496,18 +502,28 @@ pub fn generate_cargo_toml(cap: Cap) -> String {
|
|||
/// Generate `src/main.rs` — the harness entry point.
|
||||
///
|
||||
/// Reads the payload from env, calls `entry::{entry_name}` with the payload
|
||||
/// routed according to `spec.payload_slot` and `shape`.
|
||||
/// routed according to `spec.payload_slot` and `shape`. The probe shim
|
||||
/// (Phase 06 / Phase 08) is spliced in at file scope so
|
||||
/// `__nyx_install_crash_guard` is callable from `main` before the entry
|
||||
/// invocation.
|
||||
fn generate_main_rs(spec: &HarnessSpec, shape: RustShape) -> String {
|
||||
let entry_fn = &spec.entry_name;
|
||||
let (pre_call, call_expr) = build_call(spec, entry_fn, shape);
|
||||
let shim = probe_shim();
|
||||
let entry_label = spec.entry_name.replace('\\', "\\\\").replace('"', "\\\"");
|
||||
|
||||
format!(
|
||||
r#"//! Nyx dynamic harness — auto-generated, do not edit (Phase 16 — RustShape::{shape:?}).
|
||||
mod entry;
|
||||
|
||||
{shim}
|
||||
fn main() {{
|
||||
let payload = nyx_payload();
|
||||
let _ = &payload;
|
||||
// Phase 08 sink-site signal handler: install AFTER payload decode so a
|
||||
// crash in `nyx_payload` / `b64_decode` (harness setup) writes no Crash
|
||||
// probe. A crash inside the entry call below fires the handler and
|
||||
// writes a Crash probe to NYX_PROBE_PATH for `Oracle::SinkCrash`.
|
||||
__nyx_install_crash_guard("{entry_label}");
|
||||
{pre_call} {call_expr}
|
||||
}}
|
||||
|
||||
|
|
@ -809,6 +825,51 @@ mod tests {
|
|||
assert!(src.contains("entry::fuzz_target(payload.as_bytes())"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_splices_probe_shim_and_installs_crash_guard() {
|
||||
// Phase 16 follow-up: Rust emitter now splices probe_shim() into
|
||||
// src/main.rs and installs the sink-site signal handler around the
|
||||
// entry call. Mirrors the C / C++ splicing tests.
|
||||
let spec = make_spec(PayloadSlot::Param(0));
|
||||
let h = emit(&spec).unwrap();
|
||||
assert!(
|
||||
h.source.contains("__nyx_probe shim (Phase 06 — Track C.1"),
|
||||
"probe_shim banner missing from generated src/main.rs",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("fn __nyx_install_crash_guard("),
|
||||
"install_crash_guard definition missing from generated src/main.rs",
|
||||
);
|
||||
assert!(
|
||||
h.source.contains("__nyx_install_crash_guard(\"run\");"),
|
||||
"install_crash_guard call site missing or wrong callee",
|
||||
);
|
||||
let install_pos = h
|
||||
.source
|
||||
.find("__nyx_install_crash_guard(\"run\");")
|
||||
.unwrap();
|
||||
let payload_pos = h.source.find("let payload = nyx_payload();").unwrap();
|
||||
let invoke_pos = h.source.find("entry::run(&payload);").unwrap();
|
||||
assert!(
|
||||
payload_pos < install_pos && install_pos < invoke_pos,
|
||||
"install_crash_guard ordering wrong: payload={payload_pos} install={install_pos} invoke={invoke_pos}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cargo_toml_always_pins_libc_for_probe_shim() {
|
||||
// Phase 16 follow-up: the probe shim calls `libc::sigaction` so
|
||||
// `libc` must be unconditionally pinned (independent of the
|
||||
// expected_cap dep matrix).
|
||||
for cap in [Cap::SQL_QUERY, Cap::CODE_EXEC, Cap::FILE_IO, Cap::SSRF] {
|
||||
let cargo = generate_cargo_toml(cap);
|
||||
assert!(
|
||||
cargo.contains("libc = \"0.2\""),
|
||||
"libc dep missing for cap={cap:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b64_decode_roundtrip() {
|
||||
// Test by compiling: actual b64_decode is in generated code.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue