secure-exec-kernel 0.3.1-rc.2

Shared kernel plane for secure-exec native and browser sidecars
Documentation
use secure_exec_kernel::command_registry::CommandDriver;
use secure_exec_kernel::kernel::{KernelProcessHandle, KernelVm, KernelVmConfig, SpawnOptions};
use secure_exec_kernel::permissions::Permissions;
use secure_exec_kernel::resource_accounting::ResourceLimits;
use secure_exec_kernel::socket_table::{InetSocketAddress, SocketSpec, SocketState};
use secure_exec_kernel::vfs::MemoryFileSystem;

fn spawn_shell(kernel: &mut KernelVm<MemoryFileSystem>) -> KernelProcessHandle {
    kernel
        .spawn_process(
            "sh",
            Vec::new(),
            SpawnOptions {
                requester_driver: Some(String::from("shell")),
                ..SpawnOptions::default()
            },
        )
        .expect("spawn shell")
}

fn new_kernel(vm_id: &str) -> KernelVm<MemoryFileSystem> {
    let mut config = KernelVmConfig::new(vm_id);
    config.permissions = Permissions::allow_all();
    let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
    kernel
        .register_driver(CommandDriver::new("shell", ["sh"]))
        .expect("register shell");
    kernel
}

#[test]
fn socket_resources_appear_in_kernel_resource_snapshot_and_cleanup_with_process_exit() {
    let mut config = KernelVmConfig::new("vm-socket-resources");
    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 = spawn_shell(&mut kernel);
    let listener = kernel
        .socket_create("shell", process.pid(), SocketSpec::tcp())
        .expect("create listener socket");
    kernel
        .socket_set_state("shell", process.pid(), listener, SocketState::Listening)
        .expect("mark listener");

    let connected = kernel
        .socket_create("shell", process.pid(), SocketSpec::tcp())
        .expect("create connected socket");
    kernel
        .socket_set_state("shell", process.pid(), connected, SocketState::Connected)
        .expect("mark connected");

    let snapshot = kernel.resource_snapshot();
    assert_eq!(snapshot.sockets, 2);
    assert_eq!(snapshot.socket_listeners, 1);
    assert_eq!(snapshot.socket_connections, 1);

    process.finish(0);

    let snapshot_after_exit = kernel.resource_snapshot();
    assert_eq!(snapshot_after_exit.sockets, 0);
    assert_eq!(snapshot_after_exit.socket_listeners, 0);
    assert_eq!(snapshot_after_exit.socket_connections, 0);
}

#[test]
fn socket_resource_limits_reject_extra_sockets_and_connections() {
    let mut config = KernelVmConfig::new("vm-socket-limits");
    config.permissions = Permissions::allow_all();
    config.resources = ResourceLimits {
        max_sockets: Some(2),
        max_connections: Some(1),
        ..ResourceLimits::default()
    };

    let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
    kernel
        .register_driver(CommandDriver::new("shell", ["sh"]))
        .expect("register shell");

    let process = spawn_shell(&mut kernel);
    let listener = kernel
        .socket_create("shell", process.pid(), SocketSpec::tcp())
        .expect("create listener socket");
    kernel
        .socket_set_state("shell", process.pid(), listener, SocketState::Listening)
        .expect("mark listener");

    let first_connection = kernel
        .socket_create("shell", process.pid(), SocketSpec::tcp())
        .expect("create first connection socket");
    kernel
        .socket_set_state(
            "shell",
            process.pid(),
            first_connection,
            SocketState::Connected,
        )
        .expect("mark first connection");

    let socket_error = kernel
        .socket_create("shell", process.pid(), SocketSpec::tcp())
        .expect_err("third socket should exceed max_sockets");
    assert_eq!(socket_error.code(), "EAGAIN");

    kernel
        .socket_close("shell", process.pid(), listener)
        .expect("close listener");
    let second_connection = kernel
        .socket_create("shell", process.pid(), SocketSpec::tcp())
        .expect("create replacement socket");
    let connection_error = kernel
        .socket_set_state(
            "shell",
            process.pid(),
            second_connection,
            SocketState::Connected,
        )
        .expect_err("second connection should exceed max_connections");
    assert_eq!(connection_error.code(), "EAGAIN");
}

#[test]
fn socket_resource_snapshot_counts_stream_bytes_and_udp_queue_pressure() {
    let mut kernel = new_kernel("vm-socket-buffer-snapshot");
    let sender = spawn_shell(&mut kernel);
    let receiver = spawn_shell(&mut kernel);

    let stream_sender = kernel
        .socket_create("shell", sender.pid(), SocketSpec::tcp())
        .expect("create stream sender");
    let stream_receiver = kernel
        .socket_create("shell", receiver.pid(), SocketSpec::tcp())
        .expect("create stream receiver");
    kernel
        .socket_connect_pair("shell", sender.pid(), stream_sender, stream_receiver)
        .expect("connect stream pair");
    kernel
        .socket_write("shell", sender.pid(), stream_sender, b"hello")
        .expect("write stream payload");

    let datagram_sender = kernel
        .socket_create("shell", sender.pid(), SocketSpec::udp())
        .expect("create datagram sender");
    kernel
        .socket_bind_inet(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 54071),
        )
        .expect("bind datagram sender");
    let datagram_receiver = kernel
        .socket_create("shell", receiver.pid(), SocketSpec::udp())
        .expect("create datagram receiver");
    kernel
        .socket_bind_inet(
            "shell",
            receiver.pid(),
            datagram_receiver,
            InetSocketAddress::new("127.0.0.1", 43171),
        )
        .expect("bind datagram receiver");
    kernel
        .socket_send_to_inet_loopback(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 43171),
            b"abc",
        )
        .expect("send first datagram");
    kernel
        .socket_send_to_inet_loopback(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 43171),
            b"defg",
        )
        .expect("send second datagram");

    let snapshot = kernel.resource_snapshot();
    assert_eq!(snapshot.socket_buffered_bytes, 12);
    assert_eq!(snapshot.socket_datagram_queue_len, 2);

    let _ = kernel
        .socket_read("shell", receiver.pid(), stream_receiver, 5)
        .expect("read stream payload");
    let _ = kernel
        .socket_recv_datagram("shell", receiver.pid(), datagram_receiver, 16)
        .expect("receive datagram");

    let snapshot = kernel.resource_snapshot();
    assert_eq!(snapshot.socket_buffered_bytes, 4);
    assert_eq!(snapshot.socket_datagram_queue_len, 1);
}

#[test]
fn socket_resource_limits_reject_buffer_and_datagram_queue_growth() {
    let mut config = KernelVmConfig::new("vm-socket-buffer-limits");
    config.permissions = Permissions::allow_all();
    config.resources = ResourceLimits {
        max_socket_buffered_bytes: Some(5),
        max_socket_datagram_queue_len: Some(1),
        ..ResourceLimits::default()
    };

    let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
    kernel
        .register_driver(CommandDriver::new("shell", ["sh"]))
        .expect("register shell");
    let sender = spawn_shell(&mut kernel);
    let receiver = spawn_shell(&mut kernel);

    let stream_sender = kernel
        .socket_create("shell", sender.pid(), SocketSpec::tcp())
        .expect("create stream sender");
    let stream_receiver = kernel
        .socket_create("shell", receiver.pid(), SocketSpec::tcp())
        .expect("create stream receiver");
    kernel
        .socket_connect_pair("shell", sender.pid(), stream_sender, stream_receiver)
        .expect("connect stream pair");

    kernel
        .socket_write("shell", sender.pid(), stream_sender, b"12345")
        .expect("write up to stream buffer limit");
    let stream_error = kernel
        .socket_write("shell", sender.pid(), stream_sender, b"6")
        .expect_err("extra stream byte should exceed socket buffer limit");
    assert_eq!(stream_error.code(), "EAGAIN");
    assert_eq!(
        kernel
            .socket_get(stream_receiver)
            .expect("stream receiver")
            .buffered_read_bytes(),
        5
    );
    let _ = kernel
        .socket_read("shell", receiver.pid(), stream_receiver, 5)
        .expect("drain stream buffer");
    kernel
        .socket_write("shell", sender.pid(), stream_sender, b"6")
        .expect("write should succeed after draining stream buffer");
    let _ = kernel
        .socket_read("shell", receiver.pid(), stream_receiver, 1)
        .expect("drain second stream write");

    let datagram_sender = kernel
        .socket_create("shell", sender.pid(), SocketSpec::udp())
        .expect("create datagram sender");
    kernel
        .socket_bind_inet(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 54072),
        )
        .expect("bind datagram sender");
    let datagram_receiver = kernel
        .socket_create("shell", receiver.pid(), SocketSpec::udp())
        .expect("create datagram receiver");
    kernel
        .socket_bind_inet(
            "shell",
            receiver.pid(),
            datagram_receiver,
            InetSocketAddress::new("127.0.0.1", 43172),
        )
        .expect("bind datagram receiver");

    kernel
        .socket_send_to_inet_loopback(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 43172),
            b"abc",
        )
        .expect("send first datagram");
    let queue_error = kernel
        .socket_send_to_inet_loopback(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 43172),
            b"d",
        )
        .expect_err("second datagram should exceed queue length limit");
    assert_eq!(queue_error.code(), "EAGAIN");
    assert_eq!(
        kernel
            .socket_get(datagram_receiver)
            .expect("datagram receiver")
            .queued_datagrams(),
        1
    );
    let _ = kernel
        .socket_recv_datagram("shell", receiver.pid(), datagram_receiver, 16)
        .expect("drain datagram queue");

    let byte_error = kernel
        .socket_send_to_inet_loopback(
            "shell",
            sender.pid(),
            datagram_sender,
            InetSocketAddress::new("127.0.0.1", 43172),
            b"123456",
        )
        .expect_err("oversized datagram should exceed socket buffer byte limit");
    assert_eq!(byte_error.code(), "EAGAIN");
    assert_eq!(
        kernel
            .socket_get(datagram_receiver)
            .expect("datagram receiver after oversized send")
            .queued_datagrams(),
        0
    );
}