#![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, 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 .parent() .expect("fake child parent") .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 { env::var_os("GHIDR_TEST_BWRAP") .map(PathBuf::from) .or_else(|| find_in_path("bwrap")) } fn find_in_path(executable: &str) -> Option { env::var_os("PATH").and_then(|path| { env::split_paths(&path) .map(|directory| directory.join(executable)) .find(|candidate| candidate.is_file()) }) }