use agentos_kernel::bridge::LifecycleState;use agentos_kernel::command_registry::CommandDriver;use agentos_kernel::kernel::{KernelVm, KernelVmConfig, SpawnOptions};use agentos_kernel::permissions::Permissions;use agentos_kernel::process_table::SIGPIPE;use agentos_kernel::pty::LineDisciplineConfig;use agentos_kernel::vfs::MemoryFileSystem;use std::time::Duration; #[test]fn minimal_vm_lifecycle_transitions_between_ready_busy_and_terminated() { let mut config = KernelVmConfig::new("vm-kernel"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); assert_eq!(kernel.state(), LifecycleState::Ready); let process = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell"); assert_eq!(kernel.state(), LifecycleState::Busy); let (master_fd, slave_fd, path) = kernel.open_pty("shell", process.pid()).expect("open pty"); assert!(path.starts_with("/dev/pts/")); kernel .pty_set_discipline( "shell", process.pid(), master_fd, LineDisciplineConfig { canonical: Some(false), echo: Some(false), isig: Some(false), ..Default::default() }, ) .expect("set raw mode"); kernel .fd_write("shell", process.pid(), master_fd, b"kernel-ready") .expect("write PTY input"); let data = kernel .fd_read("shell", process.pid(), slave_fd, 64) .expect("read PTY slave"); assert_eq!(String::from_utf8(data).expect("valid utf8"), "kernel-ready"); process.finish(0); let (_, exit_code) = kernel.wait_and_reap(process.pid()).expect("reap shell"); assert_eq!(exit_code, 0); assert_eq!(kernel.state(), LifecycleState::Ready); assert_eq!(kernel.resource_snapshot().fd_tables, 0); assert_eq!(kernel.resource_snapshot().open_fds, 0); kernel.dispose().expect("dispose kernel"); assert_eq!(kernel.state(), LifecycleState::Terminated);} #[test]fn raw_mode_recovery_lease_is_limited_to_foreground_process_group() { let mut config = KernelVmConfig::new("vm-pty-raw-owner"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let shell = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell"); let (master_fd, slave_fd, _) = kernel .open_pty("shell", shell.pid()) .expect("open controlling pty"); kernel .fd_dup2("shell", shell.pid(), slave_fd, 0) .expect("install shell stdin"); kernel .pty_set_foreground_pgid("shell", shell.pid(), master_fd, shell.pid()) .expect("make shell group foreground"); let foreground_child = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), parent_pid: Some(shell.pid()), ..SpawnOptions::default() }, ) .expect("spawn foreground child"); assert!(kernel .pty_set_raw_mode("shell", foreground_child.pid(), 0, true) .expect("foreground raw mode") .is_some()); let background_child = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), parent_pid: Some(shell.pid()), ..SpawnOptions::default() }, ) .expect("spawn background child"); kernel .setpgid("shell", background_child.pid(), background_child.pid()) .expect("move child into background process group"); assert_eq!( kernel .pty_set_raw_mode("shell", background_child.pid(), 0, true) .expect("background raw mode"), None, "background process must not own foreground recovery" );} #[test]fn dispose_kills_running_processes_and_cleans_special_resources() { let mut config = KernelVmConfig::new("vm-dispose"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let process = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn shell"); let _ = kernel.open_pipe("shell", process.pid()).expect("open pipe"); let _ = kernel.open_pty("shell", process.pid()).expect("open pty"); kernel.dispose().expect("dispose kernel"); assert_eq!(kernel.state(), LifecycleState::Terminated); assert_eq!(process.wait(Duration::from_millis(50)), Some(143)); assert_eq!(process.kill_signals(), vec![15]); let snapshot = kernel.resource_snapshot(); assert_eq!(snapshot.fd_tables, 0); assert_eq!(snapshot.open_fds, 0); assert_eq!(snapshot.pipes, 0); assert_eq!(snapshot.ptys, 0);} #[test]fn process_exit_cleanup_closes_pipe_writers_and_returns_eof_to_readers() { let mut config = KernelVmConfig::new("vm-process-exit-pipe"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let writer = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn writer"); let (read_fd, write_fd) = kernel .open_pipe("shell", writer.pid()) .expect("open writer pipe"); let reader = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), parent_pid: Some(writer.pid()), ..SpawnOptions::default() }, ) .expect("spawn reader"); kernel .fd_close("shell", reader.pid(), write_fd) .expect("close inherited write end"); kernel .fd_write("shell", writer.pid(), write_fd, b"before-exit") .expect("write pipe contents"); let bytes = kernel .fd_read("shell", reader.pid(), read_fd, 64) .expect("read pipe contents"); assert_eq!(String::from_utf8(bytes).expect("valid utf8"), "before-exit"); writer.finish(0); assert_eq!( kernel .open_pipe("shell", writer.pid()) .expect_err("exited writer should lose PID ownership") .code(), "ESRCH" ); let eof = kernel .fd_read("shell", reader.pid(), read_fd, 64) .expect("read EOF after writer exit"); assert!(eof.is_empty());} #[test]fn broken_pipe_writes_deliver_sigpipe_and_return_epipe() { let mut config = KernelVmConfig::new("vm-broken-pipe-sigpipe"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let writer = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn writer"); let (read_fd, write_fd) = kernel .open_pipe("shell", writer.pid()) .expect("open writer pipe"); kernel .fd_close("shell", writer.pid(), read_fd) .expect("close inherited read end"); let error = kernel .fd_write("shell", writer.pid(), write_fd, b"fail") .expect_err("broken pipe writes should fail"); assert_eq!(error.code(), "EPIPE"); assert_eq!(writer.kill_signals(), vec![SIGPIPE]); assert_eq!(writer.wait(Duration::from_millis(50)), Some(128 + SIGPIPE));} #[test]fn process_exit_cleanup_removes_fd_tables_before_and_after_reap() { let mut config = KernelVmConfig::new("vm-process-exit-fds"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); let process = kernel .spawn_process( "sh", Vec::new(), SpawnOptions { requester_driver: Some(String::from("shell")), ..SpawnOptions::default() }, ) .expect("spawn process"); let _ = kernel.open_pipe("shell", process.pid()).expect("open pipe"); let _ = kernel.open_pty("shell", process.pid()).expect("open pty"); process.finish(0); let snapshot_after_exit = kernel.resource_snapshot(); assert_eq!(snapshot_after_exit.fd_tables, 0); assert_eq!(snapshot_after_exit.open_fds, 0); assert_eq!(snapshot_after_exit.pipes, 0); assert_eq!(snapshot_after_exit.ptys, 0); let (_, exit_code) = kernel .wait_and_reap(process.pid()) .expect("wait and reap exited process"); assert_eq!(exit_code, 0); let snapshot_after_reap = kernel.resource_snapshot(); assert_eq!(snapshot_after_reap.fd_tables, 0); assert_eq!(snapshot_after_reap.open_fds, 0); assert_eq!( kernel .fd_stat("shell", process.pid(), 0) .expect_err("reaped process should not keep FD entries") .code(), "ESRCH" );} #[test]fn spawn_process_executes_shebang_scripts_with_registered_interpreters() { let mut config = KernelVmConfig::new("vm-shebang"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .register_driver(CommandDriver::new("node", ["node"])) .expect("register node"); kernel .write_file("/tmp/script.sh", b"#!/bin/sh -eu\necho shell\n".to_vec()) .expect("write shell script"); kernel .chmod("/tmp/script.sh", 0o755) .expect("chmod shell script"); let shell_process = kernel .spawn_process( "/tmp/script.sh", vec![String::from("arg")], SpawnOptions::default(), ) .expect("spawn shell script"); assert_eq!( kernel .read_file(&format!("/proc/{}/cmdline", shell_process.pid())) .expect("read shell cmdline"), b"sh\0-eu\0/tmp/script.sh\0arg\0".to_vec() ); kernel .write_file( "/tmp/script.mjs", b"#!/usr/bin/env node --trace-warnings\nconsole.log('node');\n".to_vec(), ) .expect("write node script"); kernel .chmod("/tmp/script.mjs", 0o755) .expect("chmod node script"); let node_process = kernel .spawn_process("/tmp/script.mjs", Vec::new(), SpawnOptions::default()) .expect("spawn node script"); assert_eq!( kernel .read_file(&format!("/proc/{}/cmdline", node_process.pid())) .expect("read node cmdline"), b"node\0--trace-warnings\0/tmp/script.mjs\0".to_vec() );} #[test]fn spawn_process_rejects_invalid_shebang_scripts() { let mut config = KernelVmConfig::new("vm-shebang-errors"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); kernel .register_driver(CommandDriver::new("shell", ["sh"])) .expect("register shell"); kernel .write_file("/tmp/missing.sh", b"#!/missing/interpreter\n".to_vec()) .expect("write missing-interpreter script"); kernel .chmod("/tmp/missing.sh", 0o755) .expect("chmod missing-interpreter script"); let missing = kernel .spawn_process("/tmp/missing.sh", Vec::new(), SpawnOptions::default()) .expect_err("missing interpreter should fail"); assert_eq!(missing.code(), "ENOENT"); let long_shebang = format!("#!/{0}\n", "a".repeat(256)); kernel .write_file("/tmp/long.sh", long_shebang.into_bytes()) .expect("write long-shebang script"); kernel .chmod("/tmp/long.sh", 0o755) .expect("chmod long-shebang script"); let long_error = kernel .spawn_process("/tmp/long.sh", Vec::new(), SpawnOptions::default()) .expect_err("overlong shebang should fail"); assert_eq!(long_error.code(), "ENOEXEC");} #[test]fn driver_registration_rejects_command_names_that_escape_bin_stubs() { let mut config = KernelVmConfig::new("vm-command-registry-traversal"); config.permissions = Permissions::allow_all(); let mut kernel = KernelVm::new(MemoryFileSystem::new(), config); let error = kernel .register_driver(CommandDriver::new("malicious", ["safe", "../escape"])) .expect_err("path-like command names should be rejected"); assert_eq!(error.code(), "EINVAL"); assert!( error.to_string().contains("invalid command name"), "unexpected error: {error}" ); assert!(!kernel.exists("/bin").expect("check /bin")); assert!(!kernel.exists("/bin/safe").expect("check safe stub")); assert!(!kernel.exists("/escape").expect("check escaped stub"));}
use agentos_kernel::bridge::LifecycleState;
use agentos_kernel::command_registry::CommandDriver;
use agentos_kernel::kernel::{KernelVm, KernelVmConfig, SpawnOptions};
use agentos_kernel::permissions::Permissions;
use agentos_kernel::process_table::SIGPIPE;
use agentos_kernel::pty::LineDisciplineConfig;
use agentos_kernel::vfs::MemoryFileSystem;
use std::time::Duration;
#[test]
fn minimal_vm_lifecycle_transitions_between_ready_busy_and_terminated() {
let mut config = KernelVmConfig::new("vm-kernel");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
assert_eq!(kernel.state(), LifecycleState::Ready);
let process = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
..SpawnOptions::default()
},
)
.expect("spawn shell");
assert_eq!(kernel.state(), LifecycleState::Busy);
let (master_fd, slave_fd, path) = kernel.open_pty("shell", process.pid()).expect("open pty");
assert!(path.starts_with("/dev/pts/"));
kernel
.pty_set_discipline(
"shell",
process.pid(),
master_fd,
LineDisciplineConfig {
canonical: Some(false),
echo: Some(false),
isig: Some(false),
..Default::default()
},
)
.expect("set raw mode");
kernel
.fd_write("shell", process.pid(), master_fd, b"kernel-ready")
.expect("write PTY input");
let data = kernel
.fd_read("shell", process.pid(), slave_fd, 64)
.expect("read PTY slave");
assert_eq!(String::from_utf8(data).expect("valid utf8"), "kernel-ready");
process.finish(0);
let (_, exit_code) = kernel.wait_and_reap(process.pid()).expect("reap shell");
assert_eq!(exit_code, 0);
assert_eq!(kernel.state(), LifecycleState::Ready);
assert_eq!(kernel.resource_snapshot().fd_tables, 0);
assert_eq!(kernel.resource_snapshot().open_fds, 0);
kernel.dispose().expect("dispose kernel");
assert_eq!(kernel.state(), LifecycleState::Terminated);
}
#[test]
fn raw_mode_recovery_lease_is_limited_to_foreground_process_group() {
let mut config = KernelVmConfig::new("vm-pty-raw-owner");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
let shell = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
..SpawnOptions::default()
},
)
.expect("spawn shell");
let (master_fd, slave_fd, _) = kernel
.open_pty("shell", shell.pid())
.expect("open controlling pty");
kernel
.fd_dup2("shell", shell.pid(), slave_fd, 0)
.expect("install shell stdin");
kernel
.pty_set_foreground_pgid("shell", shell.pid(), master_fd, shell.pid())
.expect("make shell group foreground");
let foreground_child = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
parent_pid: Some(shell.pid()),
..SpawnOptions::default()
},
)
.expect("spawn foreground child");
assert!(kernel
.pty_set_raw_mode("shell", foreground_child.pid(), 0, true)
.expect("foreground raw mode")
.is_some());
let background_child = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
parent_pid: Some(shell.pid()),
..SpawnOptions::default()
},
)
.expect("spawn background child");
kernel
.setpgid("shell", background_child.pid(), background_child.pid())
.expect("move child into background process group");
assert_eq!(
kernel
.pty_set_raw_mode("shell", background_child.pid(), 0, true)
.expect("background raw mode"),
None,
"background process must not own foreground recovery"
);
}
#[test]
fn dispose_kills_running_processes_and_cleans_special_resources() {
let mut config = KernelVmConfig::new("vm-dispose");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
let process = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
..SpawnOptions::default()
},
)
.expect("spawn shell");
let _ = kernel.open_pipe("shell", process.pid()).expect("open pipe");
let _ = kernel.open_pty("shell", process.pid()).expect("open pty");
kernel.dispose().expect("dispose kernel");
assert_eq!(kernel.state(), LifecycleState::Terminated);
assert_eq!(process.wait(Duration::from_millis(50)), Some(143));
assert_eq!(process.kill_signals(), vec![15]);
let snapshot = kernel.resource_snapshot();
assert_eq!(snapshot.fd_tables, 0);
assert_eq!(snapshot.open_fds, 0);
assert_eq!(snapshot.pipes, 0);
assert_eq!(snapshot.ptys, 0);
}
#[test]
fn process_exit_cleanup_closes_pipe_writers_and_returns_eof_to_readers() {
let mut config = KernelVmConfig::new("vm-process-exit-pipe");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
let writer = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
..SpawnOptions::default()
},
)
.expect("spawn writer");
let (read_fd, write_fd) = kernel
.open_pipe("shell", writer.pid())
.expect("open writer pipe");
let reader = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
parent_pid: Some(writer.pid()),
..SpawnOptions::default()
},
)
.expect("spawn reader");
kernel
.fd_close("shell", reader.pid(), write_fd)
.expect("close inherited write end");
kernel
.fd_write("shell", writer.pid(), write_fd, b"before-exit")
.expect("write pipe contents");
let bytes = kernel
.fd_read("shell", reader.pid(), read_fd, 64)
.expect("read pipe contents");
assert_eq!(String::from_utf8(bytes).expect("valid utf8"), "before-exit");
writer.finish(0);
assert_eq!(
kernel
.open_pipe("shell", writer.pid())
.expect_err("exited writer should lose PID ownership")
.code(),
"ESRCH"
);
let eof = kernel
.fd_read("shell", reader.pid(), read_fd, 64)
.expect("read EOF after writer exit");
assert!(eof.is_empty());
}
#[test]
fn broken_pipe_writes_deliver_sigpipe_and_return_epipe() {
let mut config = KernelVmConfig::new("vm-broken-pipe-sigpipe");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
let writer = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
..SpawnOptions::default()
},
)
.expect("spawn writer");
let (read_fd, write_fd) = kernel
.open_pipe("shell", writer.pid())
.expect("open writer pipe");
kernel
.fd_close("shell", writer.pid(), read_fd)
.expect("close inherited read end");
let error = kernel
.fd_write("shell", writer.pid(), write_fd, b"fail")
.expect_err("broken pipe writes should fail");
assert_eq!(error.code(), "EPIPE");
assert_eq!(writer.kill_signals(), vec![SIGPIPE]);
assert_eq!(writer.wait(Duration::from_millis(50)), Some(128 + SIGPIPE));
}
#[test]
fn process_exit_cleanup_removes_fd_tables_before_and_after_reap() {
let mut config = KernelVmConfig::new("vm-process-exit-fds");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
let process = kernel
.spawn_process(
"sh",
Vec::new(),
SpawnOptions {
requester_driver: Some(String::from("shell")),
..SpawnOptions::default()
},
)
.expect("spawn process");
let _ = kernel.open_pipe("shell", process.pid()).expect("open pipe");
let _ = kernel.open_pty("shell", process.pid()).expect("open pty");
process.finish(0);
let snapshot_after_exit = kernel.resource_snapshot();
assert_eq!(snapshot_after_exit.fd_tables, 0);
assert_eq!(snapshot_after_exit.open_fds, 0);
assert_eq!(snapshot_after_exit.pipes, 0);
assert_eq!(snapshot_after_exit.ptys, 0);
let (_, exit_code) = kernel
.wait_and_reap(process.pid())
.expect("wait and reap exited process");
assert_eq!(exit_code, 0);
let snapshot_after_reap = kernel.resource_snapshot();
assert_eq!(snapshot_after_reap.fd_tables, 0);
assert_eq!(snapshot_after_reap.open_fds, 0);
assert_eq!(
kernel
.fd_stat("shell", process.pid(), 0)
.expect_err("reaped process should not keep FD entries")
.code(),
"ESRCH"
);
}
#[test]
fn spawn_process_executes_shebang_scripts_with_registered_interpreters() {
let mut config = KernelVmConfig::new("vm-shebang");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
kernel
.register_driver(CommandDriver::new("node", ["node"]))
.expect("register node");
kernel
.write_file("/tmp/script.sh", b"#!/bin/sh -eu\necho shell\n".to_vec())
.expect("write shell script");
kernel
.chmod("/tmp/script.sh", 0o755)
.expect("chmod shell script");
let shell_process = kernel
.spawn_process(
"/tmp/script.sh",
vec![String::from("arg")],
SpawnOptions::default(),
)
.expect("spawn shell script");
assert_eq!(
kernel
.read_file(&format!("/proc/{}/cmdline", shell_process.pid()))
.expect("read shell cmdline"),
b"sh\0-eu\0/tmp/script.sh\0arg\0".to_vec()
);
kernel
.write_file(
"/tmp/script.mjs",
b"#!/usr/bin/env node --trace-warnings\nconsole.log('node');\n".to_vec(),
)
.expect("write node script");
kernel
.chmod("/tmp/script.mjs", 0o755)
.expect("chmod node script");
let node_process = kernel
.spawn_process("/tmp/script.mjs", Vec::new(), SpawnOptions::default())
.expect("spawn node script");
assert_eq!(
kernel
.read_file(&format!("/proc/{}/cmdline", node_process.pid()))
.expect("read node cmdline"),
b"node\0--trace-warnings\0/tmp/script.mjs\0".to_vec()
);
}
#[test]
fn spawn_process_rejects_invalid_shebang_scripts() {
let mut config = KernelVmConfig::new("vm-shebang-errors");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
kernel
.register_driver(CommandDriver::new("shell", ["sh"]))
.expect("register shell");
kernel
.write_file("/tmp/missing.sh", b"#!/missing/interpreter\n".to_vec())
.expect("write missing-interpreter script");
kernel
.chmod("/tmp/missing.sh", 0o755)
.expect("chmod missing-interpreter script");
let missing = kernel
.spawn_process("/tmp/missing.sh", Vec::new(), SpawnOptions::default())
.expect_err("missing interpreter should fail");
assert_eq!(missing.code(), "ENOENT");
let long_shebang = format!("#!/{0}\n", "a".repeat(256));
kernel
.write_file("/tmp/long.sh", long_shebang.into_bytes())
.expect("write long-shebang script");
kernel
.chmod("/tmp/long.sh", 0o755)
.expect("chmod long-shebang script");
let long_error = kernel
.spawn_process("/tmp/long.sh", Vec::new(), SpawnOptions::default())
.expect_err("overlong shebang should fail");
assert_eq!(long_error.code(), "ENOEXEC");
}
#[test]
fn driver_registration_rejects_command_names_that_escape_bin_stubs() {
let mut config = KernelVmConfig::new("vm-command-registry-traversal");
config.permissions = Permissions::allow_all();
let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
let error = kernel
.register_driver(CommandDriver::new("malicious", ["safe", "../escape"]))
.expect_err("path-like command names should be rejected");
assert_eq!(error.code(), "EINVAL");
assert!(
error.to_string().contains("invalid command name"),
"unexpected error: {error}"
);
assert!(!kernel.exists("/bin").expect("check /bin"));
assert!(!kernel.exists("/bin/safe").expect("check safe stub"));
assert!(!kernel.exists("/escape").expect("check escaped stub"));
}