agentos-kernel 0.2.13

Shared kernel plane for secure-exec native and browser sidecars
Documentation
use agentos_kernel::fd_table::{O_DIRECTORY, O_RDONLY};
use agentos_kernel::kernel::{KernelVm, KernelVmConfig, VirtualProcessOptions};
use agentos_kernel::mount_table::MountTable;
use agentos_kernel::permissions::Permissions;
use agentos_kernel::root_fs::{RootFileSystem, RootFilesystemDescriptor, RootFilesystemMode};
use agentos_kernel::socket_table::SocketType;
use agentos_kernel::user::UserConfig;
use agentos_kernel::vfs::MemoryFileSystem;

const DRIVER: &str = "ownership-driver";
const UNCHANGED: u32 = u32::MAX;

fn kernel_and_pid() -> (KernelVm<MemoryFileSystem>, u32) {
    let mut config = KernelVmConfig::new("vm-ownership");
    config.permissions = Permissions::allow_all();
    config.user = UserConfig {
        supplementary_gids: vec![44],
        ..UserConfig::default()
    };
    let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
    let process = kernel
        .create_virtual_process(
            DRIVER,
            DRIVER,
            "ownership-check",
            Vec::new(),
            VirtualProcessOptions::default(),
        )
        .expect("create process");
    (kernel, process.pid())
}

#[test]
fn unprivileged_chown_matches_linux_owner_and_group_rules() {
    let (mut kernel, pid) = kernel_and_pid();
    kernel
        .write_file_for_process(DRIVER, pid, "/file", b"data", Some(0o6755))
        .expect("create owned file");

    kernel
        .chown_for_process(DRIVER, pid, "/file", UNCHANGED, 44, true)
        .expect("owner may select supplementary group");
    let stat = kernel.stat("/file").expect("stat changed file");
    assert_eq!((stat.uid, stat.gid), (1000, 44));
    assert_eq!(stat.mode & 0o7777, 0o755, "chown clears set-id bits");

    let error = kernel
        .chown_for_process(DRIVER, pid, "/file", 1001, UNCHANGED, true)
        .expect_err("unprivileged owner change must fail");
    assert_eq!(error.code(), "EPERM");
    let error = kernel
        .chown_for_process(DRIVER, pid, "/file", UNCHANGED, 55, true)
        .expect_err("unprivileged foreign group change must fail");
    assert_eq!(error.code(), "EPERM");

    kernel
        .chown("/file", 2000, 2000)
        .expect("seed foreign owner");
    kernel
        .chown_for_process(DRIVER, pid, "/file", UNCHANGED, UNCHANGED, true)
        .expect("Linux accepts an all-unchanged request on a foreign inode");
    let error = kernel
        .chown_for_process(DRIVER, pid, "/file", 2000, UNCHANGED, true)
        .expect_err("specifying even the existing uid requires ownership");
    assert_eq!(error.code(), "EPERM");
}

#[test]
fn chown_preserves_non_executable_setgid_like_linux() {
    let (mut kernel, pid) = kernel_and_pid();
    kernel
        .write_file_for_process(DRIVER, pid, "/mandatory-lock", b"data", Some(0o6745))
        .expect("create set-id file without group execute");

    kernel
        .chown_for_process(DRIVER, pid, "/mandatory-lock", UNCHANGED, 44, true)
        .expect("change file group");
    let stat = kernel.stat("/mandatory-lock").expect("stat changed file");
    assert_eq!((stat.uid, stat.gid), (1000, 44));
    assert_eq!(
        stat.mode & 0o7777,
        0o2745,
        "Linux clears setuid but preserves setgid without group execute"
    );
}

#[test]
fn chown_follows_but_lchown_mutates_the_symlink_inode() {
    let (mut kernel, pid) = kernel_and_pid();
    kernel
        .write_file_for_process(DRIVER, pid, "/target", b"data", None)
        .expect("create target");
    kernel.symlink("/target", "/link").expect("create link");

    kernel
        .chown_for_process(DRIVER, pid, "/link", UNCHANGED, 44, false)
        .expect("lchown link");
    assert_eq!(kernel.lstat("/link").expect("lstat link").gid, 44);
    assert_eq!(kernel.stat("/target").expect("stat target").gid, 1000);

    kernel
        .chown_for_process(DRIVER, pid, "/link", UNCHANGED, 44, true)
        .expect("chown target through link");
    assert_eq!(kernel.stat("/target").expect("stat target").gid, 44);
}

#[test]
fn fchown_updates_an_open_file_after_unlink() {
    let (mut kernel, pid) = kernel_and_pid();
    kernel
        .write_file_for_process(DRIVER, pid, "/open", b"data", Some(0o6755))
        .expect("create file");
    let fd = kernel
        .fd_open(DRIVER, pid, "/open", O_RDONLY, None)
        .expect("open file");
    kernel.remove_file("/open").expect("unlink open file");

    kernel
        .fd_chown_for_process(DRIVER, pid, fd, UNCHANGED, 44)
        .expect("fchown detached file");
    let stat = kernel
        .dev_fd_stat(DRIVER, pid, fd)
        .expect("fstat detached file");
    assert_eq!((stat.uid, stat.gid), (1000, 44));
    assert_eq!(stat.mode & 0o7777, 0o755);
}

#[test]
fn fchown_after_unlink_updates_the_surviving_hard_link_inode() {
    let (mut kernel, pid) = kernel_and_pid();
    kernel
        .write_file_for_process(DRIVER, pid, "/original", b"data", Some(0o6745))
        .expect("create hard-link source");
    kernel
        .link("/original", "/alias")
        .expect("create surviving hard link");
    let fd = kernel
        .fd_open(DRIVER, pid, "/original", O_RDONLY, None)
        .expect("open source");
    let original = kernel.stat("/original").expect("stat source");

    kernel.remove_file("/original").expect("unlink source name");
    kernel
        .fd_chown_for_process(DRIVER, pid, fd, UNCHANGED, 44)
        .expect("fchown through deleted name");

    let descriptor = kernel
        .dev_fd_stat(DRIVER, pid, fd)
        .expect("fstat open hard link");
    let alias = kernel.stat("/alias").expect("stat surviving alias");
    assert_eq!((descriptor.dev, descriptor.ino), (alias.dev, alias.ino));
    assert_eq!((alias.dev, alias.ino), (original.dev, original.ino));
    assert_eq!((descriptor.uid, descriptor.gid), (1000, 44));
    assert_eq!((alias.uid, alias.gid), (1000, 44));
    assert_eq!(descriptor.mode & 0o7777, 0o2745);
    assert_eq!(alias.mode & 0o7777, 0o2745);
}

#[test]
fn fchmod_mutates_detached_directories_pipes_and_sockets() {
    let (mut kernel, pid) = kernel_and_pid();
    kernel.mkdir("/detached", false).expect("create directory");
    let directory_fd = kernel
        .fd_open(DRIVER, pid, "/detached", O_RDONLY | O_DIRECTORY, None)
        .expect("open directory");
    kernel
        .remove_dir("/detached")
        .expect("remove open directory");
    kernel
        .fd_chmod_for_process(DRIVER, pid, directory_fd, 0o701)
        .expect("fchmod detached directory");
    assert_eq!(
        kernel
            .dev_fd_stat(DRIVER, pid, directory_fd)
            .expect("fstat detached directory")
            .mode
            & 0o7777,
        0o701
    );

    let (pipe_read, pipe_write) = kernel.open_pipe(DRIVER, pid).expect("open pipe");
    kernel
        .fd_chmod_for_process(DRIVER, pid, pipe_read, 0o640)
        .expect("fchmod pipe");
    for fd in [pipe_read, pipe_write] {
        let stat = kernel.dev_fd_stat(DRIVER, pid, fd).expect("fstat pipe");
        assert_eq!(stat.mode, 0o010640);
        assert_eq!((stat.uid, stat.gid), (1000, 1000));
    }

    let (socket_left, socket_right) = kernel
        .fd_socketpair(DRIVER, pid, SocketType::Stream, false, false)
        .expect("open socketpair");
    kernel
        .fd_chmod_for_process(DRIVER, pid, socket_left, 0o601)
        .expect("fchmod socket");
    let left = kernel
        .dev_fd_stat(DRIVER, pid, socket_left)
        .expect("fstat changed socket");
    let right = kernel
        .dev_fd_stat(DRIVER, pid, socket_right)
        .expect("fstat peer socket");
    assert_eq!(left.mode, 0o140601);
    assert_eq!(right.mode, 0o140777);
    assert_eq!((left.uid, left.gid), (1000, 1000));
}

#[test]
fn lchown_reaches_the_ephemeral_root_overlay_without_following() {
    let mut config = KernelVmConfig::new("vm-root-ownership");
    config.permissions = Permissions::allow_all();
    let root = RootFileSystem::from_descriptor(RootFilesystemDescriptor {
        mode: RootFilesystemMode::Ephemeral,
        disable_default_base_layer: true,
        lowers: Vec::new(),
        bootstrap_entries: Vec::new(),
    })
    .expect("build root filesystem");
    let mut kernel = KernelVm::new(MountTable::new(root), config);
    let process = kernel
        .create_virtual_process(
            DRIVER,
            DRIVER,
            "root-ownership-check",
            Vec::new(),
            VirtualProcessOptions::default(),
        )
        .expect("create process");
    let pid = process.pid();
    kernel
        .write_file("/target", b"data")
        .expect("create target");
    kernel.symlink("/target", "/link").expect("create link");

    kernel
        .chown_for_process(DRIVER, pid, "/link", UNCHANGED, UNCHANGED, false)
        .expect("lchown through mounted root overlay");
    assert!(kernel.lstat("/link").expect("lstat link").is_symbolic_link);
}