feat: implement worker lifecycle and sandbox
This commit is contained in:
parent
170ca32f58
commit
b9b9bfe05a
13 changed files with 2456 additions and 7 deletions
114
tests/bubblewrap_probe.rs
Normal file
114
tests/bubblewrap_probe.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#![allow(clippy::expect_used)]
|
||||
#![doc = "Host-permitted production Bubblewrap network-isolation probe."]
|
||||
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
net::TcpListener,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use ghidra_cli::{
|
||||
cli::SandboxMode,
|
||||
sandbox::{BubblewrapConfig, WorkerCommand},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn production_bubblewrap_command_cannot_reach_host_listener_when_supported() {
|
||||
let Some(bubblewrap) = bubblewrap_path() else {
|
||||
return;
|
||||
};
|
||||
let fake_child = PathBuf::from(env!("CARGO_BIN_EXE_ghidr-fake-child"));
|
||||
let Ok(listener) = TcpListener::bind("127.0.0.1:0") else {
|
||||
// Some CI sandboxes prohibit even loopback listeners.
|
||||
return;
|
||||
};
|
||||
let address = listener.local_addr().expect("listener address").to_string();
|
||||
|
||||
let direct = Command::new(&fake_child)
|
||||
.args(["network-probe", &address])
|
||||
.status()
|
||||
.expect("direct probe process");
|
||||
assert!(
|
||||
direct.success(),
|
||||
"control probe must reach the host listener"
|
||||
);
|
||||
|
||||
let private = tempfile::tempdir().expect("sandbox private directory");
|
||||
let home = private.path().join("home");
|
||||
let temporary = private.path().join("tmp");
|
||||
std::fs::create_dir(&home).expect("private home");
|
||||
std::fs::create_dir(&temporary).expect("private tmp");
|
||||
|
||||
let benign = bubblewrap_command(
|
||||
&bubblewrap,
|
||||
&fake_child,
|
||||
vec![OsString::from("exit-success")],
|
||||
&home,
|
||||
&temporary,
|
||||
);
|
||||
let benign_status = Command::new(&benign.program)
|
||||
.args(&benign.arguments)
|
||||
.status()
|
||||
.expect("Bubblewrap capability probe");
|
||||
if !benign_status.success() {
|
||||
// User namespaces are legitimately unavailable on some test hosts.
|
||||
return;
|
||||
}
|
||||
|
||||
let isolated = bubblewrap_command(
|
||||
&bubblewrap,
|
||||
&fake_child,
|
||||
vec![OsString::from("network-probe"), OsString::from(address)],
|
||||
&home,
|
||||
&temporary,
|
||||
);
|
||||
let isolated_status = Command::new(&isolated.program)
|
||||
.args(&isolated.arguments)
|
||||
.status()
|
||||
.expect("isolated probe");
|
||||
assert!(
|
||||
!isolated_status.success(),
|
||||
"worker unexpectedly reached a host listener across --unshare-all"
|
||||
);
|
||||
}
|
||||
|
||||
fn bubblewrap_command(
|
||||
bubblewrap: &Path,
|
||||
fake_child: &Path,
|
||||
worker_arguments: Vec<OsString>,
|
||||
home: &Path,
|
||||
temporary: &Path,
|
||||
) -> WorkerCommand {
|
||||
WorkerCommand::for_policy(
|
||||
SandboxMode::Bubblewrap,
|
||||
fake_child.to_path_buf(),
|
||||
worker_arguments.clone(),
|
||||
Some(BubblewrapConfig {
|
||||
bubblewrap: bubblewrap.to_path_buf(),
|
||||
worker_program: fake_child.to_path_buf(),
|
||||
worker_arguments,
|
||||
readonly_paths: vec![PathBuf::from("/nix/store"), fake_child.to_path_buf()],
|
||||
staged_sample: None,
|
||||
writable_paths: Vec::new(),
|
||||
private_home: home.to_path_buf(),
|
||||
private_tmp: temporary.to_path_buf(),
|
||||
}),
|
||||
)
|
||||
.expect("production Bubblewrap command")
|
||||
}
|
||||
|
||||
fn bubblewrap_path() -> Option<PathBuf> {
|
||||
env::var_os("GHIDR_TEST_BWRAP")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| find_in_path("bwrap"))
|
||||
}
|
||||
|
||||
fn find_in_path(executable: &str) -> Option<PathBuf> {
|
||||
env::var_os("PATH").and_then(|path| {
|
||||
env::split_paths(&path)
|
||||
.map(|directory| directory.join(executable))
|
||||
.find(|candidate| candidate.is_file())
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue