#![cfg(all(target_os = "linux", feature = "backend-linux"))]
use bvisor::linux::launch::{self, AuthorityFd, LaunchObservation};
use bvisor::linux::protocol::{
DescriptorKind, DescriptorRole, DescriptorShape, DescriptorSlotV1, LinuxLaunchBodyV1,
LinuxLaunchPlanV1, LoweringWireEntryV1, LoweringWireV1, TargetSpecV1,
};
use bvisor::{AdmissionProgramHash, AttemptId, BackendProfileHash, BoundaryPlanHash};
use std::os::fd::{OwnedFd, RawFd};
use std::path::PathBuf;
const ID_AMBIENT_SCRUB: &str = "linux.ambient.scrub.v1";
const ID_EXEC: &str = "linux.exec.v1";
const PHASE_CODE_SCRUB: u8 = 3; const PHASE_CODE_EXEC: u8 = 5;
const SLOT_EXE: RawFd = 10;
fn launcher_path() -> PathBuf {
launch::resolve_launcher_path(env!("CARGO_BIN_EXE_bvisor-linux-launcher"))
}
fn entry(id: &str, phase_code: u8) -> LoweringWireEntryV1 {
LoweringWireEntryV1 {
id: id.to_owned(),
version: 1,
phase_code,
param_digest: [0u8; 32],
decl_digest: [0u8; 32],
}
}
fn exe_slot() -> DescriptorSlotV1 {
DescriptorSlotV1 {
slot_index: u32::try_from(SLOT_EXE).expect("fd fits u32"),
role: DescriptorRole::TargetExe,
expected: DescriptorShape {
kind: DescriptorKind::Regular,
writable: false,
},
}
}
fn exec_only_plan(argv: Vec<String>) -> LinuxLaunchPlanV1 {
let lowering = LoweringWireV1 {
entries: vec![
entry(ID_AMBIENT_SCRUB, PHASE_CODE_SCRUB),
entry(ID_EXEC, PHASE_CODE_EXEC),
],
};
let bytes = batpak::canonical::to_bytes(&lowering).expect("encode lowering");
let h_l = batpak::event::hash::compute_hash(&bytes);
let body = LinuxLaunchBodyV1 {
attempt_id: AttemptId([7u8; 32]),
plan_id: BoundaryPlanHash([1u8; 32]),
h_a: AdmissionProgramHash([2u8; 32]),
h_p: BackendProfileHash([3u8; 32]),
h_l,
lowering,
descriptor_table: vec![exe_slot()],
target: TargetSpecV1 {
argv,
envp: vec![("PATH".to_owned(), "/usr/bin:/bin".to_owned())],
exe_slot: u32::try_from(SLOT_EXE).expect("fd fits u32"),
user_namespace: None,
network_namespace: None,
seccomp: None,
},
};
LinuxLaunchPlanV1 { body }
}
fn exe_authority() -> AuthorityFd {
AuthorityFd {
slot_index: SLOT_EXE,
handle: OwnedFd::from(std::fs::File::open("/bin/sh").expect("open /bin/sh")),
}
}
fn run_marker_workload() -> LaunchObservation {
let argv = vec![
"sh".to_string(),
"-c".to_string(),
"printf OUT; printf ERR 1>&2".to_string(),
];
let plan = exec_only_plan(argv);
launch::run_launcher(&launcher_path(), &plan, vec![exe_authority()])
.expect("the launcher harness runs the marker workload to a verdict")
}
const LAUNCHER_DIAGNOSTICS: &[&str] = &[
"bvisor-linux-launcher",
"SetupRefused",
"SetupFaulted",
"launcher OS fault",
];
fn assert_clean_capture(obs: &LaunchObservation) {
let out = String::from_utf8_lossy(&obs.captured_stdout);
let err = String::from_utf8_lossy(&obs.captured_stderr);
assert!(
obs.exec_succeeded(),
"the exec-only marker workload must reach ExecSucceeded; terminal={:?} \
notes={:?}",
obs.terminal,
obs.notes
);
assert!(
out.contains("OUT"),
"captured stdout must contain the stdout marker OUT; got {out:?}"
);
assert!(
!out.contains("ERR"),
"captured stdout must NOT contain the stderr marker ERR (streams crossed); \
got {out:?}"
);
assert!(
err.contains("ERR"),
"captured stderr must contain the stderr marker ERR; got {err:?}"
);
assert!(
!err.contains("OUT"),
"captured stderr must NOT contain the stdout marker OUT (streams crossed); \
got {err:?}"
);
for diag in LAUNCHER_DIAGNOSTICS {
assert!(
!out.contains(diag),
"captured stdout must be free of launcher diagnostic {diag:?} (launcher \
not stdio-silent); got {out:?}"
);
assert!(
!err.contains(diag),
"captured stderr must be free of launcher diagnostic {diag:?} (launcher \
not stdio-silent); got {err:?}"
);
}
}
#[test]
fn launcher_captures_workload_streams_cleanly_and_deterministically() {
for iteration in 0..5 {
let obs = run_marker_workload();
if launch::launch_confinement_unavailable(&obs) {
use std::io::Write as _;
let mut sink = std::io::stderr();
let _ = writeln!(
sink,
"SKIP launcher_captures_workload_streams_cleanly_and_deterministically: \
kernel/container lacks landlock/userns/seccomp (ENOSYS); the launcher faulted \
before exec — exercised on capable kernels + the bvisor-linux CI lane"
);
return;
}
assert_clean_capture(&obs);
assert_eq!(
obs.captured_stdout, b"OUT",
"iteration {iteration}: captured stdout must be exactly the workload's \
`OUT` bytes (no launcher contamination, deterministic)"
);
assert_eq!(
obs.captured_stderr, b"ERR",
"iteration {iteration}: captured stderr must be exactly the workload's \
`ERR` bytes (no launcher contamination, deterministic)"
);
}
}
#[test]
fn large_workload_output_is_fully_captured_without_deadlock() {
const FLOOD_BYTES: usize = 256 * 1024;
let argv = vec![
"sh".to_string(),
"-c".to_string(),
format!("head -c {FLOOD_BYTES} /dev/zero | tr '\\0' 'X'"),
];
let plan = exec_only_plan(argv);
let obs = launch::run_launcher(&launcher_path(), &plan, vec![exe_authority()])
.expect("the launcher harness runs the flood workload to a verdict");
if launch::launch_confinement_unavailable(&obs) {
use std::io::Write as _;
let mut sink = std::io::stderr();
let _ = writeln!(
sink,
"SKIP large_workload_output_is_fully_captured_without_deadlock: kernel/container lacks \
landlock/userns/seccomp (ENOSYS); the launcher faulted before exec — exercised on \
capable kernels + the bvisor-linux CI lane"
);
return;
}
assert!(
obs.exec_succeeded(),
"the flood workload must reach ExecSucceeded; terminal={:?} notes={:?}",
obs.terminal,
obs.notes
);
assert_eq!(
obs.captured_stdout.len(),
FLOOD_BYTES,
"every flooded byte must be captured (no deadlock, no truncation)"
);
assert!(
obs.captured_stdout.iter().all(|&b| b == b'X'),
"the captured flood must be exactly the workload's bytes"
);
}