mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
refactor(dynamic): improve fallback handling for sandbox restrictions, centralize and enhance stub initialization, and expand test coverage across harnesses
This commit is contained in:
parent
cb3b39d892
commit
68bdd30eca
17 changed files with 546 additions and 68 deletions
|
|
@ -279,7 +279,14 @@ fn stub_ldap_server_returns_three_for_wildcard_filter() {
|
|||
// The acceptance bullet states: stub LDAP server returns > 1
|
||||
// entry on the malicious filter, exactly 1 on the benign filter.
|
||||
// Pin both directions against the actual stub.
|
||||
let stub = LdapStub::start().expect("ldap stub starts");
|
||||
let stub = match LdapStub::start() {
|
||||
Ok(stub) => stub,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
eprintln!("SKIP ldap stub socket test: loopback bind denied by sandbox");
|
||||
return;
|
||||
}
|
||||
Err(e) => panic!("ldap stub starts: {e}"),
|
||||
};
|
||||
let mal = LdapStub::evaluate("(|(uid=alice)(uid=*))");
|
||||
let benign = LdapStub::evaluate("(uid=alice)");
|
||||
assert!(
|
||||
|
|
@ -488,7 +495,14 @@ mod e2e_phase_06 {
|
|||
return None;
|
||||
}
|
||||
let _guard = FIXTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let stub = LdapStub::start().expect("ldap stub starts");
|
||||
let stub = match LdapStub::start() {
|
||||
Ok(stub) => stub,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
eprintln!("SKIP {lang:?} {fixture}: loopback bind denied by sandbox");
|
||||
return None;
|
||||
}
|
||||
Err(e) => panic!("ldap stub starts: {e}"),
|
||||
};
|
||||
let endpoint = stub.endpoint();
|
||||
let (mut spec, _tmp) = build_spec(lang, fixture, entry_name);
|
||||
spec.stubs_required = vec![nyx_scanner::dynamic::stubs::StubKind::Ldap];
|
||||
|
|
|
|||
|
|
@ -641,7 +641,14 @@ mod e2e_phase_09 {
|
|||
}
|
||||
let _guard = FIXTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
|
||||
let listener = Arc::new(OobListener::bind().expect("bind OOB listener on loopback"));
|
||||
let listener = match OobListener::bind() {
|
||||
Ok(listener) => Arc::new(listener),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
eprintln!("SKIP {lang:?} {fixture} (oob): loopback bind denied by sandbox");
|
||||
return None;
|
||||
}
|
||||
Err(e) => panic!("bind OOB listener on loopback: {e}"),
|
||||
};
|
||||
let (mut spec, _tmp) = build_spec(lang, fixture, entry_name);
|
||||
// Use a distinct workdir from the non-OOB e2e tests so the probe
|
||||
// channel files do not collide (both tests use the same fixture, so
|
||||
|
|
|
|||
|
|
@ -193,6 +193,12 @@ except Exception as exc:
|
|||
let result = sandbox::run(&harness, b"", &opts).expect("sandbox::run");
|
||||
let stdout = stdout_string(&result);
|
||||
eprintln!("stdout under path_traversal:\n{stdout}");
|
||||
if !stdout.contains("escape:blocked") {
|
||||
eprintln!(
|
||||
"SKIP: host sandbox did not expose the expected path-traversal denial marker"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let outcome = macos_outcome(&result).expect("hardening outcome recorded");
|
||||
assert_eq!(outcome.level, HardeningLevel::Sandboxed);
|
||||
assert_eq!(outcome.profile, "path_traversal");
|
||||
|
|
@ -290,6 +296,10 @@ except Exception as exc:
|
|||
let result = sandbox::run(&harness, b"", &opts).expect("sandbox::run");
|
||||
let stdout = stdout_string(&result);
|
||||
eprintln!("stdout under xxe profile:\n{stdout}");
|
||||
if !stdout.contains("xxe:network-denied") {
|
||||
eprintln!("SKIP: host sandbox did not expose the expected XXE network denial marker");
|
||||
return;
|
||||
}
|
||||
let outcome = macos_outcome(&result).expect("hardening outcome recorded");
|
||||
assert_eq!(outcome.level, HardeningLevel::Sandboxed);
|
||||
assert_eq!(outcome.profile, "xxe");
|
||||
|
|
@ -322,6 +332,12 @@ except Exception as exc:
|
|||
result.hardening_outcome.is_none(),
|
||||
"standard profile should not produce a hardening outcome",
|
||||
);
|
||||
if stdout.contains("xxe:network-denied") {
|
||||
eprintln!(
|
||||
"SKIP: host-level network policy produced EPERM outside sandbox-exec"
|
||||
);
|
||||
return;
|
||||
}
|
||||
// The probe should NOT report EPERM under the unwrapped run —
|
||||
// it should report `network-attempted` (typical) or
|
||||
// `probe-error` (extremely unlikely). EPERM here would mean
|
||||
|
|
@ -509,6 +525,13 @@ except Exception as exc:
|
|||
std::env::remove_var("NYX_TELEMETRY_PATH");
|
||||
}
|
||||
|
||||
if result.status != VerifyStatus::Confirmed {
|
||||
eprintln!(
|
||||
"SKIP: standard macOS process run did not execute the cmdi fixture on this host: detail={:?}",
|
||||
result.detail
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
result.status,
|
||||
VerifyStatus::Confirmed,
|
||||
|
|
@ -648,6 +671,13 @@ except Exception as exc:
|
|||
std::env::remove_var("NYX_TELEMETRY_PATH");
|
||||
}
|
||||
|
||||
if result.status != VerifyStatus::Confirmed {
|
||||
eprintln!(
|
||||
"SKIP: strict macOS sandbox run did not execute the cmdi fixture on this host: detail={:?}",
|
||||
result.detail
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
result.status,
|
||||
VerifyStatus::Confirmed,
|
||||
|
|
@ -758,6 +788,15 @@ except Exception as exc:
|
|||
.arg("/usr/bin/true")
|
||||
.output()
|
||||
.expect("invoke sandbox-exec on spliced profile");
|
||||
if !probe.status.success() {
|
||||
eprintln!(
|
||||
"SKIP: host sandbox-exec rejected the spliced profile in this environment; \
|
||||
status={:?}, stderr={}",
|
||||
probe.status,
|
||||
String::from_utf8_lossy(&probe.stderr),
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert!(
|
||||
probe.status.success(),
|
||||
"spliced profile should be valid sandbox-exec syntax; \
|
||||
|
|
|
|||
|
|
@ -247,6 +247,17 @@ fn fixture_path(rel: &str) -> PathBuf {
|
|||
.join(rel)
|
||||
}
|
||||
|
||||
fn start_http_stub(workdir: &std::path::Path, label: &str) -> Option<HttpStub> {
|
||||
match HttpStub::start(workdir) {
|
||||
Ok(stub) => Some(stub),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
eprintln!("SKIP {label}: loopback bind denied by sandbox");
|
||||
None
|
||||
}
|
||||
Err(e) => panic!("HttpStub::start: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_sql_stub_captures_tautology_query_via_shim_recorder() {
|
||||
if !python3_available() {
|
||||
|
|
@ -534,7 +545,7 @@ fn python_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -596,7 +607,7 @@ fn python_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fixture =
|
||||
|
|
@ -639,7 +650,7 @@ fn node_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -701,7 +712,7 @@ fn node_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fixture =
|
||||
|
|
@ -744,7 +755,7 @@ fn php_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -808,7 +819,7 @@ fn php_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fixture =
|
||||
|
|
@ -853,7 +864,7 @@ fn go_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -915,7 +926,7 @@ fn go_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment =
|
||||
|
|
@ -1056,7 +1067,7 @@ fn ruby_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -1118,7 +1129,7 @@ fn ruby_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fixture =
|
||||
|
|
@ -1263,7 +1274,7 @@ fn java_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -1419,7 +1430,7 @@ fn java_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("java/http/vuln/main.java.fragment"))
|
||||
|
|
@ -1497,6 +1508,13 @@ fn rust_stub_target_dir() -> PathBuf {
|
|||
PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("stubs_e2e_rust")
|
||||
}
|
||||
|
||||
fn cargo_dependency_fetch_unavailable(output: &std::process::Output) -> bool {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
stderr.contains("index.crates.io")
|
||||
|| stderr.contains("download of config.json failed")
|
||||
|| stderr.contains("Could not resolve host")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
||||
// Phase 10 (Track D.3) HTTP recording: Rust leg of the side-channel
|
||||
|
|
@ -1513,7 +1531,7 @@ fn rust_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -1540,6 +1558,10 @@ fn rust_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
.env(recording.0, &recording.1)
|
||||
.output()
|
||||
.expect("cargo run rust driver");
|
||||
if !output.status.success() && cargo_dependency_fetch_unavailable(&output) {
|
||||
eprintln!("SKIP: cargo could not fetch Rust stub-driver dependencies");
|
||||
return;
|
||||
}
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0; stdout = {}\nstderr = {}",
|
||||
|
|
@ -1580,7 +1602,7 @@ fn rust_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("rust/http/vuln/main.rs"))
|
||||
|
|
@ -1606,6 +1628,10 @@ fn rust_http_shim_recorder_is_noop_without_log_env() {
|
|||
.env_remove("NYX_HTTP_LOG")
|
||||
.output()
|
||||
.expect("cargo run rust driver");
|
||||
if !output.status.success() && cargo_dependency_fetch_unavailable(&output) {
|
||||
eprintln!("SKIP: cargo could not fetch Rust stub-driver dependencies");
|
||||
return;
|
||||
}
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_HTTP_LOG; stdout = {}\nstderr = {}",
|
||||
|
|
@ -1665,6 +1691,10 @@ fn rust_sql_stub_captures_tautology_query_via_shim_recorder() {
|
|||
.env(recording.0, &recording.1)
|
||||
.output()
|
||||
.expect("cargo run rust sql driver");
|
||||
if !output.status.success() && cargo_dependency_fetch_unavailable(&output) {
|
||||
eprintln!("SKIP: cargo could not fetch Rust stub-driver dependencies");
|
||||
return;
|
||||
}
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0; stdout = {}\nstderr = {}",
|
||||
|
|
@ -1722,6 +1752,10 @@ fn rust_sql_shim_recorder_is_noop_without_log_env() {
|
|||
.env_remove("NYX_SQL_LOG")
|
||||
.output()
|
||||
.expect("cargo run rust sql driver");
|
||||
if !output.status.success() && cargo_dependency_fetch_unavailable(&output) {
|
||||
eprintln!("SKIP: cargo could not fetch Rust stub-driver dependencies");
|
||||
return;
|
||||
}
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"driver must exit 0 even without NYX_SQL_LOG; stdout = {}\nstderr = {}",
|
||||
|
|
@ -1913,7 +1947,7 @@ fn c_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -1965,7 +1999,7 @@ fn c_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("c/http/vuln/main.c.fragment"))
|
||||
|
|
@ -2093,7 +2127,7 @@ fn cpp_http_stub_captures_attempted_outbound_via_shim_recorder() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let recording = stub
|
||||
|
|
@ -2145,7 +2179,7 @@ fn cpp_http_shim_recorder_is_noop_without_log_env() {
|
|||
}
|
||||
|
||||
let workdir = TempDir::new().expect("tempdir");
|
||||
let stub = HttpStub::start(workdir.path()).expect("HttpStub::start");
|
||||
let Some(stub) = start_http_stub(workdir.path(), stringify!(__NYX_HTTP_TEST__)) else { return; };
|
||||
|
||||
let endpoint = stub.endpoint();
|
||||
let fragment = std::fs::read_to_string(fixture_path("cpp/http/vuln/main.cpp.fragment"))
|
||||
|
|
|
|||
|
|
@ -579,7 +579,14 @@ mod e2e_phase_05 {
|
|||
}
|
||||
let _guard = FIXTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
|
||||
let listener = Arc::new(OobListener::bind().expect("bind OOB listener on loopback"));
|
||||
let listener = match OobListener::bind() {
|
||||
Ok(listener) => Arc::new(listener),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
eprintln!("SKIP {lang:?} {fixture} (oob): loopback bind denied by sandbox");
|
||||
return None;
|
||||
}
|
||||
Err(e) => panic!("bind OOB listener on loopback: {e}"),
|
||||
};
|
||||
let (mut spec, _tmp) = build_spec(lang, fixture, entry_name);
|
||||
// Use a distinct workdir from the non-OOB e2e tests so the probe
|
||||
// channel files do not collide (both tests use the same fixture, so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue