pub fn assert_proc_and_sysfs_mounted(mountinfo: &str) -> Result<(), String> {
let mut saw_proc = false;
let mut saw_sysfs = false;
for line in mountinfo.lines() {
if line.trim().is_empty() {
continue;
}
let Some((pre, post)) = split_mountinfo_line(line) else {
continue;
};
let pre_fields: Vec<&str> = pre.split_whitespace().collect();
let post_fields: Vec<&str> = post.split_whitespace().collect();
if pre_fields.len() < 5 || post_fields.is_empty() {
continue;
}
let mount_point = pre_fields[4];
let fs_type = post_fields[0];
if fs_type == "proc" && mount_point == "/proc" {
saw_proc = true;
}
if fs_type == "sysfs" && mount_point == "/sys" {
saw_sysfs = true;
}
}
match (saw_proc, saw_sysfs) {
(true, true) => Ok(()),
(false, true) => Err(
"FC-13 violation: /proc/self/mountinfo has NO `proc` line on /proc. \
cellos-init::mount_proc_and_sys() did not mount /proc before workload \
exec — or mounted it after fork(). Re-check L241–L261 of \
crates/cellos-init/src/main.rs."
.to_string(),
),
(true, false) => Err(
"FC-13 violation: /proc/self/mountinfo has NO `sysfs` line on /sys. \
cellos-init::mount_proc_and_sys() did not mount /sys before workload \
exec. Re-check L263–L280 of crates/cellos-init/src/main.rs."
.to_string(),
),
(false, false) => Err(
"FC-13 violation: /proc/self/mountinfo has NEITHER a `proc` line on \
/proc NOR a `sysfs` line on /sys. cellos-init::mount_proc_and_sys() \
apparently never ran — the workload was exec'd against a bare rootfs. \
This is the SEAM-13 regression."
.to_string(),
),
}
}
fn split_mountinfo_line(line: &str) -> Option<(&str, &str)> {
let (pre, post) = line.split_once(" - ")?;
Some((pre, post))
}
pub fn assert_sys_devices_listing_nonempty(listing: &str) -> Result<(), String> {
let entries: Vec<&str> = listing
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.collect();
if entries.is_empty() {
return Err(
"FC-13 violation: captured /sys/devices listing is empty. /sys was \
either not mounted or mounted as an empty tmpfs. On a real Linux \
system /sys/devices contains kernel device-class roots (block, \
system, virtual, etc.) so an empty listing here is unambiguous."
.to_string(),
);
}
Ok(())
}
pub fn assert_workload_exit_zero(exit_code: i32) -> Result<(), String> {
if exit_code != 0 {
return Err(format!(
"FC-13 violation: workload exited {exit_code} (expected 0). \
The shell chain `cat /proc/self/mountinfo && ls /sys/devices && \
exit 0; exit 1` short-circuits to exit 1 only when one of \
/proc or /sys is unreadable. cellos-init::mount_proc_and_sys() \
did not establish the mounts before the workload ran."
));
}
Ok(())
}
#[test]
fn mountinfo_predicate_passes_on_canonical_capture() {
let mi = "\
21 19 0:20 / /proc rw,relatime shared:7 - proc proc rw
22 19 0:21 / /sys rw,relatime shared:8 - sysfs sysfs rw
23 19 0:22 / /dev rw,relatime - devtmpfs devtmpfs rw
";
assert_proc_and_sysfs_mounted(mi).expect("canonical capture must pass");
}
#[test]
fn mountinfo_predicate_passes_without_optional_tags() {
let mi = "\
21 19 0:20 / /proc rw,relatime - proc proc rw
22 19 0:21 / /sys rw,relatime - sysfs sysfs rw
";
assert_proc_and_sysfs_mounted(mi).expect("optional-fields-absent must pass");
}
#[test]
fn mountinfo_predicate_fails_when_proc_missing() {
let mi = "22 19 0:21 / /sys rw,relatime - sysfs sysfs rw\n";
let err = assert_proc_and_sysfs_mounted(mi).expect_err("missing /proc must fail");
assert!(err.contains("FC-13 violation"), "got: {err}");
assert!(err.contains("NO `proc` line"), "got: {err}");
}
#[test]
fn mountinfo_predicate_fails_when_sysfs_missing() {
let mi = "21 19 0:20 / /proc rw,relatime - proc proc rw\n";
let err = assert_proc_and_sysfs_mounted(mi).expect_err("missing /sys must fail");
assert!(err.contains("FC-13 violation"), "got: {err}");
assert!(err.contains("NO `sysfs` line"), "got: {err}");
}
#[test]
fn mountinfo_predicate_fails_when_both_missing() {
let mi = "23 19 0:22 / /dev rw,relatime - devtmpfs devtmpfs rw\n";
let err = assert_proc_and_sysfs_mounted(mi).expect_err("missing both must fail");
assert!(err.contains("FC-13 violation"), "got: {err}");
assert!(err.contains("SEAM-13"), "got: {err}");
}
#[test]
fn mountinfo_predicate_rejects_proc_on_wrong_mount_point() {
let mi = "\
21 19 0:20 / /tmp/proc rw,relatime - proc proc rw
22 19 0:21 / /sys rw,relatime - sysfs sysfs rw
";
let err = assert_proc_and_sysfs_mounted(mi).expect_err("proc on wrong mount point must fail");
assert!(err.contains("NO `proc` line"), "got: {err}");
}
#[test]
fn mountinfo_predicate_rejects_sysfs_on_wrong_mount_point() {
let mi = "\
21 19 0:20 / /proc rw,relatime - proc proc rw
22 19 0:21 / /tmp/sys rw,relatime - sysfs sysfs rw
";
let err = assert_proc_and_sysfs_mounted(mi).expect_err("sysfs on wrong mount point must fail");
assert!(err.contains("NO `sysfs` line"), "got: {err}");
}
#[test]
fn mountinfo_predicate_tolerates_blank_and_malformed_lines() {
let mi = "\nthis-line-has-no-marker-and-must-be-skipped\n\
21 19 0:20 / /proc rw,relatime - proc proc rw\n\n\
22 19 0:21 / /sys rw,relatime - sysfs sysfs rw\n\n";
assert_proc_and_sysfs_mounted(mi).expect("blank + malformed lines must not break parser");
}
#[test]
fn sys_devices_listing_predicate_accepts_canonical_listing() {
let listing = "block\nsystem\nvirtual\n";
assert_sys_devices_listing_nonempty(listing).expect("canonical listing must pass");
}
#[test]
fn sys_devices_listing_predicate_accepts_single_entry() {
assert_sys_devices_listing_nonempty("system\n").expect("single-entry listing must pass");
}
#[test]
fn sys_devices_listing_predicate_rejects_empty() {
let err = assert_sys_devices_listing_nonempty("").expect_err("empty listing must fail");
assert!(err.contains("FC-13 violation"), "got: {err}");
assert!(err.contains("/sys/devices"), "got: {err}");
}
#[test]
fn sys_devices_listing_predicate_rejects_whitespace_only() {
let err = assert_sys_devices_listing_nonempty("\n \n\t\n")
.expect_err("whitespace-only listing must fail");
assert!(err.contains("FC-13 violation"), "got: {err}");
}
#[test]
fn workload_exit_predicate_accepts_zero() {
assert_workload_exit_zero(0).expect("exit 0 must pass");
}
#[test]
fn workload_exit_predicate_rejects_one() {
let err = assert_workload_exit_zero(1).expect_err("exit 1 must fail");
assert!(err.contains("FC-13 violation"), "got: {err}");
assert!(err.contains("workload exited 1"), "got: {err}");
}
#[test]
fn workload_exit_predicate_rejects_signal_terminations() {
let err = assert_workload_exit_zero(137).expect_err("SIGKILL-coded exit must fail");
assert!(err.contains("workload exited 137"), "got: {err}");
}
#[cfg(target_os = "linux")]
fn fc13_e2e_opted_in() -> bool {
std::env::var("CELLOS_FIRECRACKER_FC13_E2E")
.map(|v| v.trim() == "1")
.unwrap_or(false)
}
#[cfg(target_os = "linux")]
fn read_fixture_file(env_var: &str) -> String {
let path = std::env::var(env_var).unwrap_or_else(|_| {
panic!(
"{env_var} must be set when CELLOS_FIRECRACKER_FC13_E2E=1; the \
firecracker-e2e workflow's FC-13 stage drops this file"
)
});
if !std::path::Path::new(&path).exists() {
panic!(
"FC-13 fixture at {path:?} (env {env_var}) does not exist. \
The firecracker-e2e workflow must have produced this file \
before invoking `cargo test -p cellos-host-firecracker --test \
mountinfo_assertion`. Most likely cause: the in-VM workload \
did not reach `exit 0` (so the && chain short-circuited and \
the redirect target was never written) — which is itself the \
FC-13 negative case. Check the workload's exit code first."
);
}
std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read FC-13 fixture at {path:?} (env {env_var}): {e}"))
}
#[cfg(target_os = "linux")]
fn read_workload_exit_code() -> i32 {
let raw = std::env::var("CELLOS_FIRECRACKER_FC13_EXIT_CODE").unwrap_or_else(|_| {
panic!(
"CELLOS_FIRECRACKER_FC13_EXIT_CODE must be set when \
CELLOS_FIRECRACKER_FC13_E2E=1 — the firecracker-e2e \
workflow forwards the workload's vsock-reported exit code \
into this env var so the assertion can match the brief's \
shell-chain contract"
)
});
raw.trim().parse::<i32>().unwrap_or_else(|e| {
panic!(
"CELLOS_FIRECRACKER_FC13_EXIT_CODE={raw:?} is not a valid \
decimal i32: {e}"
)
})
}
#[cfg(target_os = "linux")]
#[test]
fn fc13_proc_and_sys_mounted_before_workload_exec_e2e() {
if !fc13_e2e_opted_in() {
eprintln!(
"skipping FC-13 e2e mountinfo assertion: \
CELLOS_FIRECRACKER_FC13_E2E not set. This is expected \
outside the firecracker-e2e CI workflow."
);
return;
}
let exit_code = read_workload_exit_code();
assert_workload_exit_zero(exit_code).unwrap_or_else(|e| {
panic!(
"FC-13 workload exit-code invariant failed: {e}\n\
This means the in-VM `cat /proc/self/mountinfo && ls \
/sys/devices && exit 0; exit 1` chain fell through to \
`exit 1` — at least one of /proc or /sys was unreadable \
when the workload ran."
)
});
let mountinfo = read_fixture_file("CELLOS_FIRECRACKER_FC13_MOUNTINFO_PATH");
assert_proc_and_sysfs_mounted(&mountinfo).unwrap_or_else(|e| {
panic!(
"FC-13 mountinfo invariant failed: {e}\n\
----- captured /proc/self/mountinfo -----\n{mountinfo}\n\
----- end -----"
)
});
let sys_devices = read_fixture_file("CELLOS_FIRECRACKER_FC13_SYS_DEVICES_PATH");
assert_sys_devices_listing_nonempty(&sys_devices).unwrap_or_else(|e| {
panic!(
"FC-13 /sys/devices listing invariant failed: {e}\n\
----- captured /sys/devices listing -----\n{sys_devices}\n\
----- end -----"
)
});
}
#[cfg(target_os = "linux")]
#[test]
fn fc13_negative_no_mount_workload_fails_distinguishably_e2e() {
let opt_in = std::env::var("CELLOS_FIRECRACKER_FC13_NEGATIVE")
.map(|v| v.trim() == "1")
.unwrap_or(false);
if !opt_in {
eprintln!(
"skipping FC-13 negative-leg e2e: \
CELLOS_FIRECRACKER_FC13_NEGATIVE not set. This is expected \
outside the firecracker-e2e CI workflow's failure-injection \
stage."
);
return;
}
let exit_code = read_workload_exit_code();
if exit_code == 0 {
panic!(
"FC-13 negative-leg violation: workload exited 0 even though \
the failure-injection harness disabled mount_proc_and_sys(). \
This means /proc and /sys appear to be mounted by some other \
code path — investigate before trusting the positive-leg \
evidence. exit_code={exit_code}"
);
}
}