415 lines · 13.9 KB · Rust
RawDownload
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"));}