[pitboss/grind] deferred session-0007 (20260516T052512Z-20f8)

This commit is contained in:
pitboss 2026-05-16 04:42:17 -05:00
parent 92e90f05cc
commit f053665a83
3 changed files with 197 additions and 10 deletions

View file

@ -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));

View file

@ -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));

View file

@ -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.