A modern, minimal, secure operating system designed specifically for virtual machines — fast boot, reproducible images, strong security defaults, and a full Unix utility layer. Written entirely in Rust.
| Target | Goal | Result |
|---|---|---|
| Boot time | < 2 s (BIOS, headless QEMU) | 1.79–1.93 s ✅ |
| Idle RAM | < 256 MB | < 256 MB ✅ |
| Base image | < 1 GB compressed QCOW2 | 12.5 MB ✅ |
| SSH ready | < 5 s after shell prompt | < 1 s ✅ |
| Integration tests | 100% pass | 10/10 smoke suite + 31-program syscall-diff corpus ✅ |
| Unit tests | All pass | 456/456 kernel + 17/17 dwarf-extractor + 134/134 mymc (cargo test --lib) ✅ |
| File manager | Dual-pane Rust TUI shipped on the image | /bin/mymc 2.05 MiB stripped — copy/move/delete/mkdir + resumable transfers + previewer + fuzzy filter + history (Feature 070) ✅ |
| /proc entries | Linux-compatible | 15 virtual files + /proc/audit/{data,stats} + per-PID /proc/<pid>/{trace,stack,wchan,status} — status shows real Uid:/Gid:/Groups: lines (Feature 071) ✅ |
| Credential audit log | Every privilege transition recorded | dmesg | grep AUDT (Stage 1) + /proc/audit/data binary stream + /proc/audit/stats KV counters (Stage 2) — every set{u,g,res,fs}{uid,gid} syscall (success + EPERM + EINVAL) is recorded; 112-byte fixed AuditRecord with seq + drop counter (Features 072 + 073) ✅ |
| Memory-safety diagnostics | KASAN + FASAN | Both default-on for debug builds (heap redzones + frame poisoning/ownership) ✅ |
| Panic backtrace | Source-line annotated | In-kernel DWARF lookup — every frame shows at <file>:<line> (Feature 066) ✅ |
| Networking userland | DNS, HTTP, nc, ping | 5/5 tests pass ✅ |
| Reproducible builds | Identical SHA-256 | ✅ |
| Verified boot | BLAKE2b hash chain | ✅ |
All 11 success criteria (SC-001–SC-011) pass. See VALIDATION.md.
nsh$ prompt with mybox applets, pipe chains, and standard utilities — captured via make screenshot.
Real nsh session over SSH — uname, /proc/meminfo, /proc/cpuinfo, ps, a base64 pipe, and the colored [1] prompt that appears after a failed command. Generated via make demo-gif (paramiko + compound nsh -c, cast trimmed to remove SSH-negotiation dead time). See Feature 062 below.
A complete, self-contained OS stack:
+-------------------------------------------------------+
| User Space |
| init | nsh | myos-pkg | cloud-init | dropbear |
| mybox (97 applets) | sandbox | exploit-test |
| /proc/self/{maps,fd/,status,exe} | /proc/{cpuinfo, |
| uptime,net/dev,net/tcp} | /proc/[pid]/… |
+-------------------------------------------------------+
| Security Layer |
| Per-process syscall allowlist (SYS_SANDBOX_ENTER) |
| Capability tokens (CAP_FS_ADMIN, CAP_NET_BIND, …) |
| Verified boot (BLAKE2b → ed25519 attestation) |
+-------------------------------------------------------+
| System Layer |
| VFS (symlink-following) | Syscall dispatch | Pipes |
| IPC | MLFQ scheduler | Linux ELF binary compatibility |
+-------------------------------------------------------+
| Kernel |
| MM (demand paging) | Interrupts (APIC/HPET) |
| smoltcp 0.11 | DHCP | ext2 (read/write) | firewall |
| procfs (12 virtual files, Linux-compatible) |
+-------------------------------------------------------+
| VirtIO Drivers |
| blk | net | console | rng | scsi (VirtualBox compat) |
+-------------------------------------------------------+
| Virtual Hardware |
| QEMU q35 (primary) | VirtualBox (secondary) |
+-------------------------------------------------------+
# Prerequisites
apt install qemu-system-x86 ovmf sgdisk mtools e2fsprogs qemu-utils nasm python3
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
rustup target add x86_64-unknown-linux-musl
# Build and boot (< 2s to shell prompt)
RELEASE=1 bash build/scripts/assemble-image.sh myos.qcow2
make qemu💾 Spare your SSD:
make tmpfs-setupredirectstarget/anddist/(the only large gitignored output trees) into/tmp/MyOS/<hash>/so the write-heavy build cycle hits RAM. Reversible (make tmpfs-teardown); idempotent; opt-in; no-op on CI. Seedocs/dev-tmpfs.md.
Boot the VM in a graphical window showing the kernel framebuffer terminal:
make qemu-sdl # opens SDL window; QEMU monitor via Ctrl+Alt+2SSH in simultaneously on port 2222:
ssh -p 2222 -i tests/keys/test_id_ed25519 \
-o StrictHostKeyChecking=no root@127.0.0.1qemu-system-x86_64 -machine q35 -cpu qemu64 -smp 2 -m 256M \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive file=myos.qcow2,format=qcow2,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 -device virtio-rng-pci \
-serial stdio -display none &
ssh -p 2222 -i tests/keys/test_id_ed25519 \
-o StrictHostKeyChecking=no root@127.0.0.1MyOS2026 boots in VirtualBox using the default hardware profile — no custom settings required. The kernel includes a native LSI Logic MPT SCSI driver and an Intel E1000 NIC driver.
Import and boot:
# Convert QCOW2 → VDI (VirtualBox native format)
qemu-img convert -f qcow2 -O vdi myos.qcow2 myos.vdi
# Create VM via VBoxManage (or use the GUI)
VBoxManage createvm --name MyOS2026 --ostype Linux_64 --register
VBoxManage modifyvm MyOS2026 --memory 256 --cpus 1 --nic1 nat
VBoxManage modifyvm MyOS2026 --firmware efi --graphicscontroller vmsvga
# Add SCSI controller (LSI Logic — the VirtualBox default)
VBoxManage storagectl MyOS2026 --name SCSI --add scsi --controller LsiLogic
VBoxManage storageattach MyOS2026 --storagectl SCSI --port 0 --device 0 \
--type hdd --medium myos.vdi
# Start (headless)
VBoxManage startvm MyOS2026 --type headless
# SSH in (once nsh$ appears in VBoxManage guestcontrol or serial log)
ssh -p 22 -i tests/keys/test_id_ed25519 -o StrictHostKeyChecking=no root@<guest-ip>VirtualBox hardware mapping:
| VirtualBox device | Kernel driver | Notes |
|---|---|---|
| LsiLogic SCSI | drivers::lsi_scsi |
MPT 1.x protocol; auto-detected |
| Intel E1000 (NIC1) | drivers::e1000 |
Default VirtualBox NIC; MAC from RA[0] |
| EFI firmware | Limine UEFI loader | Use --firmware efi |
The kernel auto-detects which block device is present (root= cmdline override
available for explicit selection: root=lsi-scsi, root=virtio-scsi,
root=virtio-blk).
# Styled PNG screenshot → docs/screenshots/demo.png
# Requires: cargo install silicon
make screenshot
# Animated GIF demo → docs/demo.gif
# Requires: pip install asciinema && (download agg from github.com/asciinema/agg/releases)
make demo-gifOnce in, the full Unix utility set is available via mybox:
nsh$ ls /bin | wc -l # 91+ binaries (mybox applets + sh + test binaries)
nsh$ cat /etc/hostname | grep -c .
1
nsh$ ps | head -5
nsh$ echo hello | grep hello
helloA multi-call binary providing 91 Unix applets via symlinks in /bin.
Dispatch is purely by argv[0] basename — no runtime overhead per applet.
| Category | Applets |
|---|---|
| File ops | cat, cp, mv, rm, ln, mkdir, rmdir, touch, chmod, chown, chgrp, install, truncate |
| Text | grep, sed, awk, cut, head, tail, sort, uniq, wc, tr, comm, diff, patch, tee |
| Filesystem | ls, find, du, df, stat, file, readlink, realpath, basename, dirname, pathchk |
| Process | ps, kill, killall, nice, nohup, timeout, watch, pgrep, pkill, wait |
| System | uname, hostname, dmesg, uptime, free, sysctl, env, printenv, nproc |
| Archive | tar, gzip, gunzip, zcat, bzip2, bzcat, xz, unxz |
| Shell utils | echo, printf, test, true, false, yes, seq, sleep, date, expr, xargs |
| Misc | od, xxd, base64, md5sum, sha256sum, cmp, strings, stty |
Networking applets (
nslookup,wget,nc,ping) are included — shipped in Feature 025. DNS resolution, HTTP fetch, TCP netcat, and ICMP ping all pass integration tests.
nsh$ /bin/grep -i root /etc/passwd
root:x:0:0:root:/root:/bin/sh
nsh$ /bin/ls -la /bin/ls
lrwxrwxrwx 10 ls -> /bin/mybox
nsh$ mybox --list | wc -l
91Statically-linked musl ELF binaries compiled on Linux run directly on MyOS2026 without modification or recompilation:
# On a Linux host:
musl-gcc -static -o hello hello.c
cargo build --target x86_64-unknown-linux-musl --release
# Copy to MyOS2026 (scp or baked into the disk image) and run:
nsh$ /bin/hello
Hello, World!
nsh$ /bin/mybox-linux echo "from linux musl"
from linux muslWhat works: ELF64 static executables (ET_EXEC), full System V AMD64 ABI
initial stack layout (argc/argv/envp/auxv), all musl startup syscalls
(set_tid_address, arch_prctl(ARCH_SET_FS), prlimit64, getrandom,
rt_sigprocmask), anonymous mmap, brk, /proc/self/exe, /proc/self/maps.
Invalid accesses deliver SIGSEGV (no kernel panic); stack overflows are caught
at the stack bottom guard and deliver SIGSEGV to the process.
Out of scope: dynamic linking (PT_INTERP), 32-bit ELF, kernel modules.
The VFS resolve() function follows symlinks after every directory lookup
(depth-capped at 8 to prevent loops). EXT2 fast symlinks (≤60 bytes stored
directly in i_block[]) are supported without data block allocation. This
enables execve("/bin/cat") to transparently dispatch through /bin/mybox.
nsh$ sandbox --allow=read,write,exit /usr/bin/exploit-test
BLOCKED (errno=1) ← mount(2) blocked by kernel allowlistThe kernel enforces a deny-by-default syscall filter per process, installed
via SYS_SANDBOX_ENTER (nr=999). Filters survive execve and are independent
across processes.
Every RELEASE build embeds a BLAKE2b hash chain:
UEFI → Limine (config hash enrolled) → kernel.elf (BLAKE2b verified)
→ kernel_main ([vboot] ACTIVE pubkey: be5f7844108bcdd1)
Any binary tampering before a single kernel instruction executes causes an
immediate boot abort (hash_mismatch_panic: yes).
Boots with a cidata ISO and applies provisioning automatically:
- Sets hostname via
sethostname(2) - Writes SSH authorized keys to
/root/.ssh/authorized_keys - Runs
runcmdentries (e.g.chmod 700 /root/.ssh)
Two independent builds from identical source produce byte-identical QCOW2.
RELEASE=1 bash build/scripts/assemble-image.sh build-a.qcow2
RELEASE=1 bash build/scripts/assemble-image.sh build-b.qcow2
sha256sum build-a.qcow2 build-b.qcow2
# a3e64333... build-a.qcow2
# a3e64333... build-b.qcow2 ← identical ✓Achieved via SOURCE_DATE_EPOCH, pinned GPT/FAT UUIDs, and
build/scripts/fix-ext2-timestamps.py.
| Test | Suite | Description | Status |
|---|---|---|---|
| test_boot.py | integration | 9-phase boot sequence → nsh$ | ✅ PASS |
| test_ssh.py | integration | SSH login (Dropbear, key auth) | ✅ PASS |
| test_shell.py | integration | nsh pipes, redirects, builtins | ✅ PASS |
| test_cloud_init.py | integration | cidata: hostname + SSH key injection | ✅ PASS |
| test_sandbox.py | integration | Sandbox blocks mount(2) | ✅ PASS |
| test_rollback.sh | integration | QCOW2 snapshot / rollback | ✅ PASS |
| test_lsi_scsi.py | vbox | mptsas1068 detection + CD-ROM coexistence | ✅ PASS |
| test_e1000_ssh.py | vbox | E1000 NIC + SSH login | ✅ PASS |
| test_vbox_combined.py | vbox | LSI SCSI + E1000 together | ✅ PASS |
| test_dual_nic.py | vbox | virtio-net + E1000 dual NIC | ✅ PASS |
| test_signal.py | syscalls | rt_sigaction delivery + sigreturn | ✅ PASS |
| test_nanosleep.py | syscalls | nanosleep blocks correct duration | ✅ PASS |
| test_futex.py | syscalls | futex WAIT/WAKE under concurrent load | ✅ PASS |
| test_misc_posix.py | syscalls | uname, getcwd/chdir, TIOCGWINSZ | ✅ PASS |
| test_debug_mode.py | misc | kdebug subsystem log tags | ✅ PASS |
| test_scheduler.py | scheduler | MLFQ: CPU demotion, I/O boost, starvation, nice | ✅ PASS |
| test_linux_elf.py | elf-compat | 16 scenarios: musl C/Rust ELF, SIGSEGV, stack overflow, /proc | ✅ PASS |
| test_reproducible.sh | misc | Two builds produce identical SHA-256 | ✅ PASS |
Run all suites:
make test-all QCOW2=dist/myos2026.qcow2
# Or individually:
make test-unit # 434 kernel unit tests, no QEMU
make test-integration QCOW2=dist/myos2026.qcow2 # boot, SSH, shell, cloud-init, sandbox
make test-vbox QCOW2=dist/myos2026.qcow2 # LSI SCSI, E1000, dual-NIC
make test-syscalls QCOW2=dist/myos2026.qcow2 # signal, nanosleep, futex, misc-posix
python3 tests/boot/test_linux_elf.py dist/myos2026.qcow2 # Linux ELF compat (16 scenarios)Every PR is gated by GitHub Actions; the ci check runs clippy, unit tests, and the integration suite under smp ∈ {1, 2} matrix axes. To run the same pipeline locally before pushing:
make ci-local # ~15 min; same step order, same per-step timeouts as remote CIFailed local runs leave artifacts under dist/ci-artifacts/ (QEMU serial log, test stdout/stderr, optional kernel-panic excerpt). The CI gate is documented in detail in specs/035-ci-pr-gate/quickstart.md.
- Boot: Limine (BIOS + UEFI), x86-64 entry, GDT/IDT, APIC/HPET timer
- Memory: bitmap physical allocator, 4-level page table, demand paging, kernel heap
- Drivers: UART, virtio-blk, virtio-net, virtio-console (bidirectional: TX output + RX input via poll), virtio-rng, virtio-scsi, LSI Logic MPT SCSI (VirtualBox), Intel E1000 NIC (VirtualBox), framebuffer
- Filesystem: ext2 (superblock, block groups, inodes, directories, read/write, symlinks), VFS with symlink-following
resolve(), 64-slot LRU write-back block cache - Linux ELF compat: ELF64 static loader, full System V AMD64 ABI initial stack (auxv),
/proc/self/exe|maps, mmap_min_addr guard, stack-bottom guard → SIGSEGV - Process: PCB (with
nice: i8,stack_bottom), fork, exec, wait, exit, FD table, dup/dup2, pipes - Scheduler: 3-level MLFQ — quanta P0=1/P1=2/P2=4 ticks, priority decay on quantum exhaustion, I/O boost (wake at P0), starvation prevention (boost every 100 ticks), nice-based base priority, fork inherits nice
- Syscalls: Full POSIX Linux ABI (SYSCALL/SYSRET). Process: fork (CoW lazy frame sharing), execve, waitpid, exit, getpid, getppid, clone (CLONE_THREAD), set_tid_address, exit_group. Files: open, close, read, write, lseek, dup, dup2, pipe, fcntl, stat, fstat, lstat, getdents64. Memory: brk, mmap (anon+file-backed, MAP_SHARED/PRIVATE), munmap, mprotect. Signals: kill, rt_sigaction, rt_sigprocmask, rt_sigreturn, alarm. Time: clock_gettime, nanosleep, clock_nanosleep, gettimeofday. Threading: futex (WAIT/WAKE). Sockets: socket (TCP/UDP/ICMP-raw), bind, listen, accept, connect, send, recv, sendto, recvfrom, sendmsg, recvmsg, setsockopt, getsockopt, shutdown. Priority: nice (nr=34), getpriority (nr=140), setpriority (nr=141). Misc: uname, getcwd, chdir, ioctl (TIOCGWINSZ), sysinfo, gettid. Custom: gethostname (nr=125), vboot-status (nr=998), sandbox-enter (nr=999).
- Network: smoltcp 0.11, virtio-net device, DHCP, TCP/UDP/ICMP sockets,
sendmsg/recvmsg, packet firewall (default-deny, allow TCP/22 + ICMP + UDP) - /proc filesystem (Feature 024):
/proc/self/exe,/proc/self/maps(dynamic load+heap+stack ranges),/proc/self/fd/<N>(symlinks to open file paths),/proc/self/status(Name/Pid/VmRSS/Threads),/proc/<pid>/exe,/proc/<pid>/maps,/proc/cpuinfo(model name, cores),/proc/uptime,/proc/net/dev(rx/tx per NIC),/proc/net/tcp(TCP socket table); fully compatible with muslreadlink("/proc/self/exe"),/proc/self/mapsmmap parsing, and standard Unix tools - Fix: kernel
sys_writeto a pipe now blocks instead of dropping data past the 4 KiB ring (closes #106). Bothsys_writecall sites forPIPE_FS_ID(stdout/stderr fast path at line ~322; general-fd path at line ~456) now route through a newpipe_write_blockinghelper that loops over the kernel pipe primitives, yielding when the ring is full and any reader is still attached, returningEPIPEif all readers close before any byte was written. Before this fix, a singlewrite(fd, buf, N)forN > 4096returned only what fit in the ring (pipe::writecaps atBUF_SZ), and the rest was silently dropped —dmesg | tail -5andls /bin | head -12both returned empty chunks because dmesg's first write filled the ring and musl's stdio gave up.O_NONBLOCKstill returnsEAGAINimmediately. 7 new unit tests inkernel/src/proc/pipe.rsdocument the kernel pipe contract (writecaps atBUF_SZ,write_spacetracks outstanding bytes,read_readyflips on writer-close,has_readersis the EPIPE signal, ring wraparound preserves bytes across(head + count) % BUF_SZ). Newpipe::write_space(idx)accessor for callers that need to size their reads/writes. Live end-to-end verification (runningdmesg | tail -5over SSH) is currently blocked by #105 — the kernel[BAD-RET]scheduling panic fires during dropbear's SSH-handshake handling before any pipe activity from a test command can complete; will resolve automatically when #105 is fixed. - Dedicated kernel audit ring — Stage 2 (Feature 073 — Stage 2 of #144). Adds a 4096-slot, BSS-resident, SeqLock-protected ring of fixed 112-byte binary
AuditRecords alongside Stage 1's kmsg-ring ASCII path. Each credential-changing syscall now pushes into BOTH transports inside the samePTABLE.lockacquisition (FR-011 atomicity): the Stage 1dmesg | grep AUDTline for ad-hoc operator grep, AND a Stage 2 binary record for programmatic streaming consumers. Userland reads via/proc/audit/data(binary, 112-byte aligned; cursor model:oldest_seq + offset / 112; blocking-read viasched_yieldper FR-022; root-onlyopen(2)via explicit gate in sys_open) and/proc/audit/stats(4-line ASCII KV:seq_oldest/seq_newest/dropped_total/bytes_written; idempotent — reads don't consume records; root-only). Per-slotAtomicU32SeqLock counters give lock-free reader correctness without taking a writer lock (PTABLE.lock already serialises the F072 hook). Drop counter is monotonically exact (SC-006): if a slow consumer falls behind, the seq returned jumps anddropped_totalaccounts for every overwritten record. Record discriminants pinned incontracts/audit-ring-format.md:op(0–8, 9 values),result(0–2),target_kind(0=Single, 1=Triple-for-setresuid/setresgid withu32::MAXsentinel, 2=Groups). 22 new kernel unit tests (5 layout/encoder + 8 ring SeqLock + 3 stats + 4 read_data + 2 other) bring the kernel suite to 456/456 passing. 5 new integration scenarios intests/boot/test_credentials.py(S5 streaming + S5b 9-op coverage + S6 stats + S7 EACCES non-root + S8 SC-006 overflow + S9 bytes_written stability).make bench-setuid-stage2enforces ≤100 ns Stage 2 delta vs the post-F072 baseline. Userland audit daemon (drain to ext2) deferred — needs ext2 write path (#73); follow-up issue + ROADMAP row filed. Spec:specs/073-credential-audit-ring/. - Kernel audit log of credential transitions — Stage 1 (Feature 072 — Stage 1 of #144; Stage 2 shipped as Feature 073 above). Every credential-changing syscall —
setuid/seteuid/setresuid/setgid/setegid/setresgid/setgroups/setfsuid/setfsgid— now emits a structured single-line audit record into the existing kmsg ring (Feature 038) under a newLevel::Audit = 6(tagAUDT). Records carry the operation name, caller PID, pre-call credential tuple, target argument, post-call credential tuple, and the result (ok|EPERM|EINVAL). Both successful changes AND denials are recorded (FR-012) — an attacker probing for privilege escalation leaves a trace whether they succeed or not. The hook fires INSIDEproc::table::with_creds_mut'sPTABLE.lockacquisition so pre/post snapshots are atomic with the mutation (FR-010 — same lock the mutation already holds). Thesetfsuid/setfsgidLinux quirk (return value is OLD fsuid even on EPERM) is preserved at the syscall ABI; the audit record reports the LOGICAL outcome (pre.fsuid != post.fsuid?ok:EPERM). The(uid_t)-1sentinel forsetresuid/setresgidrenders literally as-1(not4294967295). Default-on, no compile flag, no per-PID gate — by design, you cannot opt out of audit on your own PID. Read records withdmesg | grep AUDTor programmatically viaSYS_KMSG_READ(440) filtering onlevel == 6. 15 audit format unit tests + 2Level::AuditABI tests inkernel/src/proc/audit.rs::tests; integration scenarios intests/boot/test_credentials.pycover success (US1) + EPERM/setfsuid quirk (US2) + zero-bytes-for-non-cred (NFR-002 / SC-004);make bench-setuidenforces the ≤200 ns regression budget vs the pre-F072 baseline (NFR-001 / SC-003). Stage 2 (dedicated/proc/auditring with drop counter + sequence number + userland daemon draining to ext2) is deferred per #147. Spec:specs/072-credential-audit-log/. - Real UID / credential model on
Pcb(Feature 071 — closes #74). Replaces the 11 hardcoded0UID/GID syscall arms inkernel/src/proc/syscall.rs(lines 315-318 + 340-350) with reads/writes through a newCredentialssub-struct onPcb: 8 ×u32scalars (uid/euid/suid/fsuid+ GID equivalents) + a fixed[u32; 16]supplementary-group array +ngroups: u8= exactly 100 bytes per Pcb (within NFR-001 ≤112 B budget). All 14 set/get UID/GID syscalls land inkernel/src/proc/syscalls/io.rs:getuid/geteuid/getgid/getegid/getresuid/getresgid/getgroups(reads) +setuid/seteuid/setresuid/setgid/setegid/setresgid/setgroups/setfsuid/setfsgid(writes) — each implementing the full Linux EPERM truth tables verbatim, with all-or-nothing atomicity on setresuid/setresgid (no partial field writes on EPERM) and the Linux setfsuid "returns OLD value even on permission denial" quirk preserved./proc/<pid>/statusgrows three new tab-separated lines (Uid:\t<r>\t<e>\t<s>\t<f>\n,Gid:,Groups:\t<space-separated>\n) matching Linux byte-for-byte; the old hardcoded "Uid:\t0 0 0 0" space-separated form is replaced. Feature 045's/proc/<pid>/tracepermission rule upgrades from structural (writer == target || writer == PID 1) to standard own-effective-UID-or-effective-root (writer.creds.is_root() || writer.creds.euid == target.creds.euid), closing the F045 spec TODO. Default boot state preserved (every process startsCredentials::root()= all zeros), so existing v0 binaries see identical values — the change is plumbing, not policy. 31 EPERM-matrix kernel unit tests inkernel/src/proc/credentials.rs::testscover the truth tables (setuid_unprivileged_to_real/saved_ok, setresuid_atomicity, setfsuid_returns_old_value_on_perm_check, setgroups_size_over_16_einval, trace allow/deny 4-cell matrix, etc.). 3 single-process corpus programs (credentials_round_trip,getgroups_query,setresuid_root_nop) ridemake syscall-difffor live VM coverage; the multi-PID setuid scenarios are covered by unit tests because live multi-user shells aren't shipped today. SC-005 audit grep returns zero matches post-merge. Deferrals (4 GitHub issues + ROADMAP rows): S_ISUID exec semantics (#141), capabilities(7) (#142), user namespaces (#143), audit log (#144). Spec:specs/071-pcb-credentials/. mymc— dual-pane file manager in Rust userland (Feature 070). Trimmed from upstream Cargonaut to fit MyOS2026's image constraints: 2.05 MiB stripped musl release binary (NFR-001 budget: 2.5 MiB), no async runtime (std::thread + std::sync::mpsc), no wasmtime plugin host, no SFTP/S3 backends, no OS keychain dependency. Phase A ships dual-pane LocalFs navigation (j/k/Enter/Backspace + Tab focus + Alt-1/2 jump), resumable copy/move engine (8 MiB chunks fsync'd + CRC-chained.mymc-transfer-*.jsoncheckpoints; SIGKILL → scan_resumable picks up at last fsync), confirm/input/resume/tasks/picker dialog system (F5 copy/F6 rename/F7 mkdir/F8 delete + F12 in-flight jobs + launch-time resume prompt), text previewer (F3; plain text, no syntect to fit NFR-001), hand-rolled fuzzy filter (<; nucleo-matcher would have added 300 KiB of Unicode tables), per-pane directory history (Alt-Shift-h / Alt-y / Alt-u), quick-cd popup (Alt-c), panel filter (Alt-!), sync-other-panel (Alt-i/Alt-o), toggle-hidden / split-orient / recursive-dir-size (Alt-./Alt-,/Ctrl-Space), and themymc.shcd-on-exit shell wrapper (FR-017;MYMC_EXIT_CWD_FILE). Configuration is layered TOML (/etc/mymc/config.toml∪~/.config/mymc/config.toml∪--config). Keymap is data-driven (/etc/mymc/keymap.toml) with 7 dialog kinds and Mode/Key/Mods abstraction. Three CI jobs feedcirollup:mymc-size(NFR-001 + NFR-007 unsafe-grep),mymc-coverage(NFR-006 tarpaulin ≥ 80%),mymc-startup(SC-004 + FR-010 ≤ 150 ms),mymc-boot(T_A.07 image install + non-interactive CLI). 134 unit tests pass; clippy-D warningsclean; zerounsafeblocks inuserland/mymc/src/**. Deferrals (5 GitHub issues + ROADMAP rows): editor handoff (#129; needs MyOS2026 editor), archive-as-VFS (#130; needs tar/zip workspace deps), HMAC audit (#131; needs OS keychain), seccomp hardening (#132), io_uring (#133). Spec:specs/070-mymc-userland-fm/. Upstream: github.com/mohnkhan/cargonaut.- DWARF CFI-based frame-pointer-free unwinding (Feature 068 — closes #112). Off-by-default kernel-side stack unwinder that walks past frames with broken or zeroed frame pointers via DWARF
.debug_frameCFI rules. Build-timedwarf-extractor --cfiwalks every FDE via gimli'sUnwindContext, emits a sorted 16-byteCfiEntryrule table tokernel/src/debug/cfi_generated.rs(currently 55,893 rows; 100% of 13,379 FDEs fit the 4-tuple shape). Runtimekernel::debug::unwind::next_frameis pure binary search + 4-tuple arithmetic with kernel-VA bounds checking — panic-safe by construction, reuses Feature 066's recursion guard. Panic-handler integration is purely ADDITIVE: existing FP backtrace runs as today; when the FP walker truncates (typically RBP=0 from an asm trampoline), the handler re-walks the rbp chain to find the broken frame, then callsnext_framerepeatedly to extend the backtrace past it. Each CFI-recovered frame is prefixed by acfi> rip=<hex>marker so operators can distinguish FP-walked vs CFI-walked frames at a glance. SC-002 (zero CFI bytes when feature OFF) enforced byscripts/ci/check-cfi-zero-cost.sh; SC-003 (≤20% loaded-image growth when ON) enforced byscripts/ci/check-cfi-size-budget.sh. Verified end-to-end viaimage-cfi-testwhich boots a chaincfi_test_outer → cfi_test_middle → asm-trampoline-zeroes-rbp → panic; the CFI walker bridges past the broken frame as designed. Quickstart:specs/068-dwarf-cfi-unwinding/quickstart.md. - Idle-flush UART decoupling for
kprint!(Feature 067 — closes #69). Boot withuart_async=1on the kernel cmdline; from that point forward,kprint!pushes to the kmsg ring and returns immediately — the UART catches up via a 4-line-per-pass drain called from the idle loop. Measured speedup: 813× (7.2 ms → 8.9 µs perkprint!call under QEMU TCG; production should be ~3 µs). Default sync behavior preserved (byte-identical boot log under no opt-in). The framebuffer terminal write is gated on the same flag so async mode is genuinely fire-and-forget for the issuing CPU.set_uart_mirror(true)runtime flip synchronously flushes any backlog before returning (FR-007a — preserves UART monotonic-by-seq invariant). Ring overflow during async mode emits a single[uart: NN lines dropped (ring overflow)]marker per skip event so silent gaps are impossible (FR-008a). Panic-time emission unaffected —write_directbypass meansuart_async=1 panic_now=1produces a complete panic block on UART (FR-009 + SC-004). Quickstart:specs/067-uart-async-flush/quickstart.md. - CI gate for named-root-cause discipline in PR bodies (closes #72). Feature 047 FR-011 / US4 require every feature PR body to name the concrete root cause (file:line + what was wrong + the fix + why it works); until now this was enforced only by self-policing + reviewer eyes.
scripts/ci/check-pr-body.shis a 100-line bash gate that runs alongsidedocs-gateon every PR: requires a## Summaryor## Root causeheading; if the body uses## Root cause, additionally requires at least onefile.ext:linereference. Same[no-docs]bypass token as docs-gate (one knob for both). 9-case self-test (scripts/ci/test-check-pr-body.sh) covers summary-only, root-cause-with-fileref, root-cause-without-fileref, empty-body, no-required-heading, case-insensitive, both-headings, fileref-anywhere, and gh-fails-skip cases. - DWARF inlined-function chain expansion in panic backtrace (closes #111). Feature 066's panic-handler emission loop now displays the full inlined-function chain beneath each primary frame as indented
inline> <name> at <file>:<line>lines. Critical for kernel debugging because rustc aggressively inlines generic methods (Option::unwrap,Mutex::lock,Result::expect) — before this, a panic that fired inside such a method would just show the OUTER function with a confusing source location (e.g.,kernel_main+0x109a at <rust>/core/src/option.rs:1015); now it ALSO showsinline> <core::option::Option<u32>>::unwrap at kernel/src/lib.rs:136naming the exact call site. The build-timedwarf-extractortool (userland/tools/dwarf-extractor) walks every CU's DIE tree, capturesDW_TAG_inlined_subroutineentries, resolves names through theDW_AT_abstract_origin → DW_AT_specificationchain, and emitsINLINED_CHAINS/INLINE_OVERFLOWstatic tables. Sweep-line bucketing produces non-overlapping per-RIP-range chains with outermost-to-innermost ordering; the 8-level cap (FR-003) records elided counts inINLINE_OVERFLOW. Measured: 6,169 chains captured for the current kernel ELF; total DWARF table grew from 9.29% → 10.34% of loaded image (still well under FR-011's 20% ceiling). Closes US2 of Feature 066, which deferred this work for time-budget reasons. - #105 BAD-RET scheduling panic — root cause fixed. Three months of intermittent kernel panics during dropbear SSH handshake under multi-execve load (
[BAD-RET] about to switch to pid=N new_rsp=... ret_addr=0x0 — halting) traced to a 128-KiB struct-assignment overflowing the kernel stack. Inkernel/src/net/unix.rs:alloc_pair, the linet.pairs[i] = PairEntry::new()was compiled as "construct on caller's kstack, then move to static slot";PairEntrycontains two[u8; 65536]ring buffers, so 128 KiB landed on a 256 KiB kstack — and overflowed into PHYSICALLY ADJACENT kstack frames, silently corrupting OTHER processes' saved-context area (PCB.kernel_rsp + 56). The all-zeros pattern in the BAD-RET dump was the implicit memset of the ring data arrays. Fix: in-place field reset (no construct), one struct field at a time. Regression test pins both invariants. Found via the sticky HW watchpoint instrumented for #120 — first reproduction after the false-positive filter caught the corruption with a 10-frame backtrace fromcompiler_builtins::memsetthroughunix::alloc_pairtosys_socket. Verified across 572 execves + 250 zombie-reaps on a 5-minute reproducer with zero panics. - DWARF-based in-kernel stack unwinder (Feature 066 — closes #67). Every panic backtrace now ends each line with
at <file>:<line>— sourced from a build-time KALLSYMS-style lookup table compiled from the kernel ELF's DWARF (userland/tools/dwarf-extractoris a new host-target Rust workspace member using gimli). The kernel itself has no DWARF parser; runtime is just binary search on&'staticarrays — panic-safe by construction. Path normalization at build time keeps the embedded strings short: kernel/-relative for first-party (kernel/src/mm/phys.rs),<cargo>/-prefixed for crates (<cargo>/spin-0.9.8/src/mutex.rs),<rust>/-prefixed for stdlib (<rust>/core/src/option.rs) — no developer-host paths leak. SC-001 acceptance: 3/3 kernel frames in the panic backtrace now show file+line (4th is the_startasm trampoline). SC-002 measured: 14% loaded-image growth — the embedded table is ~1.5 MB for 125k line entries; well under FR-011's 20% hard ceiling but above the original 5% soft target (spec updated to reflect measured reality). The infrastructure for inlined-function chain expansion (US2) ships as empty stubs in v1 — kernel accessors + panic-handler iteration are wired and zero-cost when tables are empty; a future feature populates them. CFI walking (US3) similarly deferred. Quickstart:specs/066-dwarf-unwinder/quickstart.md. - FASAN — Frame Allocator SANitizer (Feature 065). Per-frame physical-memory poisoning + ownership tracking + diagnostic accessors layered on
kernel/src/mm/phys.rs. Every allocated frame is filled with0xAABBCCDDAABBCCDD(alloc-pattern sentinel); every freed frame is filled with0xDEADBEEFDEADBEEF(free-poison sentinel). A 1-byte-per-frame shadow array (1 MiB BSS) records the current owner (one ofFREE/KSTACK/HEAP/USERPAGE/PAGETABLE/DEVICE/BOOT/UNKNOWN). The BAD-RET handler in the scheduler now emits three[FASAN] frame=... owner=... sample=[...]lines per panic (failing frame + page±1 neighbours), so issue #105's "kstack contents are all zero, no idea why" mystery becomes a one-repro diagnosis — the developer sees whether bytes are the alloc pattern ("allocated but never written"), free poison ("freed, then handed back without re-init"), or true zeros (something actively wrote 0), plus the owners of the neighbouring frames so corruption from above/below is visible. The kernel panic handler andkassert!path additionally emit a--- FASAN per-PID kstack summary ---block, one line per live PID with the top-of-kstack frame's owner + 4-word sample. The allocator emits[FASAN-XSTATE]warnings on illegal owner transitions (double-free, alloc-over-non-FREE) — non-halting per FR-011. SC-002 budget: ≤10% binary growth — measured at 0.04% (text+datadelta 4,512 bytes; the 1 MiB SHADOW is BSS-only). SC-004 budget: ≤10% boot-time hit —tests/boot/test_boot.pyconfirms all 9 phases still pass with FASAN on. Feature-flaggedframe-poisoninkernel/Cargo.toml(default-on for debug + KASAN; off via--no-default-featuresfor release; OFF stubs preserve every consumer's signature per contract §1.4). 26 productionalloc_frame()call sites migrated toalloc_frame_owned(<owner>)covering kstack/heap/userpage/pagetable/device buckets. Quickstart:specs/065-frame-poisoning/quickstart.md. - Wiki-ready demo GIF + nsh non-TTY fallback (Feature 062).
make demo-gifnow produces a real recording of nsh over SSH —docs/demo.gif, 416 KB, ~15 s playback — usable directly in a wiki article or README. Three pieces: (1) nsh's main loop falls back to a line-bufferedprompt → read → runloop whenEditor::with_configreturnsENOTTY, so scripted demos no longer panic with "Not a tty" (userland/shell/src/main.rs:108); (2)build/scripts/capture-demo-gif.shswitched from the brokennsh$-on-serial wait + paramiko-with-tight-timeouts pattern to asshd started-marker wait + raw-socket banner probe, and runs all demo commands as a single semicolon-chainednsh -c "cmd1; cmd2; ..."via paramiko (per-command exec_command was flaky against MyOS dropbear, ~7 s per cmd and the 3rd hung); (3) cast post-processing trims ~30 s of SSH-negotiation dead time so the GIF starts immediately.tests/demo/demo-commands.shwas rewritten in nsh-compatible syntax (one command per line,#comments stripped by the driver). Colored prompt is rendered host-side using the same ANSI escapes asuserland/shell/src/prompt.rsso the GIF matches what a real TTY would show. Discoveries documented in Learnings.MD include: dropbear in MyOS does not allocate PTYs; a multi-line stdin pipe through nsh's new fallback path triggers a kernel[BAD-RET]scheduling panic (separate issue, not blocking demo); nsh pipes drop output for large left-hand sides (dmesg | tail -5returns empty,echo small | base64works fine). - TSC-resolution timestamps in kmsg ring (Feature 061 — closes #68). Replaces the kmsg ring's 32-bit scheduler-tick timestamp source (10 ms granularity) with a 64-bit nanosecond timestamp derived from the x86 TSC.
/proc/dmesg's text format switches from[<integer-ticks>]to Linuxdmesg-style[<seconds>.<microseconds>]— sub-microsecond precision visible in every dmesg line, panic-tail block, and per-PID syscall trace record (Feature 045 inherits the new resolution transparently)./proc/uptime's first field also switches to the new TSC-derived source. Per-entry layout grew from 256 to 264 bytes (theEntryheader widened from 16 to 24 bytes; the 240-byte message payload is unchanged) — an 8 KiB BSS bump on the always-on ring.SYS_KMSG_READ(440)'s binary struct ABI changed (v1 → v2: seespecs/061-tsc-kmsg-timestamps/contracts/kmsg-entry-format.md); zero current userspace consumers are affected because the text-format consumers (mybox dmesg,mybox strace) parse/proc/dmesgnot the struct. Newmonotonic_ns()accessor inkernel/src/arch/x86_64/timer.rsis the canonical TSC-derived nanosecond clock for future kernel features. Quickstart:specs/061-tsc-kmsg-timestamps/quickstart.md. - In-kernel
dmesgring buffer + GDB workflow (Feature 038): 1024-entry × 256-byte ring in BSS captures everykprint!/debug!line via dual-write at the UART driver; readable from userland via/proc/dmesgor syscall 440 (SYS_KMSG_READ); cleared by writing to/proc/dmesg-clear; the panic handler dumps the most recent 256 entries to UART with--- dmesg tail ---markers so the QEMU serial log preserves pre-panic context;make debugandmake debug-kasanlaunch the kernel under QEMU paused at entry with gdbserver on:1234(seespecs/038-dmesg-gdb-stub/quickstart.md). Note: per-entry size and format updated by Feature 061 — see that entry above. - Live-verified: kernel-side strace decoder works end-to-end (issue #91 — PR #92 follow-up). New
strace_test=1cmdline trigger callscrate::proc::strace::emitdirectly with 7 synthetic records (covering path-decode arms, integer-arg arms, and the catchall) then panics — the panic handler's--- dmesg tail ---block surfaces all 7 records on serial output. Newmake image-strace-testtarget builds the QCOW2 with the trigger baked in; newtests/boot/test_strace_kernel.pyintegration test scrapes the captured serial and asserts each expected record's exact format. Proves the kernel decoder + kmsg-write path + panic-dump surface are correct in isolation from any userland glue. The remaining userland-applet integration (strace ./proground-trip via hvc0/nsh) can now be diagnosed cleanly — anything misbehaving from here is in the userland polling loop, not the kernel emission. - Strace-format syscall trace + userland
stracebinary (issues #70 + #78 — Feature 045 follow-ups). The per-PID trace gate insyscall_dispatchnow emits decoded records like[S 7] open("/etc/passwd", 0x80002, 0o0),[S 7] write(1, 0x20001030, 51),[S 7] execve("/bin/echo", 0x..., 0x...)instead of the v0[S pid/nr]format. Decoder lives inkernel/src/proc/strace.rsand covers the ~10 most-used syscalls (read/write/open/openat/close/mmap/brk/execve/exit_group/fork); any syscall not in the decode table falls through to the v0 catchall, so coverage is purely additive. Path arguments are read from userland via the same*const u8+ null-scan pattern assys_open; flags are emitted as hex (symbolic decoding deferred to v2). Newstracemybox applet atuserland/mybox/src/applets/strace.rs—strace ./prog [args]forks, enables trace on the child via/proc/<pid>/traceinpre_exec, execs, polls/proc/dmesgfiltering for the child's[S <pid>]records, and streams to stderr. v1 supports the fork-then-exec mode only;-p <pid>attach mode and return-value capture are tracked as follow-ups. - Per-CPU current-PCB cache (issue #66 — Feature 045 R3 Option B).
sched::current_pid()was takingSCHED.lock()on every call — once per syscall viasyscall_dispatch, adding ~50–100 ns of uncontended-mutex overhead per syscall. Replaced with a lock-freeAtomicU32cache (kernel/src/sched/percpu.rs) updated at every context-switch commit point (schedule_to_next+exit_current).current_pid()andcurrent_pid_try()now read the atomic in single-digit ns. The panic path'scurrent_pid_tryno longer needs the try_lock fallback (which could return None on contention and forcepid=unknownin the dump). Single-AtomicU32 today; the shape transparently becomes a per-CPU-array indexed by APIC ID when SMP arrives (#75). - Panic dump: register snapshot + page-table chain walk (issues #64 + #65 — Feature 046 follow-ups). The
#[panic_handler]now emits two new blocks alongside the existing dmesg-tail / backtrace / pcb-dump.--- registers ---captures all 16 GPRs + RFLAGS + RIP + CR0/CR2/CR3/CR4 via a single inline-asm spill to a stack buffer (snapshots panic_handler entry state, not the panic site — the actual fault RIP is in the backtrace block).--- page table chain (VA=CR2, CR3=...) ---walks PML4 → PDPT → PD → PT for the value in CR2 (the faulting VA on page-fault panics, informational otherwise), showing the raw PTE + present/not-present at each level and halting at the first missing level. Lock-free, panic-safe, follows the existingDirectWriterpattern. Newwalk_page_table_chain(va) -> [PageTableLevel; 4]accessor inkernel/src/mm/virt.rsis reusable from fault handlers, futurekassert!s, or any other diagnostic that needs to show the translation chain. - Fix: per-channel TTY input buffer —
/dev/hvc0no longer races/dev/tty0/1/2/3for host-sent bytes (issue #82, closes #63). Before this change, every byte arriving on the virtio-console RX queue landed in the same PS/2-fed shared line buffer that all other VT readers were sleeping on; whichever nsh was first offsched::sleep_untilconsumed the bytes, so host input to/dev/hvc0almost never reached the nsh actually listening there. Added a dedicatedHVC0_*set of input statics (canonical line buffer + raw ring + waiter PID), apush_byte_hvc0()producer entry point called fromvirtio::console::poll_rx, and aread_hvc0()consumer entry point dispatched from both branches ofsys_read(redirected fd 0/1/2 + general fd ≥ 3). Echo on hvc0 routes throughvirtio::console::writeback to the host (not the framebuffer). The previously-failingtests/boot/test_hvc0_rx.py(HELLO_FROM_HOSTround-trip) now passes in ~0.00s after socket connect. - Fix: nsh on
/dev/hvc0no longer SIGABRTs at startup (issue #63 partial): two surgical kernel fixes. (1)PTY_MASTER_FS_IDhad silently shared0xF5withHVC0_FS_ID, so every write to/dev/hvc0was being routed intopty::master_write(0)(slot 0 neverin_use→ returns 0 → Rust stdio panics withWriteZero→ SIGABRT). MovedPTY_MASTER_FS_IDto0xF2. (2)sys_read's redirected-fd branch (used wheninitdup2s/dev/hvc0onto fd 0/1/2 before exec'ingnsh) was missing anHVC0_FS_IDhandler and fell through tovfs::read→EIO. Added the missing branch. Banner now flows host-bound through the virtio-console socket. Full host-input echo is gated on a follow-up architectural change (per-channel TTY input buffers). - Per-PID
/proc/<pid>/{stack,wchan}+ kernel symbol-table accessor + symbolized panic backtrace (Feature 049): two new procfs files answer the classic "where is this process blocked?" question without a debugger./proc/<pid>/stackwalks the saved RBP chain of a sleeping task and emits up to 16 RIPs as0x<hex>\nper line;/proc/<pid>/wchanemits a one-line<name>+0x<offset>\nfor the top frame, or[running]/[zombie]/[never scheduled]for non-walkable states. Backed by a new build-time symbol-table extractor (build/scripts/gen-symtab.{sh,awk}) that pipesnm --demangle target/kernel.elfthrough a two-pass kernel link (Linux KALLSYMS pattern) so the table is byte-deterministic and embedded inside the samekernel.elfit describes — measured 6.21% .text growth for 3,183 symbols, well under the SC-005 ≤10% budget. Same accessor symbolizes Feature 046's panic-backtrace block (raw RIPs →0x<rip> <name>+0x<offset>), turning a hex dump into something readable at a glance. Newmake image-wchan-testtarget boots the kernel withwchan_test=1for the end-to-end integration test. Quickstart:specs/049-stack-wchan-symbols/quickstart.md. - Structured kernel panic +
kassert!with PCB context (Feature 046): everypanic!()and every failedkassert!()emits a self-contained, line-oriented block on serial output for post-mortem diagnosis. Panic emission order: existingPANIC <file>:<line> <message>line → existing--- dmesg tail (N entries) ---block (Feature 038) → new--- backtrace (M frames) ---block (frame-pointer walker, max 16 frames) → new--- pcb dump (K live pids) ---block (one line per live PID with PID/PPID/state/last_syscall_nr/CR3).kassert!(cond, msg)is a drop-in replacement forassert!()that on failure emits--- kassert FAILED ---plus message, file:line, current PID, CR3, kernel RSP, last_syscall_nr, and the most-recent 16 kmsg ring entries — then halts. Zero-cost when the condition is true. NewPcb.last_syscall_nrfield is written at every syscall entry and feeds both dump paths. Frame-pointer flag (-C force-frame-pointers=yes) promoted from KASAN-only to ALL kernel builds; binary cost measured at −1.54% (text section actually shrinks slightly at debug-profile, well under SC-005's ≤5% budget — seespecs/046-structured-panic-kassert/research.mdR9). Newmake image-kasserttarget for the integration-test trigger. Quickstart:specs/046-structured-panic-kassert/quickstart.md. - Per-PID syscall trace toggle (Feature 045):
/proc/<pid>/traceexposes a runtime per-task flag. Writing1enables[S pid/nr](and[S blocked pid/nr]) records into the kmsg ring (Feature 038) for syscalls issued by that PID; writing0disables. Output reaches the same surfaces as any other dmesg line —dmesgfrom userland,/proc/dmesgdirect read, orSYS_KMSG_READ(440). Trace records bypass the UART mirror so a heavily traced process does not stall on serial output. Inherited at fork (FR-010), preserved acrossexecve(FR-011). v1 permission model: writer must be the target task or PID 1 (research R1 — degrades from standard own-UID-or-root because MyOS does not yet model UID per task). Replaces the prior compile-timedebug-procCargo feature for the syscall-entry trace point. Quickstart:specs/045-per-pid-syscall-trace/quickstart.md. - Differential syscall harness vs Linux (Feature 040 + transport rework in Feature 041): a corpus of 31 small static-musl C programs (
tests/syscall_diff/corpus/) runs on both the host Linux kernel and inside MyOS2026 via QEMU; the harness diffs observable outputs (exit code, exit signal, stdout, stderr, normalized) and reports per-test PASS / FAIL / KNOWN / SKIP. A TOML allowlist (known-divergences.toml) suppresses by-design divergences with mandatory justifications. Five syscall families are covered (file-I/O, process, signal, memory, time). Invoke withmake syscall-difffor the full corpus,make syscall-diff CASE=<name>to debug one program,make syscall-diff TRANSPORT=sshfor the legacy paramiko/dropbear path. Wired into thecirollup as thesyscall-diffjob — PRs that introduce POSIX-deviation regressions are blocked from merge once Actions is re-enabled at the repo level. The default transport since Feature 041 is virtio-console over a UNIX socket (no dropbear) — code complete + unit-tested; live integration blocked on a kernel-side hvc0/socket bug (#54). The legacy SSH transport remains available via--transport ssh(still hit by #50 intermittently). Operations guide:docs/syscall-diff.md(architecture, CLI reference, troubleshooting, known limitations, Transports section). - Security: per-process syscall allowlist, capability bitmask, verified boot attestation
| Binary | Description |
|---|---|
init |
Stage 1–3: mount root, spawn cloud-init, sshd, nsh |
nsh |
Shell: rustyline REPL (↑/↓ history, ← → editing, Ctrl-R search, Tab completion), ANSI color prompt, persistent /root/.nsh_history, pipes, redirects, &&, banner, help |
mybox |
97 Unix applets via multi-call binary (stripped) |
cloud-init |
cidata provisioning: hostname, SSH keys, runcmd |
dropbear |
SSH daemon (cross-compiled C, key auth, port 22) |
myos-pkg |
Package manager: install, remove, list, verify (signed tar.gz) |
sandbox |
Installs per-process syscall allowlist then exec's target |
exploit-test |
Calls mount(2) via raw syscall; used by T057 regression test |
vboot-check |
Queries SYS_VBOOT_STATUS; prints key fingerprint + chain |
make all # kernel + userland + disk image
make test-unit # 434 kernel unit tests (no QEMU)
make test-smoke QCOW2=dist/myos2026.qcow2 # curated 9-test smoke suite (no KVM required)
make test-slow QCOW2=dist/myos2026.qcow2 # timing-sensitive tests (requires /dev/kvm)
cargo clippy -p mybox --target x86_64-unknown-linux-musl -- -D warnings| Principle | Choice |
|---|---|
| Kernel type | Minimal monolithic (Rust, no_std) |
| Bootloader | Limine v8.x (BIOS + UEFI, single config) |
| I/O model | virtio-only (blk / net / console / rng / scsi) |
| Network | smoltcp 0.11 (pure Rust, no_std) |
| Filesystem | ext2 (custom pure-Rust read/write driver) |
| SSH | Dropbear (userspace, cross-compiled for musl) |
| Userland | Rust + statically linked musl |
| Assembly | ~170 LOC total (entry stub, ISR trampoline, context-switch) |
kernel/ Rust kernel (no_std)
src/
arch/x86_64/ Entry stub, GDT/IDT, APIC, timer, syscall setup
mm/ Physical + virtual memory, heap, demand paging
drivers/ UART, virtio (blk/net/console/rng/scsi), LSI Logic MPT SCSI,
Intel E1000 NIC, generic PCI scanner, framebuffer
fs/ ext2 + VFS (symlink-following) + 64-slot LRU block cache
net/ smoltcp, DHCP, firewall, ethernet dispatch
proc/ Process table, ELF loader, syscall handlers, capabilities
sched/ MLFQ scheduler (3-level, decay, I/O boost, starvation prevention, nice)
ipc/ Pipes
userland/ Userspace crates (musl-static)
init/ Stage 1–3 init
shell/ nsh minimal shell
mybox/ 91-applet multi-call binary (Busybox-in-Rust)
src/applets/ cat, chmod, chown, cp, cut, date, echo, env, false,
grep, head, hostname, kill, ls, mkdir, mv, ps, pwd,
rm, sleep, sort, sed, awk, find, tar, gzip,
nslookup, wget, nc, ping, …(97 total)
pkg/ myos-pkg package manager
cloud-init/ Provisioning agent (hostname, SSH keys, runcmd)
sshd/ Dropbear SSH daemon + host keys
tools/ sandbox, exploit-test, vboot-check, network utilities
bootloader/ Limine config + vendored binaries
build/ Makefile, image assembly scripts, CI helpers
scripts/ assemble-image.sh, fix-ext2-timestamps.py,
setup-verified-boot.sh, sign-release.sh, convert-vdi.sh
tests/ Integration and benchmark test suites
boot/ test_boot.py, test_ssh.py, test_shell.py, test_cloud_init.py,
test_sandbox.py, test_debug_mode.py, test_lsi_scsi.py,
test_e1000_ssh.py, test_vbox_combined.py, test_dual_nic.py,
test_signal.py, test_nanosleep.py, test_futex.py,
test_misc_posix.py, test_scheduler.py, test_linux_elf.py,
test_reproducible.sh
snapshot/ test_rollback.sh
bench/ boot_time.sh, bench_boot_time.py
keys/ Test SSH keypair (test_id_ed25519)
specs/ Feature specs, implementation plans, contracts
001-vm-optimized-os/ Core OS: kernel, userland, verified boot, security
002-rust-busybox/ mybox 30-applet multi-call binary
003-shell-screenshots-demo/ Shell banner + demo artifacts
004-kdebug-copyright/ Compile-time debug feature flags + copyright
005-scsi-e1000-drivers/ LSI Logic MPT SCSI + Intel E1000 NIC drivers
006-syscall-coverage/ Full POSIX syscall layer (60 tasks)
009-mlfq-scheduler/ MLFQ priority scheduler + nice syscalls
010-mybox-core-utils/ mybox expanded to 91 applets (full POSIX core utility set)
011-linux-elf-compat/ Linux ELF binary compatibility (static musl binaries)
022-file-backed-mmap/ File-backed mmap (MAP_SHARED/MAP_PRIVATE, demand paging)
023-cow-fork/ Copy-on-Write fork (lazy frame sharing, CoW fault handler)
024-proc-fs-expansion/ /proc filesystem: self/{fd/,maps,status,exe}, cpuinfo, uptime, net/*
025-networking-userland/ DNS resolver, wget (HTTP+HTTPS), nc, ping — 5/5 integration tests
026-nsh-rustyline/ rustyline REPL for nsh: interactive editing, history, ANSI prompt, Tab completion
027-interactive-console/ Console shell wired to TTY, password SSH, 3 virtual terminals, TUI demo shell
028-cmos-rtc-wallclock/ CMOS RTC driver; clock_gettime(CLOCK_REALTIME) and gettimeofday() return correct UTC epoch
032-virtio-console-rx/ virtio-console RX: receiveq allocated, pre-filled, polled every 10 ms — console now bidirectional
033-console-demo-smoke-tests/ Interactive console wired to qemu-sdl; curated 9-test smoke suite; test_shell.py fixed; KVM tests quarantined
034-terminal-multiplexer/ myscreen: screen-style terminal multiplexer; PTY kernel subsystem; AF_UNIX sockets; detach/reattach; 5 integration tests
035-console-hvc0-fix/ Fix /dev/hvc0 routing: was assigned NULL_FS_ID; now HVC0_FS_ID routes reads→tty::read() and writes→virtio::console::write()
036-socketpair-cloexec-fix/ sys_socketpair(AF_UNIX) backed by pipes; CLOEXEC honoured in sys_pipe2; recv/send pipe fallback; OVMF VARS fix for reliable UEFI boot
[ 0.20s] Booting from Hard Disk ← Limine BIOS stage 2
[ 0.69s] MyOS2026 v0.1.0 ← kernel_main
[ 0.89s] [1] UART ok
[2] mm ok
[3] interrupts ok
[4] drivers ok (virtio-net, virtio-blk, virtio-rng)
[4b] rtc ok (epoch=<unix-timestamp> from CMOS RTC)
[5] fs ok (ext2 mounted at /)
[6a] firewall ok
[6b] net stack ok (DHCP → 10.0.2.15/24)
[7] userspace: launching init
[ 4.46s] → dropbear listening :22
[ 4.83s] MyOS2026 v0.1.0 — type 'help' for built-in commands
nsh$
The kernel ships with an opt-in KASAN-equivalent for catching memory-safety bugs at the corruption site. Build with make image-kasan to produce a sanitized disk image; run python3 tests/boot/test_kasan.py dist/myos2026-kasan.qcow2 for the 7-scenario integration test (5 violations + 2 negatives). The CI kasan-test job runs this on every PR; failures block merges. See specs/037-kernel-kasan/quickstart.md for the full developer guide.
The default build (no --features kasan) is unchanged: zero runtime cost, kernel.elf binary size within 1 % of pre-feature baseline.
- Dynamic linking — only statically-linked ELF binaries run; PT_INTERP (glibc/musl dynamic linker) is rejected with ENOEXEC
- HTTPS wget — TLS (
wget https://...) is compiled in (rustls + webpki-roots) but certificate verification against public CAs requires certificate pinning or a local CA bundle; HTTP (wget http://...) works fully - Package management —
myos-pkginstalls from signed tar.gz; repo tooling and signing pipeline deferred - initrd/initramfs — no preloaded ramdisk support; all binaries live on the ext2 partition
- Loadable kernel modules (monolithic; in-VM module builds deferred)
- GPG-signed release artifacts (persistent ed25519 key used; GPG pipeline not wired up)
strace-equivalent userland tool- POSIX
lstat()does not distinguish final symlink component (stat()andlstat()both follow symlinks)
- OS learning platform — every subsystem fits in your head, written in safe Rust
- Secure ephemeral VMs — sandbox + verified boot + fast teardown via snapshot/rollback
- CI/CD throwaway environments — < 2s boot, 12.5 MB image, SSH ready in < 5s
- Kernel and systems programming research — modify kernel, rebuild, boot in < 2 minutes
- Fork the repository
- Read
specs/001-vm-optimized-os/plan.mdfor the core OS architecture - Read
specs/006-syscall-coverage/plan.mdfor the syscall layer design - Run
make test-unit— kernel unit tests, no QEMU needed - Boot:
make qemu(< 2s to shell prompt) - Open a PR
Good first issues:
- POSIX
lstat()that does not follow the final symlink component strace-style syscall tracer using kernel instrumentation hooks- Dynamic ELF loader (PT_INTERP support) — enables glibc-linked binaries
- GPG signing pipeline for release artifacts
Mozilla Public License 2.0

