m³OS docs GitHub

Phase 54

Post Serverization Kernel Hygiene

Close the `FdEntry` CLOEXEC / NONBLOCK plumbing gap and relocate the four `arch::x86_64::syscall::*_pub` wrappers into their owning subsystems so later phases do not inherit a silent security bug or a process-module arch dependency.

In progress

Learning goal

Understand why `O_CLOEXEC` must be threaded through every fd-creating syscall path, not only the ones that expose a flags argument at the type level.

Category

infrastructure

Why this phase matters

Close the FdEntry CLOEXEC / NONBLOCK plumbing gap and relocate the four arch::x86_64::syscall::*_pub wrappers into their owning subsystems so later phases do not inherit a silent security bug or a process-module arch dependency.

How it works in m3OS

Phase 54 moves meaningful storage, pathname, and networking policy out of ring 0 without breaking the existing Linux-like syscall ABI. The kernel keeps the object handles, transport, and bootstrap fallback paths; supervised userspace services now own the migrated policy slices: The result is not a fully serverized OS, but it is a materially narrower kernel than the Phase 53 baseline and a more honest microkernel story.

How it builds on earlier phases

This phase builds on Deep Serverization and turns that foundation into Post Serverization Kernel Hygiene.

Implementation outline

  • Introduce FdEntry::from_open_flags(backend, flags) in the module that owns FdEntry.
  • Convert every hardcoded cloexec: false, nonblock: false construction site in kernel/src/arch/x86_64/syscall, including vfs_service_open.
  • Add a per-backend userspace regression test that execs and verifies O_CLOEXEC cleared the fd.
  • Relocate each of the four *_pub wrappers into its owning subsystem; update kernel/src/process/mod.rs call sites.
  • Trim docs/debug/54-followups.md to the two long-term backlog items (MOUNT_OP_LOCK yielding, scheduler thresholds) that intentionally remain open.

Task progress snapshot

0 of 35 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • MOUNT_OP_LOCK yielding primitive — tracked as long-term scheduling work in docs/debug/54-followups.md.
  • Scheduler diagnostic threshold tuning — no action unless the current thresholds become noisy; tracked in docs/debug/54-followups.md.
  • Full epoll extraction out of kernel/src/arch/x86_64/syscall/mod.rs — Phase 54a hoists only the cleanup helper; a full module split remains a later refactor if it becomes blocking.

Source materials

Important code

Code spotlight cards

Code spotlight

Introduce `FdEntry::from_open_flags(backend, flags)` helper

kernel/src/process/mod.rs L215-L229

Introduce `FdEntry::from_open_flags(backend, flags)` helper. Why it matters: Every non-pipe / non-socket / non-epoll open path currently hardcodes cloexec: false, nonblock: false, silently dropping the O_CLOEXEC guarantee requested by userspace. A single helper that maps syscall-level flags to FdEntry fields keeps every construction site consistent and prevents new paths from re-introducing the bug. Focus on `FdEntry::from_open_flags` and `O_CLOEXEC` in `kernel/src/process/mod.rs` while you trace the snippet.

/// A single open-file entry in the per-process FD table.
#[derive(Clone)]
pub struct FdEntry {
    pub backend: FdBackend,
    pub offset: usize,
    /// True if the file was opened for reading.
    pub readable: bool,
    /// True if the file was opened for writing.
    pub writable: bool,
    /// Close-on-exec flag (FD_CLOEXEC).
    pub cloexec: bool,
    /// Non-blocking I/O flag (O_NONBLOCK). When set, read/write return
    /// `-EAGAIN` instead of blocking when no data is available (Phase 37).
    pub nonblock: bool,
}

Code spotlight

Convert every hardcoded `cloexec: false, nonblock: false` site to use the helper

kernel/src/arch/x86_64/syscall/mod.rs L5740-L5763

Convert every hardcoded `cloexec: false, nonblock: false` site to use the helper. Why it matters: The helper is only useful if every construction site routes through it. Without an audit-style conversion, new call sites drift back to hardcoded false, false because the helper's existence is easy to miss. Focus on `vfs_service_open` and `sys_linux_openat` in `kernel/src/arch/x86_64/syscall/mod.rs` while you trace the snippet.

fn vfs_service_open(path: &str, _flags: u64) -> u64 {
    use crate::ipc::{endpoint, message::Message, registry};
    use crate::task::scheduler;
    use kernel_core::fs::vfs_protocol::VFS_OPEN;

    let vfs_ep = match registry::lookup_endpoint_id("vfs") {
        Some(ep) => ep,
        None => return NEG_ENOENT,
    };
    let task_id = match scheduler::current_task_id() {
        Some(id) => id,
        None => return NEG_EINVAL,
    };

    let mut msg = Message::new(VFS_OPEN);
    msg.data[0] = 0;
    msg.data[1] = path.len() as u64;
    scheduler::deliver_bulk(task_id, alloc::vec::Vec::from(path.as_bytes()));

    let reply = endpoint::call_msg(task_id, vfs_ep, msg);
    if reply.label != 0 {
        return reply.label;
    }

Code spotlight

Regression test: `O_CLOEXEC` clears fd across `execve` for every backend

userspace/cloexec-test/src/main.rs See file

Regression test: `O_CLOEXEC` clears fd across `execve` for every backend. Why it matters: The only reliable way to test O_CLOEXEC is to actually execve and observe whether the child still sees the fd. Without a per-backend regression, A.2's audit can regress without detection. Focus on `O_CLOEXEC` and `execve` in `userspace/cloexec-test/src/main.rs` while you trace the snippet.

Key files

  • kernel/src/arch/x86_64/syscall/mod.rs — Thin syscall facade, fallback paths, and service routing
  • kernel/src/process/mod.rs — Signal delivery now wakes blocked IPC waiters for dying services
  • kernel/src/process/mod.rs::close_cloexec_fds — Each subsystem that owns an fd backend exposes its own free / release function. kernel/src/process/mod.rs::close_cloexec_fds and close_all_fds_for call those functions directly, eliminating the last arch-specific import from process cleanup.
  • kernel/src/arch/x86_64/syscall — false, nonblock: false construction site in kernel/src/arch/x86_64/syscall, including vfs_service_open.
  • userspace/fat_server/src/main.rs — Supervised storage-service endpoint
  • userspace/vfs_server/src/main.rs — Migrated rootfs pathname and metadata policy
  • userspace/net_server/src/main.rs — Migrated UDP policy service
  • userspace/coreutils-rs/src/service.rs — Service-stop completion wait used by validation and operators
  • userspace/init/src/main.rs — Degraded-mode logging, restart policy, and service-stop behavior
  • userspace/login/src/main.rs — Hardened passwd/shadow reads against early-boot timing during validation
  • kernel/src/ipc/endpoint.rs — IPC wait cancellation for signaled tasks
  • kernel/src/task/scheduler.rs — Blocked IPC task enumeration for signal wakeup
  • xtask/src/main.rs — Regression harness, smoke flow, and ext2 data-disk provisioning

How real OSes differ

  • Linux threads `O_CLOEXEC` through `file_operations`-level open, so the pattern cannot skip a backend in the way `FdEntry` currently can.
  • Mature kernels put per-subsystem fd cleanup behind VFS-style dispatch tables; the ad-hoc `*_pub` wrapper pattern is a transitional artifact of m3OS's current flat `FdEntry`.
  • Phase 54a does not introduce a dispatch table — it only adds the consistent helper and moves the wrappers.

Success criteria

  • `open(path, O_RDONLY | O_CLOEXEC)` results in an fd that does not survive `execve`, verified by a regression test, for every backend the kernel exposes.
  • `grep -rn "cloexec: false, nonblock: false" kernel/src` returns only call sites that deliberately create a non-CLOEXEC fd, each carrying an inline comment explaining why.
  • `kernel/src/process/mod.rs` no longer imports any symbol from `crate::arch::x86_64::syscall`.
  • `docs/debug/54-followups.md` contains only the two long-term backlog items, each annotated with its owner.
  • All existing QEMU tests pass unchanged.

Related phases

Keep exploring the roadmap