mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
234 lines
8.2 KiB
Rust
234 lines
8.2 KiB
Rust
//! C++ harness emitter (stub).
|
|
//!
|
|
//! No harness source is generated yet — `emit` returns
|
|
//! [`UnsupportedReason::LangUnsupported`]. The module exists so that
|
|
//! [`crate::dynamic::lang::entry_kinds_supported`] can advertise the entry
|
|
//! kinds Track B will deliver (Phase 16: `main(argc, argv)`,
|
|
//! `LLVMFuzzerTestOneInput`, free functions with `(const char*, size_t)`)
|
|
//! and so the verifier can surface `Inconclusive(EntryKindUnsupported { … })`
|
|
//! instead of dropping C++ findings.
|
|
|
|
use crate::dynamic::lang::{HarnessSource, LangEmitter};
|
|
use crate::dynamic::spec::{EntryKind, HarnessSpec};
|
|
use crate::evidence::UnsupportedReason;
|
|
|
|
/// Zero-sized [`LangEmitter`] handle for C++.
|
|
pub struct CppEmitter;
|
|
|
|
/// Entry kinds the C++ emitter intends to support once Phase 16 lands.
|
|
const SUPPORTED: &[EntryKind] = &[EntryKind::Function];
|
|
|
|
/// Source of the `__nyx_probe` shim for the (future) C++ harness
|
|
/// (Phase 06 — Track C.1). Uses `<fstream>` + variadic templates; the
|
|
/// JSON-emit format matches [`crate::dynamic::probe::SinkProbe`].
|
|
pub fn probe_shim() -> &'static str {
|
|
r#"
|
|
/* ── __nyx_probe shim (Phase 06 — Track C.1, Phase 08 — Track C.4 + C.5) ── */
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <csignal>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unistd.h>
|
|
|
|
#ifndef __NYX_PAYLOAD_LIMIT
|
|
#define __NYX_PAYLOAD_LIMIT (16 * 1024)
|
|
#endif
|
|
#define __NYX_REDACTED "<redacted-by-nyx-policy>"
|
|
|
|
extern char **environ;
|
|
|
|
static const char *__nyx_deny_substrings_cpp[] = {
|
|
"TOKEN","SECRET","PASSWORD","PASSWD","API_KEY","APIKEY","PRIVATE_KEY",
|
|
"CREDENTIAL","SESSION","COOKIE","AUTH","BEARER","AWS_ACCESS","AWS_SESSION",
|
|
"GH_TOKEN","GITHUB_TOKEN","NPM_TOKEN","PYPI_TOKEN","DOCKER_PASS",
|
|
};
|
|
|
|
inline void __nyx_probe_one(std::ostringstream &out, const std::string &v) {
|
|
out << "{\"kind\":\"String\",\"value\":\"";
|
|
for (char c : v) {
|
|
switch (c) {
|
|
case '"': out << "\\\""; break;
|
|
case '\\': out << "\\\\"; break;
|
|
case '\n': out << "\\n"; break;
|
|
case '\r': out << "\\r"; break;
|
|
case '\t': out << "\\t"; break;
|
|
default: out << c;
|
|
}
|
|
}
|
|
out << "\"}";
|
|
}
|
|
|
|
inline void __nyx_esc(std::ostringstream &out, const std::string &v) {
|
|
for (char c : v) {
|
|
switch (c) {
|
|
case '"': out << "\\\""; break;
|
|
case '\\': out << "\\\\"; break;
|
|
case '\n': out << "\\n"; break;
|
|
case '\r': out << "\\r"; break;
|
|
case '\t': out << "\\t"; break;
|
|
default: out << c;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline std::string __nyx_witness_json(const char *sink_callee, const std::vector<std::string> &args_repr) {
|
|
std::ostringstream out;
|
|
out << "{\"env_snapshot\":{";
|
|
bool first = true;
|
|
for (char **e = environ; *e; ++e) {
|
|
const char *eq = std::strchr(*e, '=');
|
|
if (!eq) continue;
|
|
std::string k(*e, static_cast<size_t>(eq - *e));
|
|
std::string ku = k;
|
|
std::transform(ku.begin(), ku.end(), ku.begin(), [](unsigned char c){ return (char)std::toupper(c); });
|
|
bool denied = false;
|
|
for (const char *needle : __nyx_deny_substrings_cpp) {
|
|
if (ku.find(needle) != std::string::npos) { denied = true; break; }
|
|
}
|
|
if (!first) out << ',';
|
|
first = false;
|
|
out << '"'; __nyx_esc(out, k); out << "\":\"";
|
|
if (denied) out << __NYX_REDACTED;
|
|
else __nyx_esc(out, std::string(eq + 1));
|
|
out << '"';
|
|
}
|
|
out << "},\"cwd\":\"";
|
|
char cwdbuf[4096];
|
|
if (::getcwd(cwdbuf, sizeof(cwdbuf))) __nyx_esc(out, std::string(cwdbuf));
|
|
out << "\",\"payload_bytes\":[";
|
|
const char *payload = std::getenv("NYX_PAYLOAD");
|
|
if (payload) {
|
|
size_t plen = std::strlen(payload);
|
|
if (plen > __NYX_PAYLOAD_LIMIT) plen = __NYX_PAYLOAD_LIMIT;
|
|
for (size_t i = 0; i < plen; ++i) {
|
|
if (i > 0) out << ',';
|
|
out << static_cast<int>(static_cast<unsigned char>(payload[i]));
|
|
}
|
|
}
|
|
out << "],\"callee\":\""; __nyx_esc(out, std::string(sink_callee));
|
|
out << "\",\"args_repr\":[";
|
|
for (size_t i = 0; i < args_repr.size(); ++i) {
|
|
if (i > 0) out << ',';
|
|
out << '"'; __nyx_esc(out, args_repr[i]); out << '"';
|
|
}
|
|
out << "]}";
|
|
return out.str();
|
|
}
|
|
|
|
template <typename... Args>
|
|
inline void __nyx_probe(const char *sink_callee, Args... args) {
|
|
const char *p = std::getenv("NYX_PROBE_PATH");
|
|
if (!p || *p == '\0') return;
|
|
std::ostringstream out;
|
|
out << "{\"sink_callee\":\"" << sink_callee << "\",\"args\":[";
|
|
bool first = true;
|
|
std::vector<std::string> repr;
|
|
auto emit = [&](const std::string &s) {
|
|
if (!first) out << ',';
|
|
first = false;
|
|
__nyx_probe_one(out, s);
|
|
repr.push_back(s);
|
|
};
|
|
(emit(std::string(args)), ...);
|
|
const char *pid = std::getenv("NYX_PAYLOAD_ID");
|
|
auto now = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()
|
|
).count();
|
|
out << "],\"captured_at_ns\":" << now << ",\"payload_id\":\""
|
|
<< (pid ? pid : "") << "\",";
|
|
out << "\"kind\":{\"kind\":\"Normal\"},\"witness\":"
|
|
<< __nyx_witness_json(sink_callee, repr) << "}\n";
|
|
std::ofstream f(p, std::ios::app);
|
|
if (f.is_open()) f << out.str();
|
|
}
|
|
|
|
/* Phase 08: sink-site sigaction handler. Mirrors the C variant; the
|
|
* captured `sink_callee` is held in a file-scope const char* so the
|
|
* async-signal-unsafe write path can pull it without TLS. */
|
|
static const char *__nyx_crash_sink_callee = "";
|
|
|
|
inline void __nyx_crash_handler(int sig) {
|
|
const char *p = std::getenv("NYX_PROBE_PATH");
|
|
if (p && *p) {
|
|
std::ofstream f(p, std::ios::app);
|
|
if (f.is_open()) {
|
|
const char *name = "SIGABRT";
|
|
switch (sig) {
|
|
case SIGSEGV: name = "SIGSEGV"; break;
|
|
case SIGABRT: name = "SIGABRT"; break;
|
|
case SIGBUS: name = "SIGBUS"; break;
|
|
case SIGFPE: name = "SIGFPE"; break;
|
|
case SIGILL: name = "SIGILL"; break;
|
|
}
|
|
auto now = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()
|
|
).count();
|
|
const char *pid = std::getenv("NYX_PAYLOAD_ID");
|
|
std::ostringstream out;
|
|
out << "{\"sink_callee\":\"" << __nyx_crash_sink_callee
|
|
<< "\",\"args\":[],\"captured_at_ns\":" << now
|
|
<< ",\"payload_id\":\"" << (pid ? pid : "")
|
|
<< "\",\"kind\":{\"kind\":\"Crash\",\"signal\":\"" << name
|
|
<< "\"},\"witness\":"
|
|
<< __nyx_witness_json(__nyx_crash_sink_callee, {}) << "}\n";
|
|
f << out.str();
|
|
}
|
|
}
|
|
struct sigaction dfl;
|
|
std::memset(&dfl, 0, sizeof(dfl));
|
|
dfl.sa_handler = SIG_DFL;
|
|
sigaction(sig, &dfl, nullptr);
|
|
raise(sig);
|
|
}
|
|
|
|
inline void __nyx_install_crash_guard(const char *sink_callee) {
|
|
__nyx_crash_sink_callee = sink_callee;
|
|
struct sigaction sa;
|
|
std::memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = __nyx_crash_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
for (int sig : { SIGSEGV, SIGABRT, SIGBUS, SIGFPE, SIGILL }) {
|
|
sigaction(sig, &sa, nullptr);
|
|
}
|
|
}
|
|
"#
|
|
}
|
|
|
|
impl LangEmitter for CppEmitter {
|
|
fn emit(&self, _spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|
Err(UnsupportedReason::LangUnsupported)
|
|
}
|
|
|
|
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
|
SUPPORTED
|
|
}
|
|
|
|
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
|
format!(
|
|
"cpp emitter is a stub; once Phase 16 (Track B Rust + C/C++ vertical) lands it will support {SUPPORTED:?} plus libFuzzer + main(argc, argv) shapes — attempted `EntryKind::{attempted}`"
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn entry_kinds_supported_is_non_empty() {
|
|
assert!(!CppEmitter.entry_kinds_supported().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn entry_kind_hint_names_attempted_and_phase() {
|
|
let hint = CppEmitter.entry_kind_hint(EntryKind::CliSubcommand);
|
|
assert!(hint.contains("CliSubcommand"));
|
|
assert!(hint.contains("Phase 16"));
|
|
}
|
|
}
|