Audience: Maintainers, contributors picking the next slice
Maturity: Living document; edit when an item lands or a new gap is found
This page consolidates every multi-session work item that is not yet in tree. Each entry names the surface that owns the gap and the residual that remains, so a contributor can pick one without re-deriving the field.
Policy: when a roadmap item lands, delete its entry here in
the same commit that delivers the code, record the landing in
Design-Decisions, and update the owning
subsystem wiki page. Landed work does not live on this page —
if you find a "shipped / landed / DONE" paragraph here, it is
cleanup debt: move the residual up and delete the rest.
- Bridge phase LANDED (2026-06-10, step 2 of 4). The per-CPU
runqueue locks now exist —
g_runq_locks[acpi::kMaxCpus](classkLockClassSchedRunq, kept OUTSIDEcpu::PerCputo dodge the syscall-stub layout static_asserts + false sharing on the lock-free length/online fields). The four mutation funnels (RunqueuePushOn/RunqueuePop/StealNormalFromPeer/BalancePullOnce) take the owning CPU's lock for the duration of the list+counter mutation under the still-global lock, so theg_sched_lock → runqordering and lockdep edge are validated on every boot. Step 1 (close the unlocked-walker gap) also landed. - Residual (steps 3-4 — the actual contention win): (3) make
the context-switch lock-pass slot 2-deep + dynamically seed a
fresh task's lockdep held-set, then (4) drop
g_sched_lockfrom the pure-Schedule()path (localrunq_lockonly), keep global+local on the blocking primitives (the no-gap double-run invariant requires the lock the task marked itself Blocked under to ride the switch), and convert steal/balance toSpinLockTryGuard. - Blocks on: a profile showing contention on
g_sched_lock. This is the deliberate stop: the bridge phase de-risked the split (infrastructure + ordering proven), and steps 3-4 rewrite the hard-won context-switch UAF-prevention handshake — not worth that risk in the kernel's most dangerous code until a workload shows the global lock actually hurts. The full step-3/4 design (31-site lock inventory, 2-deep slot stack, try-lock steal) is captured in the [[expansion-campaign-2026-06-10]] session scoping. - Cascading items unlocked when steps 3-4 land:
- Index the lockdep / event-trace / soft-lockup
g_per_cpuarrays by current-CPU ID (currently keyed ong_per_cpu[0]aliases). - SMP-stress versions of the RwLock + SeqLock + KMailbox contention self-tests.
- Buddy coalescing + per-CPU lock-free allocator fast paths (frame warm-pool / slab magazine) — correctness is already in place under one global allocator lock; this is the scalability follow-on.
- Move LAPIC-divider + tick-frequency programming out of
arch::TimerInitintotime::TimerConfigure(hz)once an ARM64 / generic-timer backend justifies the abstraction. - (MLFQ priority bands no longer block on the lock-drop — the per-CPU runqueue structure is enough; tracked as T8-01-followon.)
- Index the lockdep / event-trace / soft-lockup
- Landed: the held-class stack is now per-task (swapped at the
context-switch boundary) for sleeping
sched::Mutexclasses and per-CPU for spinlock classes, plus the WITNESS-styleLockKind { Spin, Sleep, Irq }acquire-time taxonomy (LOCKDEP_ASSERT_HELD). The compositor↔fat32 sleeping-mutex false inversion is gone. (History in git; the design constraints — noTask*threaded through every hook, restore after the fresh-AP guard — are documented inkernel/sched/sched.cpp.) - Blocks on: a workload that produces a false inversion the per-CPU + per-task pair doesn't already absorb. None observed since 2026-05-22.
- Residual: the cluster-scoped fan-out (one ICR write to the CPUs of one scheduler cluster, not all peers) needs x2APIC logical destination mode (LDR/cluster addressing) on top of the physical-mode x2APIC already in tree.
- Blocks on: profile evidence that a per-cluster (not all-peer) fan-out is workload-justified — reschedule is single-target, shootdown is kernel-AS-broadcast or per-AS-targeted, never per-cluster. Pre-emptive build avoided. (Clustering v0, NUMA frame allocator, wake/periodic balance, SMT + hybrid P/E bias, hard affinity, MWAIT idle, single-ICR broadcast, and x2APIC enablement all landed — see CPU Topology / Scheduler.)
- Historical finding (audit R1-14, fixed in this audit):
AddressSpace::regions_lockwas acquired in exactly ONE place —AddressSpaceMapUserPage(mm/address_space.cpp:396).AddressSpaceUnmapUserPage,AddressSpaceClearUserMappings,AddressSpaceFork,AddressSpaceLookupUserFrameandAddressSpaceReleaseall read or mutateregions[]/region_countwith no synchronisation at all. Two threads in the SAME address space reach these concurrently — Win32 processes are genuinely multithreaded (kWin32ThreadCap == 8) and Linuxcloneshares the AS — so this is reachable, not theoretical. - Why it corrupts rather than merely races:
UnmapUserPageByIndexcompacts the table by SWAP-WITH-LAST (regions[idx] = regions[last]; --region_count). A concurrentAddressSpaceLookupUserFramescanning upward can therefore miss a live entry that was just moved behind its cursor, or observe a half-copied row. The failure mode is "a mapped page reports as unmapped", which the loaders treat as "safe to map here". - The naive fix is WRONG, and this is the part the finding does
not capture.
regions_lockis a SLEEPING lock —RwLockis built onsched::MutexLock+sched::CondvarWait(sync/rwlock.cpp:81). ButAddressSpaceLookupUserFramehas a caller that runs holding a SPINLOCK:debug/breakpoints.cpp:848 ResolveStoppedUserByte(...)is annotated/* MUST hold g_lock */, andg_lockis async::SpinLock(breakpoints.cpp:105). Adding a shared acquire to the read path would sleep while holding a spinlock — a worse defect than the race being fixed. - Shape a real fix has to take. Separate the table's STRUCTURAL integrity from the long operations around it: a short IRQ-safe spinlock covering only the scan / swap / count update, with allocation/free, page copying, cross-subsystem calls, and TLB shootdown waits kept outside it. Bounded page-table edits belong inside the structural commit; allocating their intermediate tables does not.
- Implemented on the audit branch: each AS now has a task-context
sched::Mutextransaction lock above the existing structuralsync::SpinLock. Map operations inspect under the spinlock, prepare region-table storage and up to three page-table frames outside it, then commit the PTE and ledger atomically under it. Unmap/protect detach or rewrite under the spinlock, drop it, then complete the TLB shootdown and frame retirement while the mutex prevents a same-VA mutation from overtaking them. User-AS reclamation snapshots the active CPU mask while migration is pinned, walks the sparse ready-CPU set, and waits for confirmed per-target IPI completion; there is no timeout-based reuse of an owned frame. Empty user-half table paths are pruned during the structural commit, and leaf/table frames are released only after every targeted peer acknowledges invalidation. Fork snapshots one row/PTE at a time and performs frame allocation/copying outside the spinlock. Readers, including the breakpoint resolver, still take only the bounded spinlock and never sleep. - Process/cross-AS lifetime slice implemented on the audit branch: Win32 process-handle slots now have an IRQ-safe owner lock. Lookup takes a target reference before dropping that lock; close and final drain detach rows under it and release afterward. Every VM/query/terminate/info/section consumer holds the transient reference through the operation. Cross-AS read/write uses a bounded address-space transaction-copy API, so PTE resolution, permission validation, and direct-map access cannot race unmap/protect/remap; caller user-copy runs outside the AS transaction. The scheduler reaper now removes task lookup visibility before dropping its Process/AS references, and public borrowed PID/TID lookups are replaced by retained, existence-only, or scheduler-owned by-ID operations. Owner Jobs drain at the last-task boundary, and SpawnEx installs inherited stdio before the child Task becomes runnable.
- Remaining lifetime contracts: raw frame lookup remains for callers that must be classified as pre-publication/stopped-task safe or moved behind a transaction operation. Win32 section mapping must pin its section and frames before it can wait for the AS transaction, and its handle/view/W^X ledgers need serialized reserve/publish/retire state. Multi-threaded fork also needs sibling quiescence, COW, or an explicit rejection contract to promise a coherent memory snapshot.
- Verification boundary: source diff/format checks are complete. Full MSVC build, rebuilt tests, multi-vCPU QEMU boot, allocation-failure injection, and concurrent map/protect/unmap stress remain required.
- Historical blocker: deciding between those two, since it changes an
mm-core invariant. Not attempted as a drive-by:
address_space.cppis the highest-blast-radius file in the tree and a partial fix here (locking writers only, leaving the spinlock-holding reader unsynchronised) would buy very little while looking like a resolution.
- Resolved on the audit branch:
AdaptiveMutexretains its public API but delegates acquire, try-acquire, release, FIFO enrollment, and direct handoff tosched::Mutex. The rawTask*owner and the split owner-check/wait-queue enrollment are gone, so scheduler lifetime accounting covers every holder and a peer unlock cannot overtake waiter publication. - Compatibility contract: the current implementation deliberately does not
spin. A small publication spinlock protects only the race-free diagnostic
snapshot used by
AdaptiveMutexIsHeld; it is not a second ownership source and cannot be used to guard protected data. - Verification: the deterministic self-test waits until the contender is observably blocked before allowing the owner to release, then requires FIFO handoff and successor release. Source/format and structural checks are complete; delete this section after the final strict multi-vCPU boot gate.
- Landed: ring overflow now drops the incoming (newest) scan code
instead of advancing the task-owned
g_ring_tailfrom IRQ context. The IRQ writes onlyg_ring_head; readers write onlyg_ring_tail, so the ring no longer has two unsynchronised writers on SMP. - Trade-off: under overflow, a new byte may be lost instead of the oldest queued byte. This is intentional: preserving cursor ownership is more important than the previous overflow preference, and either policy loses input once the consumer is too slow.
- Residual: the raw API remains single-reader, and the blocking
reader still uses the existing
Cli()check-then-block handoff.
- Landed: scheduler result-bearing cancellable WaitQueue, mutex, and
condvar APIs opt a suspended call stack into cross-queue detach under
g_sched_lock. A killer only publishes intent and makes the Task ready; the resumed Task receivesCancelledand unwinds its own references and locks before the outer syscall/trap boundary finalizes it. Condvar cancellation re-acquires the companion mutex on every return path. - Integrated: KEvent, KSemaphore, KMailbox, KWaitable wait-any, and the GUI message wait use the cancellable APIs and preserve cancellation as a distinct result through their user-visible adapters. Hostile structural contracts guard dequeue ownership, unwind ordering, timeout accounting, and the rule that a foreign CPU never destroys a suspended kernel stack.
- Process-wide kill is linearized with spawn: each Process now owns a
monotonic Task-publication tombstone, separate from lifecycle. PID and
retained-Process kill close it under
g_sched_lockbefore their exact task scan; first and additional Task publication check it under that same lock and fully roll back on rejection. Individual-TID kill does not close the Process, and lifecycle remains Published until last-Task reap. - Residual: ordinary kernel-only/non-result-bearing waits remain intentionally non-cancellable because their callers cannot report and safely unwind interruption. Each additional user-visible blocking API must migrate deliberately or await its natural producer. Runtime SMP kill-versus-wake and kill-versus-spawn stress is still required beyond the structural contracts and exact object compilation gates.
- Residual: shadow-memory mapping, compiler-plugin
integration, per-access shadow lookup. Big lift; deferred until
a use-after-free hunt needs it. (Slab allocator + freed-object
poison landed; automatic KMalloc→slab routing for ≤512 B
allocations landed 2026-06-10 — 8
kmalloc-Nirq-safe caches, route-header discrimination,[kmalloc-route-selftest]boot gate; seekernel/mm/kmalloc_route.h+ Design-Decisions.)
- F-050 (timer-IRQ preemption livelock) landed 2026-06-08 — the fix,
kill-switch (
g_timer_nest_defer_enabled), and verification are recorded inwiki/reference/Design-Decisions.mdanddocs/usability/findings.md; not repeated here. - Still open: re-check F-040 (intermittent hung-task soft-panic,
selftest-42stuck under the sameresource-vector load that triggered F-050). Both are saturation-induced scheduler-progress failures and likely share a root; confirm whether the F-050 nesting-defer also clears F-040, or whether F-040 needs its own fix. Evidence:docs/usability/findings.mdF-040 row (calendar run-1 serial).
These E-8 findings each cite a concrete rubric bar the app does not meet, but closing the gap needs a kernel/driver subsystem that does not exist yet — so they are filed, not patched (a fake slider / read-only "selector" would be worse than the honest read-only panel).
(F-029 — Settings ▸ Display runtime resolution selector — landed
2026-06-08: a real virtio-gpu modeset path + revert-timeout. See
wiki/drivers/Graphics-Drivers.md ("Runtime modeset") and
wiki/reference/Design-Decisions.md.)
- Evidence:
docs/usability/findings.mdrows F-019 / F-030; campaign screenshots underdocs/usability/screenshots/.
Each must be honoured when the matching surface lands, not
retrofitted after. See
Linux-CVE-Audit for the
verdict matrix. (Classes E, M, N, O, CC, FF, GG, II-scaffolding
landed.)
- Class D — COW /
fork(). Dirty-bit clear-and-fault must be atomic w.r.t. any region-shrink primitive (madvise(DONTNEED)). Mirror Linux'sFOLL_WRITEgate in the v0 design. - Class C — zero-copy sendmsg / IPsec. Every externally-backed skb fragment carries an ownership marker; every in-place transform refuses to operate on a marked fragment. Bake into the network-stack ABI from day one.
- Class B — user-facing crypto API. An AF_ALG-equivalent must refuse src/dst aliasing on user scatterlists for any op that doesn't byte-copy the full output.
- Class I — Bluetooth upper stack. L2CAP / RFCOMM / SDP parser invariants per class C.
- Class L — IPv6 reassembly. Every fragment length/offset
comparison uses
len > end - offform (neverend - len). - Class K — FS write paths. Re-audit when ext4 write / NTFS directory parsing / any write-remount path lands.
- Class V — programmable kernel filters. Do not adopt an unprivileged-JIT BPF-equivalent; gate any programmable filter behind a capability or a formally-verified interpreter.
- Class W — GPU command submission. Interpose a kernel translation step producing a verified-shape submission the user cannot edit post-validation, before any user-mode GPU command-buffer surface.
- Class II follow-up (apply the KASLR slide). Candidate slide
is computed at boot (
KaslrGetCandidateSlide); the follow-on builds the kernel PIE, emits a relocation table the early-boot stub iterates, applies the slide, and flipsKaslrGetKernelSlideto return it. Same work as T5-03. Must land before any multi-tenant deployment. - When to revisit: every time a high-impact public Linux/Windows kernel CVE drops, walk the audit doc and update verdicts before the next slice lands in the affected area.
- Scope: write
IA32_S_CET/IA32_PL0_SSP, allocate shadow stacks, recompile with-fcf-protection=branch. - Blocks on: kernel-image rebuild flag wiring + per-task
shadow-stack allocator + per-IDT-vector ENDBR64 prologue.
Probe (
arch::CetGet) is in place to gate the enable code. - When to land: when a test-fleet machine advertises CET-SS / CET-IBT and a workload benefits from software-enforced CFI on top of the silicon protection.
- Status: runtime probe
(
arch::CpuMitigationsGet().needs_kpti) is in tree; on aRDCL_NO=0boot it emits a loud serial WARN. - Why deferred: every CPU in the hardware-target matrix
reports
RDCL_NO=1in silicon, making KPTI a 5–30% syscall cost mitigating an attack the hardware already prevents. - Re-open triggers: a target-fleet CPU lacking
RDCL_NO=1, or a workload that crosses a trust boundary the hardware can't enforce.
- Residual:
kernel/fs/fat32.cpp:68declares one globalsched::Mutex g_fat32_mutex(Fat32GuardRAII at every public entry) protecting both metadata (BPB, FAT chain cache, path cache) AND the single I/O staging bufferg_scratch[4096]. Every lookup / read / write / mkdir / rename serializes on it. Correct (the recursive-entry handling inFat32Guard::Fat32Guardis the standard pattern) but two saturation corners are visible without rewriting the locking:- Priority inversion — there is no priority inheritance
today. A low-priority task holding the mutex while a
high-priority task waits is blocked by a peer at the same
scheduling class. v0 has one priority band so the symptom
is "fair-share starvation under contention," not a hard
hang — but
Process::win32_priority_classis wired (T8-01-followon) and the moment band-aware enqueue lands this becomes a real inversion. - Livelock under wake-storm — many tasks repeatedly
contesting
g_fat32_mutexspend cycles waking + parking instead of doing FS work. Repro under stress=cpu workloads that touch FAT32 from the boot tail:fs/fat32 : lookupdebug lines fire hundreds of times per second per worker, each round-tripping throughMutexLock/MutexUnlockand the per-task held-stack snapshot/restore.
- Priority inversion — there is no priority inheritance
today. A low-priority task holding the mutex while a
high-priority task waits is blocked by a peer at the same
scheduling class. v0 has one priority band so the symptom
is "fair-share starvation under contention," not a hard
hang — but
- Lock-free path-cache fast path — LANDED (2026-05-22). The
smallest-concrete-fix bullet's original "per-CPU
g_scratch" approach turned out to be invasive (the buffer is read throughout the parsers, not just byReadSector), so the actually-smallest fix that helps shipped instead: a seqlock-guardedPathCacheGetSeqlockprobed BEFOREFat32GuardinFat32LookupPath. Every cache-hit lookup — the boot-storm pattern of repeated NOTES.TXT / TEST.* / TRTEST.BIN / KERNEL.FIX probes — now skips the mutex acquire- held-stack push + cli/sti + release entirely. Writers
(under the mutex) bump a per-entry
write_seqto odd before fields, back to even after; readers (lock-free) snapshot the seq before + after their copy and bail on any mismatch. The generation counter store became__ATOMIC_RELEASEso concurrent invalidation downgrades to a miss instead of a stale entry. Saves aMutexLock/MutexUnlockround-trip per cache hit — observable ontools/test/fat32-concurrent.shcontention metric.
- held-stack push + cli/sti + release entirely. Writers
(under the mutex) bump a per-entry
- Residual (per-CPU
g_scratch+ lock-drop during block-IO): the actual "release the mutex during the slow block read" win still needs the buffer split. Audit-wise that's: thread ascratch_ptrparameter through ReadSector / ReadCluster and the BPB / DirEntry parsers in fat32.cpp, fat32_dir.cpp, fat32_lookup.cpp, fat32_read.cpp, fat32_write.cpp, fat32_create.cpp — about 40 call sites and every consumer line that readsg_scratch[N]. With the buffer per-CPU, the mutex can be dropped around theBlockDeviceReaditself (the slow path). Larger but mechanical; gated until a workload shows the path-cache fast-path doesn't already absorb the contention. - Baseline measurement (2026-05-22 — gates the refactor):
tools/test/fat32-concurrent.sh 30on x86_64-release reports zerofs/fat32 : lookupdebug lines, zeroMutexLock waiterparking sentinels, zero non-deliberate lockdep inversions, and zerofs/fat32 [E]lines over the 30 s window. The path-cache fast path is doing its job — boot-storm probes (NOTES.TXT / TEST.* / TRTEST.BIN / KERNEL.FIX et al.) all retire lock-free before the slow walker is consulted, so the driver-wide mutex never serialises under the present workload. Per the gate below, the per-CPUg_scratch+ lock-drop refactor stays deferred — the cost (≈ 120 reference-site edits across 5 TUs, in the same area as the just-landed SMP=8 UAF fix) does not buy a measurable win today. Revisit when a workload shows the seqlock probe missing (e.g. write-heavy + sustained eviction beyond the 32-slot cache) or when a profile attributes wall-time to the in-mutexBlockDeviceRead. - Larger refactor (deferred): split into per-volume mutex + per-cache RwLock + lock-free FAT entry cache. Wants its own slice once the path-cache fast path + per-CPU scratch are measured.
- Saturation harness:
tools/test/fat32-concurrent.shspawns the linux-smoke synfs + win32 PE smokes concurrently and captures the boot log. Look forfs/fat32 : lookupline-rate vsMutexLock waiterparking lines as the contention signal. (Script-side fix landed 2026-05-22 — the|| echo 0fall-back was chained ontogrep -c, which already prints 0 on no-match and exits 1, so on a clean run the variable captured "0\n0" and the arithmetic below it bombed with "syntax error in expression". Replaced with; trueso a clean baseline run completes its report.) - Blocks on: evidence that the path-cache fast path didn't close the live livelock corner. The 2026-05-22 baseline run above shows it HAS closed it under the present workload, so this entry stays gated until a future workload shows the symptom.
- Residual: teach
Process::rootto carry aVfsNode(or a thinVfsDir*handle) so a sandboxed process can be rooted at a non-ramfs subtree (e.g./disk/0/SANDBOX). Today every process root is aconst RamfsNode*; trusted roots see the global mount namespace by policy and custom roots can expose individual graft points, but the root itself can't be a non-ramfs backend node. The wider syscall surface (open / stat / readdir) still lands inRamfsNode*for ramfs fall-through — migrating those is a per-syscall follow-on once a workload demands a non-ramfs sandbox root. (Global-namespace VFS mount registry + cross-mount resolver landed.)
In rough priority:
- Native DuetOS FS — journalled, ext-like, done in Rust. Partly landed (DuetFS v3) — see DuetFS follow-ups below.
- NTFS read-only — required by the Windows-PE pillar to load
a
.exefrom a real NTFS partition. (NTFS metadata walker + read path landed, including VFS integration:VfsResolveon an NTFS mount surfaces anNtfs-taggedVfsNodethat the shell read path streams viaNtfsReadMftRecord→NtfsResolveData→NtfsReadFile; ext4 read-only landed identically — seeExt4Lookup/NtfsLookupinkernel/fs/mount.cppand the[ext4-selftest]/[ntfs-selftest]"VFS resolve verified" boot gates. Both walk multi-component paths (/sub/file): ext4 viaExt4FindInDir, NTFS viaNtfsFindInDirover each record's resident$I30index. ext4 file reads follow depth>0 extent trees (MapLogicalBlock, capped atkMaxExtentDepth=16). NTFS large ($INDEX_ALLOCATION-spilled) directories are walked since 2026-06-10 (multi-run runlist +$BITMAPgating + INDX USA fixups; bounded linear scan — b-tree VCN descent is the remaining GAP inntfs.cpp). Residual: neither FS follows symlinks/reparse points; ext4 htree directories are unwalked. NTFS write is a separate item — T7-04 below.)
- Residual:
Fat32Probenow adopts ONLY DuetOS-owned volumes (BPB serialkDuetOsVolumeId+ labelkDuetOsVolumeLabel, viaFat32VolumeIsDuetOsOwned). A FAT32 volume without those markers — a Windows EFI System Partition, a real Linux FAT, a USB stick — is recognised and logged but not registered, so it can never becomeFat32Volume(0)and have the boot persistence sinks write into it. This closed the bare-metal vector where DuetOS wroteKERNEL.LOG/KERNEL.FIXinto a foreign partition. - Gap: the long-term FAT32 interop-read goal (mount a foreign
FAT read-only for
.exeloading / data import) now needs an explicit, user-invoked, read-only mount path that bypasses the ownership gate deliberately — it must register the foreign volume at an index ≥1 (never slot 0) and mark it read-only so no sink targets it. Not wired at boot today; marked// GAP:inkernel/fs/fat32.cpp(Fat32Probeforeign-volume branch). - Precondition (geometry validation) for this slice: today the
divide-by-zero / cluster-size safety of the FAT walkers rests on
Fat32Probe's implicitbytes_per_sector == 512/sectors_per_cluster != 0pin (fat32.cpp), which the adopt path runs before any access. A foreign-volume mount that builds aVolumewithout that screen would exposeReadFatEntry(byte_off / bytes_per_sector) and theoffset / cluster_byteswrite-path math to a divide-by-zero panic (2026-06-17 audit SEC-001/007). When this slice lands, extract a singleValidateGeometry(const Volume&)— power-of-twobytes_per_sector ≥ 512withbytes_per_sector * sectors_per_cluster ≤ sizeof(g_scratch),sectors_per_clustera nonzero power of two,num_fats ≥ 1,fat_size_sectors ≥ 1,root_cluster ≥ 2,total_sectors ≥ data_start_sector— and call it from EVERYVolume-building path, not just adopt. (Wideningbytes_per_sectorpast 512 also requires bumpingg_scratchand thesizeof(g_scratch)/512guards in lockstep.) - Owner:
kernel/fs/fat32.cpp,kernel/fs/mount.cpp.
- Residual: an unforced panic on an installed laptop is the
last step to graduate this from "shipped" to "lived through it
once." The encode + transport layers (QEMU debugcon + in-RAM
minidump + NVMe/AHCI reserved-region + installer
kDuetCrashDumpTypeGuidpartition) are all in tree. - Safety invariant (landed): the disk-persist path writes ONLY
into a DuetOS-owned
kDuetCrashDumpTypeGuidpartition, discovered viaGptFindCrashDumpRegionand bounds-checked byGptCrashDumpRegionSane. There is no "tail of namespace" fallback — on a disk DuetOS didn't partition (a real machine's SSD with Windows/Linux installed) a crash dump is NOT written to disk (the serial/debugcon copy still emits).DiskPersistSelfTestSKIPs (rather than writing) when no owned reservation exists, so the real-HW verification above requires booting the installer first to lay the crash-dump partition; until then disk persistence is intentionally inert.
- Residual: (1) real-hardware audible validation (no HW in
CI — the QEMU smoke proves the routed-codec DMA path:
[audio-selftest] DMA LPIB advanced (routed, audible path)); (2) per-producer write cursors — today producers all choose their ownframe_offsetand the additiveWritePcmS16Stereopath composes (saturating-add) when two writes hit the same offset, but staggered-offset multi-stream needs a per-producer cursor table anchored ahead of LPIB. (Saturating-add mixer + explicitWritePcmS16StereoOverwritefor fill-the-buffer producers landed.) - Owner:
kernel/drivers/audio/,kernel/subsystems/audio/.
- Residual / blocks on: real-hardware verification cycles;
firmware-package signing root / key IDs; per-vendor MSI/MSI-X
IRQ wiring; iwlwifi TFD descriptor build / doorbell / per-RBD
data buffers; installer integration for the offline Wi-Fi
firmware kit (
tools/firmware/prepare-wifi-firmware.pyoutput staged from install media before the network picker opens). The AR9271/AR7010ath9k_htcopen-firmware scaffold is in tree (kernel/drivers/net/ath9k_htc{,_fw,_upload}.{h,cpp}) but needs a physical dongle — open firmware exists for no on-board commodity Wi-Fi chip. (Data-decode + control tier + crypto + 4-way handshake + per-vendor upload + ring scaffolds + regdb US/EU/JP + 802.11d Country-IE intersector all landed; 17 self-tests pass.) - Unlocks: Network flyout SSID picker, Settings → Network → Wi-Fi tab, captive-portal handler.
- Owner:
kernel/drivers/net/wireless/,kernel/net/wireless/.
- Residual: PCIe MSI-X negotiation (IVAR LUT writes at
CSR_MSIX_IVAR_AD_REG = 0x2890, route every cause to vec 0 for single-vector start); per-TFDiwl_pcie_txq_build_tfd(legacy format: 20 TBs,__le16 hi_n_lenpacked,HBUS_TARG_WRPTR = 0x460doorbell); RX queue init viaFH_RSCSR_*(0xBC0,0xBC4,0xBC8— note write-ptr must be multiple of 8);iwl_rx_packetcmd dispatch onREPLY_RX_MPDU_CMD→ wdev::OnDataRx. ALIVE handler in MSI-X "other" vector. - Reference:
drivers/net/wireless/intel/iwlwifi/pcie/{tx,rx,trans}.cin Linux. Start with legacy gen1 (7000/8000/9000) — gen2's BC table + dynamic scheduler is a separate slice. - Owner:
kernel/drivers/net/iwlwifi_rings.cpp(598 lines),kernel/drivers/net/iwlwifi.cpp.
- Residual: post-firmware-upload HTC state machine. Wait for
HTC_MSG_READY_IDonUSB_REG_IN_PIPE, sendHTC_MSG_CONFIG_PIPE_ID, thenHTC_MSG_CONNECT_SERVICE_IDforWMI_CONTROL_SVC/WMI_BEACON_SVC/WMI_MGMT_SVC. SurfaceWmiSend(cmd_id, buf)to wdev.WMI_INIT_CMDID→WMI_SET_CHANNEL_CMDID→WMI_START_RECV_CMDIDlights up the scan path. - Reference:
drivers/net/wireless/ath/ath9k/{htc_hst,hif_usb}.c. - Owner:
kernel/drivers/net/ath9k_htc.cpp(301 lines).
- Residual: plug in a high-DPI USB mouse and verify the device-supplied HID Report descriptor produces the expected 12/16-bit X/Y layout, button mask, wheel, and AC-Pan fields on real interrupt-IN reports. (Descriptor-driven decoding + injector + synthetic self-tests landed.)
- Owner:
kernel/drivers/usb/.
- Today: the RCS ring at MMIO 0x2000 is programmed and the boot
self-test verifies
MI_STORE_DWORD_IMMread-back. Everything graphics-accelerated still falls back to a software rasterizer. - Plan (research landed 2026-05-29 — see
GPU-Implementation-Notes§Intel): five slices, in order —- Forcewake + GT-init — hold RENDER+GT domains (Gen9 set/ack
0xA278/0x0D84+0xA188/0x130044) with the Gen9–11 fallback-ack erratum, RC6 off, un-stop the ring viaRING_MI_MODE. - GGTT manager — encode 64-bit PTEs (
phys | present, LM=0), write through the BAR0 GTTMMADR upper-half alias, scratch-fill all slots, allocate GPU-VA above the GMADR aperture. - Batch submission + breadcrumb —
MI_BATCH_BUFFER_START(full 48-bit lo/hi addr) from a GGTT batch,wmbbefore theRING_TAILdoorbell, PIPE_CONTROL post-sync seqno + poll. - 2D BLT → GDI accel (the T4-03 win) —
XY_COLOR_BLT(ROP0xF0fill) +XY_SRC_COPY_BLT(ROP0xCCcopy) on the BCS ring; wire GDIFillRect/BitBltto it. - Display detect/modeset (independent) — GMBUS EDID read +
SDEISR/GEN11_DE_HPD_ISRconnector detect + primary-plane reprogram (keep firmware timings; defer PLL math).
- Forcewake + GT-init — hold RENDER+GT domains (Gen9 set/ack
- Verification ceiling: QEMU has no Intel-iGPU model, so the encoders (PTE / MI_* / BLT command builders) are pinned by boot self-tests asserting exact DWORDs (run + PASS under QEMU), but the MMIO submission paths are gated and unverified on silicon — they need a Gen9 NUC (Skylake/Kaby-Lake, no Optimus) + serial UART. The non-destructive proof ladder is in the notes page.
- Blocks: GPU-accelerated GDI paint (Track 4 → T4-03), DirectX real-device backends, multi-monitor mode-set.
- Owner:
kernel/drivers/gpu/intel_gpu.{h,cpp}+ a new GGTT/BLT unit.
- Residual: per-vendor register backlight (Intel/AMD PWM,
vendor WMI / Fn-key hotkeys) for laptops that do brightness
outside ACPI
_BCM; wire the UI brightness control + Fn-key events toAcpiBacklightSet. (ACPI_BCL/_BQC/_BCMpath + EC driver landed.)
Live edge-case index — the v0 happy path skips these:
kernel/drivers/net/iwlwifi_rings.cpp— legacy <7000-series RBD format; real MSI-X interrupt-driven dispatch (TX-completion polling + periodic-poll wiring landed).kernel/mm/dma.cpp— ARM64 port (dsb ishst+ per-linedc cvac).kernel/subsystems/translation/translate.cpp—rseq(restartable sequences).
Re-derive the full inventory with git grep -nE "// (STUB|GAP):".
The governing contract — default to inert; mutate persistent / physical hardware state only on positive DuetOS ownership + explicit operator action — lives in
security/Hardware-Safety. That page carries the full pre-landing precondition table: the safety gate each unimplemented risky controller (UEFI NVRAM, SPI flash, GPU VBIOS/fan/clock, NIC EEPROM, voltage/RAPL/thermal MSRs, Wi-Fi TX power, secure-erase, …) must ship in the same slice that implements it. The items below are the active safety work; everything else is the "don't build the writer without its gate" rule enforced at review.
- Landed 2026-06-06: Intel VT-d is now enforcing by default
(
DUETOS_IOMMU_ENABLEdefaults ON). It builds a full identity map (IOVA==phys, 0..512 GiB) + programs GCMD.TE when a DMAR is present; every existing driver's physical-address DMA keeps working while a rogue device is confined. No-ops without a DMAR;iommu=offcmdline escape hatch; verified underDUETOS_IOMMU_DEVICE=1 tools/qemu/run.sh(translation ENABLED, all device I/O works, 0 faults). - Residual: (1) AMD-Vi is parse-only (IVRS) — register decode /
paging / enable deferred until an AMD test machine exists. (2) DMAR
fault reporting landed 2026-06-06 (
VtdDecodeFault+LogAndClearFaultsread FSTS + the fault-record buffer after enable and log + clear any pending DMA fault;VtdFaultPoll()is wired into thekheartbeatloop (silent when clean — no per-beat spam — no-op when VT-d is off);[vtd] no DMA faults pendingverified). Residual: wire FECTL + a fault MSI so faults raise an interrupt instead of a once-per-beat poll. (3) Interrupt remapping (intremap) is decoded but not programmed. (4) Per-device domains (real isolation vs the shared identity map) are a later slice. - Precondition for every new bus-master driver: map only driver-owned buffers into device address space; validate descriptor targets. (Hardware-Safety pre-landing row "DMA without IOMMU".)
- Landed 2026-06-06: runtime surprise-removal detection for both
block drivers. A SATA/NVMe device unplugged or hard-link-dropped while
running is detected via the all-ones MMIO-decode sentinel (
kMmioGone), SATAPxSSTS.DETloss, or NVMeCSTS.CFS, and latched offline so I/O fails fast instead of spinning the full per-command timeout against absent hardware. The hot poll loops (IssueSlot0,SubmitAndWait) bail in microseconds; idle devices are swept byAhciHealthPoll/NvmeHealthPollfrom thekheartbeatbeat (next toVtdFaultPoll); each loss leaves aKLOG_WARN+kStorageDeviceGoneprobe +StorageErrorereport. Predicate self-tests ([ahci/nvme-selftest] PASS (surprise-removal predicate)) run unconditionally. Full rationale:security/Hardware-Safety→ Runtime hardware faults. - Residual: (1) Re-attach — bringing a re-plugged drive back
online (re-enumerate, COMRESET / NVMe CC.EN reset, re-IDENTIFY,
re-register with the block layer) is unimplemented; a latched-offline
device stays offline until reboot. (2) Block-layer unregister — a
vanished device's
BlockDeviceRegisterhandle leaks (noBlockDeviceUnregisteryet), so the name slot isn't reclaimed. (3) A PCIe hot-plug IRQ (Downstream Port Containment / PME) would replace the once-per-beat poll, same way the VT-d fault MSI replacesVtdFaultPoll. (4) xHCI/USB + NIC surprise-removal detection is not yet wired — the same all-ones sentinel pattern applies and is the natural next slice.
- Landed 2026-06-06: the mechanism.
DiskRegionIsOwned(handle, lba, count)+ an owned-region registry (BlockOwnedRegionAdd) + an owned-write enforcement mode (BlockOwnedWriteSetModeOff/Advisory/Deny) live at theBlockDeviceWriteboundary: under Deny a write not fully contained in a registered owned region is refused. The single property that supersedes the per-call-site ownership checks.BlockOwnedRegionSelfTestproves containment / straddle / wrong-handle / wildcard + a RAM-disk allowed/denied write pair. Default mode is Off — no behaviour change yet. - Registration pass landed 2026-06-06 (boot writers): RAM scratch
devices auto-own on create; the FAT32 system volume's partition
registers at
Fat32Probeadoption.BlockOwnedRegionAddresolves a partition handle down to its parent disk + LBA offset and owns the region in BOTH terms (the chokepoint runs at the FS-facing handle AND again whenPartitionBlockWritere-enters on the parent). Anownedwrite=advisory|denycmdline opt-in drives enforcement. Verified at boot: zero writes fall outside an owned region under both Advisory and Deny — the registry fully covers the boot write set, so Deny does not break the boot. - Residual: (1) register the remaining writers before flipping the default — the disk installer's target (declared before it formats a not-yet-owned disk), disk-backed DuetFS volumes, and the crash-dump partition (panic-only). (2) Runtime soak under Advisory (boot is clean; confirm steady-state app/FS writes are too) → then flip the default to Advisory and finally Deny. The mechanism is proven enforceable; the flip waits on installer/DuetFS registration + the runtime soak.
- Residual:
ProbeBlockHandlemounts a DuetFS volume on a bare superblock-magic match with no DuetOS-owner GUID. Real-world risk is very low (a foreign disk would need valid DuetFS magic at the exact offset), but it's inconsistent with the FAT32/exFAT ownership gates. - What's needed: add a DuetOS-owner GUID/UUID to the DuetFS
superblock (Rust crate) and verify it in
ProbeBlockHandlebefore mounting. Optional / low priority.
- Residual: the wireless stack has no TX-power programming yet (safe
by absence). When live silicon TX lands (see Drivers → iwlwifi /
ath9k_htc), TX power must be clamped to the lesser of the regulatory
limit and the EEPROM-calibrated max, defaulting to the most-restrictive
("world") domain until a country is set — exceeding limits overheats
the PA/PHY and is illegal. See
drivers/Wireless-Regulatoryand the Hardware-Safety "Wi-Fi TX power" row. Precondition, not a standalone slice — it ships with the TX path.
The 2026-06-17 sweep of code merged since the 2026-06-06 audit fixed the
guest-reachable kernel-memory-corruption + cross-process findings (VK
SPIR-V id bounds + call-depth cap; Win32 job owner_pid + spinlock +
retain-before-kill; vmap shift static_assert; TCP rtx_count
underflow guard — all in the same slice). These lower-severity residuals
were filed rather than rushed, each because the right fix is a design
choice, not a one-liner:
SYS_SECTION_CREATEresource-exhaustion DoS (Medium). Ungated, so a zero-cap PE canCreateFileMappingeight 4-MiB sections and exhaust the global pool + 32 MiB of frames. Do not gate onkCapSpawnThread(semantically wrong — mapping memory ≠ spawning threads, and it would deny a legit capless PE its file mappings). Correct fix: a per-process section budget (sections already carry a per-process handle table, so the owner is known) capping how many pool slots / bytes one process can hold. Owner:win32/section.cpp.- Cross-process
PostMessage(DoWinPostMsg) (High-ish / design). Uses the rawHwndToCompositorHandle(no owner check), so any PE can postWM_QUIT/WM_CLOSEto another process's window (DoS) with no cap. Real Win32 does allow cross-process posts; the right model is UIPI-style (restrict by integrity/owner for the dangerous system messages while allowing benign ones), not a blanket cap gate that would break shell↔app messaging. Needs the integrity-level surface to exist first. Owner:win32/window_syscall.cpp. - Win32 pipe pool calls
linux::internal::Pipe*directly (Rule 5).named_pipe_syscall.cpp/pipe_syscall.cppreach into the Linux subsystem's pipe pool — a subsystem-to-subsystem coupling that shares resource accounting across ABIs. Fix: extract the pool into a kernel-ownedkernel/ipc/pipe_pool.{h,cpp}both subsystems call. A refactor, not a patch. Owner:kernel/ipc/, both pipe syscall TUs. - TCP ECN reactions not gated on segment acceptability (Low).
EcnOnEce(cwnd/2) andpeer_ce_pending(ECE-echo obligation) fire intcp_segment.cppbefore the in-window/ACK acceptability check, so a single spoofed ECE/CE segment in the 4-tuple can repeatedly collapse throughput. Move the ECN block below the §3.9 acceptability gate. Throughput-DoS only (no memory safety); touches input-processing order so it wants its own careful slice. Owner:kernel/net/tcp_segment.cpp.
- Still gated: HLSL bytecode execution (the
d3dcompiler.dllfrontend emits a DXBC-shaped blob the draw path ignores; a DXBC->SPIR-V transpiler would feed the now-live in-kernel SPIR-V interpreter — see Vulkan ICD), texture sampling, geometry/hull/domain/compute shaders, multi-stream input, Z-buffer, D3D9 fixed-function lighting, real GPU command-ring submission. - Blocks on: per-vendor GPU drivers landing real
command-ring submission; DXBC→SPIR-V transpile for app HLSL.
(The D3D11→Vulkan thunk v0 landed 2026-06-10:
HARDWARE/UNKNOWN/REFERENCE swap chains use a kernel
VkImageback buffer; Clear/Draw record real VkOps viaSYS_VK_CALLand replay through the kernel ICD's rasterizer into the image backing —dx_raster.hno longer runs on that path; boot gate[vk-selftest] PASS (image-backed clear+draw). Remaining on the thunk: bind a SPIR-V passthrough pipeline so draws run the in-kernel interpreter (needs the paint-target refactor ingraphics_vk_shaderraster.cpp); D3D12 reusesdx_vk.has a follow-on. D3D9/11/12 COM vtables + shared software rasterizer- DXGI swap-chain present into compositor windows landed; the software back end remains for WARP/SOFTWARE + fallback.)
- Residual: common controls, multi-threaded message queues.
Menu GAPs:
TPM_LEFTBUTTON/TPM_RIGHTBUTTONactivation filtering, menubars +LoadMenuresource loading. SeeCompositor§"Popup Menus" for live state. (Message pump, GDI paint, popup menus +WM_CONTEXTMENU+TPM_*flags, modal dialog primitive, native scroll bars with drag-the-thumb + click-on-track, interactive Move/Size viamodal_input.{h,cpp}, Files-app rename UI, Trash + ramfs Files per-row context menus landed.)
- Lands: 4 ECN counters per direction for L4S / DOCSIS prioritisation, on top of the now-complete classic ECN plumbing.
- State: classic RFC 3168 ECN is done — SYN-time negotiation
plus the data plane (ECT(0) marking on outbound data, inbound
CE → ECE echo, inbound ECE → cwnd halve + CWR) landed
2026-06-12, alongside the RFC 6675 sender-side SACK scoreboard
(
kernel/net/tcp_sack.{h,cpp}). - Owner:
kernel/net/stack.cpp,kernel/net/tcp_segment.cpp.
- Deferred indefinitely. CUBIC (RFC 9438) is the default CA
(integer-only
kernel/net/tcp_cubic.cpp,max(cubic, reno)floor,Tcb.cubic.enabledkill switch). BBR needs a pacer + delivery-rate estimator + 4-state machine (~2000 LoC) on top — no workload justifies it yet.
- See Open Firmware Landscape 2026
for the full decision matrix. Concrete next slices:
- Wire ath9k_htc HTC service negotiation against
qca/open-ath9k-htc-firmwarebuilds — first physical- hardware Wi-Fi target with zero closed firmware. .duetfwpackage signing root — Ed25519 offline HSM project root + yearly intermediate; signer-key-ID format as SHA-256 truncated to 16 B (Sigstore convention).- Quarterly firmware-landscape refresh — rotate
Open-Firmware-Landscape-2026.mdevery quarter; key items to recheck: Nexmon supported chips, openwifi releases, any Realtek open-firmware emergence (currently zero).
- Wire ath9k_htc HTC service negotiation against
Running managed (.NET) executables is explicitly deferred, not forgotten. The project owner marked it "todo later" on 2026-07-29 while prioritising the native download-and-run path.
Why it is a different class of work from the rest of the Win32 backlog: a managed PE's entry point is a stub that hands control to a runtime. Supporting it is not a DLL surface to fill in but a runtime to host — metadata and IL parsing, a JIT or interpreter, a garbage collector, and the CLR's own threading and exception models layered on top of the SEH work already landed. Every other open Win32 item (COM, SxS manifests, console, fibers, D3D9, DirectWrite) is bounded by "implement these functions"; this one is bounded by "implement another language runtime", and is a multi-session project of its own.
Nothing else in the backlog depends on it, so deferring costs no ordering. A managed binary today should fail with a clear diagnostic naming the CLR header rather than a confusing loader error -- worth checking that it does.
The Aurora redesign is implemented across the Duet* theme family
(duet, duetlight, duetblue, duetviolet, duetgreen,
duetclassic) but is not the boot default. kernel/core/boot_bringup.cpp
selects a theme from the kernel cmdline (theme=classic|slate10|amber|duet)
and otherwise leaves the Classic teal palette the first GUI slice shipped.
Aurora is reachable at runtime via Ctrl+Alt+Y or Settings.
Consequence, and why this is filed rather than fixed: a default-configuration
screenshot shows Classic, so any "does the implementation match the
design?" comparison run without theme=duet is not merely inconclusive, it
is misleading — it compares the new design against a different theme
entirely. At least one such comparison was made during the 2026-07-28/29
session before this was noticed.
Open question for the project owner: the redesign was commissioned as
the look, which argues for making duet the default. Against: the change
touches every default-configuration screenshot in the wiki, and any test
that asserts on chrome geometry or colour would need re-baselining. Until
this is decided, visual-fidelity work must be verified with
DUETOS_EXTRA_CMDLINE="theme=duet".
The four fidelity deltas from the first valid theme=duet comparison have
landed -- desktop icon set, taskbar search pill + pinned app row, the
clock/date gadget, and the wallpaper glow geometry. See
wiki/subsystems/Compositor.md for what shipped.
Closed on 2026-07-30: the scheduler owns a 1 Hz sample ring shared by
the desktop gadget column and the taskbar stats pill. The gadget column
now paints the kernel stats and ABI-peers panels beneath the clock, the
kernel panel includes the FPS figure, and the taskbar pill includes the
README §10 CPU sparkline. The compositor reads the ring without taking
g_sched_lock.
What is still open:
- START paints a solid accent tile, not the design's sheer mark. The
reference's START is a dark cell carrying only the teal/amber arcs; the
implementation fills the whole 44-px cell with
taskbar_accent, which makes it the loudest object on the island. Surfaced while landing the search pill next to it; not fixed because it is a separate cell with its own hover/open states. - The pinned row is five buttons, not the design's nine. Deliberate -- see Design-Decisions "Pinned taskbar launchers are scaled by island budget, not transcribed from the 1920 canvas".
The standing target for the "run real Windows apps" pillar is a
representative 32-bit PE32 (pei-i386) GUI game, ~4.8 MB on disk /
9.4 MB image, Windows-GUI subsystem, importing KERNEL32, USER32, GDI32,
ADVAPI32, COMCTL32, SHELL32, IMM32, WININET, WSOCK32, WINMM, OPENGL32
(this build renders via OpenGL, not D3D), plus bundled third-party
DLLs (fmod.dll audio, an image codec, a video codec). Its data lives
in large bundled archive files (multi-GB).
Landed (2026-06-17): large-exe support (dynamic peexec read buffer +
heap-backed loader unwind guard — see Design-Decisions); the exe reads,
maps all sections, resolves imports, spawns, and executes in 32-bit
ring 3 (mode=pe32). The [win32-32miss] instrumentation
(SYS_EXIT handler logs the caller's return address on the
unresolved-import sentinel 0xDEAD0042) turns each fatal import into a
named RVA. The static-MSVC-CRT startup cluster is now covered in
kernel32_32 (OutputDebugStringA, GetVersionExA, Tls*, locale/codepage,
HeapCreate/Destroy, GetEnvironmentVariableA, GetModuleFileNameA,
VirtualQuery, FlushInstructionCache) and a GetModuleHandleA
wrong-syscall bug (was calling SYS_WIN_SET_CURSOR) was fixed. The exe
now clears CRT startup entirely and runs into its own application
code without faulting. Milestone: a 32-bit game exe runs on DuetOS.
The remaining rungs, in order (each the proven attempt→trace-exact-
fault→fix→re-run loop, tools/test/run-exe.sh + peexec=, using the
[win32-32miss] ret= line → i686-w64-mingw32-objdump -d --start-address to name the import):
-
Post-CRT application init. The exe clears CRT startup and reaches its own code; the climb from there is import coverage plus the surfaces those imports need to be real. This is the long middle.
Landed (2026-07-28): the USER32 rung. The quiet loop was
user32_32lying — every export inuserland/libs/user32_32/anduserland/libs/gdi32_32/returned a constant and neither file issued a single syscall. Both are real surfaces now, on the same ~40SYS_WIN_*/SYS_GDI_*handlers (58..100, 65-68/74-76) the 64-bit siblings use: class registration storing a live WNDPROC, window create/destroy/show/move, the full pump (Get / Peek / Post / Dispatch / Send / PostQuitMessage), per-window long slots, invalidate + BeginPaint/EndPaint, rects and metrics from the compositor, focus/activation, key state, cursor, capture, timers, clipboard, and the fill / rect / ellipse / line / text / pixel primitives.user32_32split intouser32_32.c+user32_32_misc.c; the shared i386 syscall trampolines and the HDC / brush / pen encodings moved touserland/libs/common/. See Win32-Surface-Status §11b for the full inventory and the three i386-specific traps (28-byte MSG, divergent WNDCLASSEX offsets, USER32-homed FillRect).Proved live, not by compile:
userland/apps/pe32_window/registers a class, creates a window, and drives post → peek → dispatch → WndProc → paint → quit, asserting 22 conditions including a canary immediately after its 28-byte MSG. It is anAlwaysrow in the PE-compat battery (ring3-pe32-window), so every ring3 boot exercises it.Still open on this rung:
msvcrt_32stdio (fopen/fread) has not been rebased onto the new file-I/O surface. The remaininggdi32_32bitmap / blit / DIB STUBs are now unblocked but not yet written: the 64-bitgdi32gained real off-screen surfaces on 2026-07-29 and the syscalls it uses (SYS_GDI_SET_DIBITS/_GET_DIBITS) are register-argument only, so the i386 port can reach them throughduet_syscall6with no struct shape to mirror.Icon and cursor resourcesLANDED 2026-07-29:SYS_GDI_CREATE_CURSOR_RGBA(216) takes RGBA image bits + hotspot;duet_res_pick_icon/duet_res_decode_iconinpe_resources.hwalkRT_GROUP_ICON->RT_ICON, decode BIH + bottom-up DIB + AND-mask -> BGRA;LoadIconA/W,LoadCursorA/W,LoadImageA/Ware REAL on both bitnesses;icon_smokePE fixture in the ring3 battery. -
Large bundled-data staging + FAT large volume. The exe reads multi-GB archive files. Staging needs a much larger disk image than the 16 MiB
make-gpt-image.pydefault, exercising the FAT32 large-volume + write paths. NOTE: the run-exe.sh staging-image boot currently trips the screenshot / trash / fix-journal-persist disk-write self-tests and a wild-LBA FAT read (the known transient emulated-block flake) — triage those here, where the FAT image work is. -
OpenGL.
OPENGL32is a NO-OP stub; the renderer needs a real GL implementation (GL 1.x fixed-function → the in-kernel Vulkan ICD, or a software GL). The big rendering-subsystem expansion. -
WSOCK32 / WININET (realm/login networking) and FMOD audio — later rungs once it renders.
spawn.cpp's preload set is the authoritative list of ~44 DLLs the
kernel embeds, but the runtime SYS_DLL_LOAD_FROM_PATH path resolves
against ramfs /lib/, which kernel/fs/ramfs.cpp populates with a
hand-written constinit node per DLL - customdll, customdll2 and
(2026-07-28) vulkan-1. Every other embedded DLL is unreachable by name
at runtime, even though its bytes are already linked into the image and
the node costs nothing (the node borrows the blob pointer).
This bit for real: stock vulkaninfo.exe LoadLibrarys vulkan-1.dll
rather than importing it, missed, and died with an unhandled C++
exception on a kernel whose Vulkan ICD was online and self-tested. It
was only reachable at all because vulkan-1 is marked non-essential and
the arch::IsEmulator() preload trim skips it - so the preload fast path
did not paper over the gap.
Adding one node per observed miss is the whitelist-incompleteness bug
class: the next app to LoadLibrary a shipped DLL by name hits the same
wall. The fix is to derive the /lib node set from the SAME list
spawn.cpp uses, so a DLL cannot be preloadable-but-not-loadable. Doing
that needs the preload table hoisted out of its enclosing function to
file scope (it is a function-local static const today) and exposed
through proc/spawn.h; that refactor is the whole item.
Amendment (2026-07-29, side-by-side DLL slice). Two corrections to
the diagnosis above, from reading the live path rather than the /lib
node list:
- The exposure is narrower than "every other embedded DLL is
unreachable".
SYS_DLL_LOAD_FROM_PATHconsultsProcessFindDllBaseByNameFIRST, andSpawnPeFileregisters every preloaded DLL intoproc->dll_images[]— so for an import-bearing PE a runtimeLoadLibrary("user32.dll")already hits the process image table and never reaches/lib. The real hole is exactly the case thevulkaninfostory describes: a DLL that thearch::IsEmulator()preload trim skipped (essential = false) was never registered, so there was nothing in the table to find. Deriving/libfrom the preload list still fixes it; so would not trimming. - There is now a third answer for the same shape: a DLL shipped on the
volume beside the
.exeis reachable at runtime via the same syscall, becauseSYS_DLL_LOAD_FROM_PATHfalls through to the side-by-side resolver usingProcess::sxs_dir. That does not close this item — it covers app-supplied DLLs, not kernel-shipped ones — but it removes the "the app must ask us to embed it" pressure that made this item urgent.
This item replaces an earlier one that named the api-set surface as the blocker. That diagnosis was wrong, and the thing that made it wrong is now fixed — see the correction below before acting on it.
BattleBit.exe exits with status 0 and no fault. Grading a run of
it by "did anything crash" scores this as a success. It is not one.
What actually happens, read off a live boot
(tools/test/run-exe.sh, DUETOS_IMAGE_MB=48,
DUETOS_STAGE_EXTRA=UNITYPLA.DLL=<path>):
[guard] WARN kind=pe name="/UNITYPLA.DLL" findings=0x1
[guard] - PE_SUSPICIOUS: 2+ injection-family APIs
[guard] Allow [y] / Deny [n] — 10s default-deny. >
[guard] prompt timeout: default-deny
[sxs] security guard blocked path="/UNITYPLA.DLL"
[pe-resolve] unknown import -> catch-all NO-OP fn="UnityMain"
[win32-miss] fn="UnityMain" slot=0x140edb210 called-from=0x140ed11f2 in-module=0x140ed0000
[dll-load] api-set contract has no host (returning NULL, as Windows does) name="api-ms-win-appmodel-runtime-l1-1-2"
[sys] exit rc val=0x0
The chain is: the image guard flags a 26 MiB game engine for importing
two or more injection-family APIs (which every game engine does), opens
an interactive allow/deny prompt, and in an unattended boot nobody
answers, so it default-denies. UnityPlayer.dll therefore never loads,
UnityMain binds to the catch-all no-op, BattleBit.exe's main
calls it, gets 0 back, and returns 0. exit(0) is the honest
consequence of a main that returned 0.
The api-set line is a red herring. It happens after the process
has already decided to exit: the MSVC UCRT's exit path probes
api-ms-win-appmodel-runtime-l1-1-2 for
AppPolicyGetProcessTerminationMethod, and takes the NULL branch to
plain ExitProcess. Returning NULL there is the correct Windows answer
for a non-packaged app — it is what any Windows build predating the
contract does. Mapping that contract onto a host DLL that does not
export its functions would convert a correct refusal into a confusing
"function missing from a DLL that claims to exist". Do not do it. The
contract now says so in the log rather than reporting
miss path=/lib/..., which is what made it look like a missing file.
So the open work is the guard, not the loader:
- An unattended PE run cannot load any WARN-verdict side-by-side DLL, because the prompt always times out to deny. That is correct as a default and must stay the default.
- What is missing is a deliberate operator opt-in — a boot-time pre-authorisation for a named image, so an automated run can consent in advance instead of relying on a human at a serial console. Flipping the timeout default to allow is not an acceptable substitute; it would silently weaken the gate for every image.
Until that exists, a BattleBit run measures the guard's default, not
the loader's. Whoever picks this up should confirm with
grep -n "sxs. security guard blocked" <log> before drawing any
conclusion about import coverage.
Demand growth covers the PE main thread only. SYS_THREAD_CREATE
carves Process::kV0ThreadStackPages off the thread-stack arena at
0x68000000 with no guard region between slots, so a worker thread that
recurses overflows into its neighbour's stack silently. The machinery
to fix it already exists (core::UserStackPlan / UserStackClassify
in kernel/proc/user_stack.h); what it needs is a per-thread
UserStackRange instead of the single per-process one, and an arena
that leaves guard gaps between slots.
A read-only audit of the PE loader and Win32 handle lifecycle found one
root shape with several symptoms: the handle bands are declared
across kernel/proc/process.h and the per-type pool modules, but the
two functions that must enumerate them all — DoFileClose
(kernel/subsystems/win32/file_syscall.cpp) and ProcessRelease
(kernel/proc/process.cpp) — each carry a hand-maintained list that has
drifted behind the set. Verified against the code, not inferred.
Ranked, with the security one first:
- (FIXED 2026-07-28) Section handles are now released at process
teardown -
ProcessReleasewalks the section pool (process.cpp~758 / ~877). The sandbox-reachable pool-exhaustion path this described is closed. - (FIXED 2026-07-29)
DoFileClosenow has akJobHandleBasearm that routes job-handle close toSysJobClose. Constants exported fromjob_syscall.h. - Fixed 2026-07-27 — local thread handles (0x400 band) now have
stable identity and lifecycle.
DoFileClosehas the missing local-thread arm, and slot claim, identity/exit publication, wait polling, and close/reuse are serialized by the per-processwin32_thread_lockinstead of local-CPUcli. Local and foreign thread-handle rows record the scheduler's immutable, non-reused TID rather than retaining a raw, non-owningTask*; operations resolve that ID through the scheduler's locked task-lifetime boundary. Reaping can therefore never leave a dereferenceable stale task pointer in either public handle table. This also fixes the optimized SMP failure whereSYS_THREAD_WAITretainedSTILL_ACTIVEafter a peer CPU had exited and been reaped. User tasks use a prepared pre-publication initializer: their TID and per-thread GS/TLS metadata are complete before the scheduler can run them, and closing the returned handle before the first timeslice no longer suppresses entry. An internal row generation prevents creator-cleanup ABA. A separate completion bit also makes an actual thread exit code ofSTILL_ACTIVE(259) waitable. The mixed-provider PE fixture covers close-before-first-run, runs twelve create/wait/exit-code/close cycles against the eight-slot table, and checks the 259-valued exit case. Still queued:TerminateThreadcancellation is not yet a full asynchronous unwind contract. A target blocked on a wait queue remains kill-requested until its normal producer wakes it, and an in-flight asynchronous syscall has no general cancellation hook that unwinds its subsystem-owned reservations. Add cancellable wait/async-syscall cleanup before treating forced termination as immediate or resource-complete. - (FIXED 2026-07-29)
ProcessReleasenow callsProcessDropOwnedProcessHandles(p)early in the teardown sequence, releasing any retained process handles before the AS goes away. Idempotent with the sched-reaper call. - Mutex ownership is not force-released when the owning task dies.
Only the explicit-
CloseHandle-while-holding path drops it, so a worker killed while holding a mutex leavesm->ownera deadTask*and any sibling thread blocks forever, with noWAIT_ABANDONED. Larger refactor — needs per-task held-lock bookkeeping or a task-death hook; the handle table stores type-erasedKObject*. - (FIXED 2026-07-29)
SpawnPeFileWin32HeapInit error path now callsProcessRelease(proc)instead ofAddressSpaceRelease(as), matching the ownership transfer atProcessCreate.
(DONE 2026-07-29) tools/test/check-handle-bands.py is the
property-test companion to the hand-fixes above: it greps every
declared kWin32*Base / kJobHandleBase constant and verifies each
appears in both DoFileClose and ProcessRelease (or is in a
documented exempt list). Currently 12/12 pass. Add future bands to
this check.
Not audited, out of that audit's fence and still open: whether
mm::CopyFromUser/CopyToUser validate a 32-bit process's range with
no 64-bit canonical-address assumption baked in.
The focused thread3_smoke regression now opens a live worker by TID
through SYS_THREAD_OPEN, exercises suspend/context/resume through the
foreign-handle row, then proves exit/reaping rejects the retained stale
handle and that closing it permits a clean numeric-slot reuse.
A local KVM-backed x86_64-debug validation run also exposed an older
socket IRQ-state livelock before any PE fixture launched: SocketAlloc
executed a raw sti inside the int 0x80 trap, so the timer repeatedly
entered at nesting depth two and the debug defer log could starve the
instruction after sti.
Landed 2026-07-27: kernel/net/socket.cpp is off raw Cli/Sti
entirely — one file-local sync::SpinLock guards the pool and the
stats, and SpinLockRelease restores the caller's saved RFLAGS instead
of unconditionally re-enabling. That is the IRQ-save lock contract, and
it also closes the cross-CPU use-after-free on a released socket's UDP
RX ring.
Landed 2026-07-31: socket operations now take a transient lifetime pin
before sleeping or entering TCP/pipe code. Last-handle and owner teardown
mark the entry closing and defer resource release until pins drain; stream,
datagram, and poll paths snapshot mutable endpoint state under the pool lock.
Raw SocketGet access has been removed from syscall handlers. Runtime build,
boot, and concurrent socket validation remain outstanding for this slice.
Still open: make interrupt nesting distinguish hardware IRQ frames
from syscall/exception frames and rate-limit the defer diagnostic. Do
not weaken the nested-IRQ scheduling guard. Separately, the TCB table
(kernel/net/tcp*.cpp, 18 remaining arch::Cli sites), the ARP
cache, and the DHCP lease are still on the UP-only scheme; the socket
pool is the worked example to copy.
vulkan_1.c— 20 call sites pass their real argument in the wrong register. All use the correct multiplexedSYS_VK_CALL(211), but the payload lands inrsiwhereDoVkCall(kernel/syscall/syscall_vk.cpp) readsrdx. Worst cases:vkAllocateMemoryandvkCreateBufferalways request size 0;vkCmdClearColorImagealways clears to 0;vkCreateComputePipelinesloses the shader handle.userland/libs/dx_vk.halready carries a developer warning that these are "latently misaligned". Two stalersi = …comments onkVkOpGetStatsCounter/kVkOpClearFramebufferRgbainsyscall.hcontradict the kernel implementation and are what misled the call sites. Left alone deliberately: adjacent to the concurrent graphics work.- Fixed 2026-07-26:
iphlpapi::GetAdaptersInfolease query.kSockOpGetLeasenow passesoutinrsiand capacity inrdx; the trampoline's third argument uses a named asm operand rather than the formerly wrong%4. Theiphlpapi_socket_abihosted CTest pins the userland call, trampoline mapping, and kernel consumption. - These are the right number, wrong register shape, which
check-syscall-numbers.pyexplicitly does not cover — it validates numbers, not argument slots. An argument-contract checker would need to parse the per-syscall register documentation out ofsyscall.h.
23 of 28 plan tasks landed (blend math, 9-slice soft shadow, 7 Theme
fields, per-theme intensity matrix). QEMU verification passed 2026-05-24;
boot-log-analyze.sh reports the TACTILITY line every boot.
Residual: VBox boot verification only. LAPIC / GS-base differences
from QEMU have caught real bugs before (see the vbox-bringup-pr266
notes). Everything else in this pass is landed and documented in
Compositor.
Landed and verified; boot-log-analyze.sh reports the PASS B line
(splash / wallpaper-motion / login-gui / umbrella) every boot.
Residual: none known. Retained only as a pointer - the subsystem
detail lives in Compositor.
All 21 planned tasks landed, plus 5 settings sub-panel migrations.
chrome-text-selftest PASSes every boot.
Residual: none known. Proportional metrics for list CONTENT landed
2026-07-29 (app_widgets/app_text.{h,cpp} plus the Files / Task Manager
column-model refactor); [app-text-selftest] PASSes every boot.
The widget library landed (app_button, app_label, app_panel,
app_divider, app_list_row, app_toolbar, app_input,
app_scrollbar) plus 28 per-app migrations. [app-widgets-selftest] and
[pass-d-selftest] PASS (widgets=ok, apps=28/28) fire every boot.
Aurora (2026-07-28) added app_palette on top, so interiors now theme
from one owner.
Residuals: the Task Manager Performance resource rail is wired and
boot-clean but has never been photographed - the demo-windows profile
opens Task Manager on the Processes tab and the headless QMP harness
could not drive a tab switch, so its per-core tiles are compile- and
boot-verified only. ABI pills, per-row dots and the Files quick-access
rail landed 2026-07-29. Files shows an ABI badge only where the image
bytes are already resident (ramfs); FAT32 rows deliberately show none
rather than inherit the launch path's extension guess. See
AppWidgets.
- v1 — Argon2id with lazy migration. Blake2b primitive
(RFC 7693) is in tree and passes the Appendix-A vectors;
Argon2id (RFC 9106) sits on top. Blocked on a record-format
extension — the 56-byte
PasswordHashRecordcan't carry Argon2id's memory/time/parallelism params; needs a V2 shape sized for both old PBKDF2 + new Argon2id rows. SeeRBAC-and-Elevation. - v1 — Persistence.
/system/secrets/holds the account + role tables encrypted at rest; Argon2id-derived key wraps the table; TPM seals the wrap key when that driver lands. Until thenAuthInit/RbacInitre-seed defaults every boot and runtime additions are lost. - v1 — First-boot installer flow. Replace the hardcoded
admin / adminseed with a userland install wizard launched by init when/system/secrets/is empty. Blocks on the persistence work above. - v1 — Secure Attention Key. Reserve Ctrl+Alt+Del at the
PS/2 driver level → kernel-drawn full-screen broker prompt, so
a paranoid user can force a known-good prompt. The v0 modal is
drawn under the compositor lock but doesn't pre-empt a focused
full-screen surface. (v0 broker + role table + grace cache +
CLI/GUI prompt +
NtAdjustPrivilegesTokenfacade routing landed.)
- Residual:
Ejectcapability gating; a hot-unplug driver path (AHCI / xHCI don't support it yet); virtio per-class queue-setup + I/O (rng/blk/net probes are attach-only in v0 — see VirtIO per-class polish below). (PCI + USB + VirtIO read-only device tables landed.)
- Residual: a real wireless backend (per the Wireless row) so the SSID list reflects an actual RF scan rather than the empty placeholder; multi-iface DHCP lease tracking (single lease today). (Iface table, rx/tx counters, firewall-drop column, routing/DNS section, Wi-Fi-scan section UI landed.)
- Today:
Ctrl+Alt+Topens the kernel shell (ring-0). - Blocks on: console-multiplex refactor — the kernel shell
is wired to a single global
ConsoleWrite; a windowed terminal needs the shell to take a per-session sink. - Owner:
userland/shell/+ a PTY layer.
- Today: BMP works (
kernel/apps/imageview.cpp). - Blocks on: PNG needs a zlib port (none in tree); JPEG needs a Huffman+IDCT decoder; PDF is huge; video needs HDA.
- PS/2 + xHCI HID drivers hardcode US layout. Blocks on an input-method framework refactor.
- UI strings are C++ literals in
kernel/apps/*.cpp. Blocks on a string-table layer with id → text indirection; refactor across all apps.
- Residual: boot an installed disk on real UEFI hardware.
The orchestration layer (
install <handle> INSTALL [--duetfs]→ GPT with ESP / system / crash-dump partitions, FAT32 or DuetFS system partition, GRUB stub, realBOOTX64.EFIstamped to the spec-mandated removable path, opt-in kernel-ELF embed viaDUETOS_INSTALLER_KERNEL_EMBED) is all in tree and the layout math runs a boot self-test every boot.
- Blocks on: code-signing infrastructure. (A/B kernel-slot layout — state machine, installer staging to the inactive slot, and the generated dual-menuentry GRUB cfg — landed 2026-06-12.)
- Residual: screen reader (blocks on an AT-SPI-equivalent kernel surface); on-screen keyboard (blocks on a widget-slot bump). (Magnifier landed.)
The Rust bring-up checklist is closed out — thirteen
production crates are live with C++ callers. Future Rust work
happens only through the two channels documented in
Rust-Subsystems: existing
crates growing to cover their successor surface, or a new
crate landing with its first real C++ caller. Not triggers:
"memory safety is cool" / "a library exists in Rust". The
crate-authoring rules also live in that page.
DuetFS v3 ships per-block CRCs, sym/hard links, fsck, on-disk
auto-mount, userland syscall surface, auto-symlink resolution,
and mkfs.duetfs. Image cap is 4 MiB (single-block CRC table).
Pending, in rough priority:
- Multi-block CRC table — restore the 32/128 MiB image cap.
- CoW — copy-on-write file-data writes on top of the existing
journal (journal already lands per
journal.rs). - Separate dirent table — decouple hard-link names from the
inode's
name(today's v3 caveat). - Indirect extents — files needing > 8 extents.
- Multi-block dirs + B-tree directory index — bump the 1024-child cap.
(AES-XTS + Argon2 KDF encryption tier in crypto.rs, LZ4
compression in compress.rs, and snapshots in snapshot.rs
all landed.)
The "Full Project TODO" import (2026-05-09) is closed except the
rows below; everything else landed and is recorded in
Design-Decisions /
Win32-Surface-Status. Syscall numbers
are ABI — do not reuse retired numbers.
| ID | Scope | Pri | Task | Acceptance |
|---|---|---|---|---|
| T4-03 | gfx | P2 | Intel iGPU Gen9+/Xe driver basics: GTT setup, command ring, 2D blitter acceleration (PCI probe + register peek + software fallback landed). | BitBlt-heavy paths use the Intel blitter instead of software fills. |
| T5-03 | mm | P2 | Real KASLR in the UEFI loader (memory-map scan, random 2 MiB-aligned base in a 64 MiB window, boot-info handoff, boot-log report). Same work as Linux-CVE Class II follow-up. | Two cold boots show different kernel .text load addresses. |
| T6-05 | win32 | complete — 2026-07-26 | MSVC x64 C++ EH now captures the caller's complete aligned CONTEXT, validates bounded PE unwind metadata, restores GPR/XMM state, binds by-value and reference catches from the correct HandlerType metadata, and defers catch execution to TARGET_UNWIND so crossed-frame destructors run first. The pe-runtime QEMU profile passes scalar catch, class-reference catch, cross-frame destructor unwind, catch-all, TLS, SEH, and synchronization with 51/51 boot self-tests. Remaining extensions are non-trivial copy-ctor catch objects, FH4 compressed FuncInfo, ESTypeList, and rethrow (throw; — attempted 2026-07-28 and backed out: supplying the in-flight object is easy, but the re-raise's frame walk returns to the same function with ControlPc still inside the inner try and re-selects the catch it just left; needs the funclet-to-tryblock link threaded through the dispatch). |
cxxeh_pe prints all four subtest PASS markers, [cxxeh] RESULT PASS, and exits 0. |
| T7-04 | fs | P2 | Scoped NTFS write: create, write, truncate, delete, rename with MFT/index/journal/bitmap updates; no compression/encryption/ADS for v0. | PEs can perform basic writes to NTFS volumes. |
| T8-01-followon | sched | P3 | Bands landed 2026-06-10 (4 MLFQ bands from win32_priority_class, band-0-first pop, wake-preempt, escape valve — acceptance met: high-prio preempts low within one tick). Residual: behavioural aging/decay (demote a quantum-hog, promote a starved task) — v0 fixes band by priority class, not behaviour; the escape valve is the only anti-starvation mechanism. Lower priority now (the user-visible preemption works); revisit if a workload shows fixed bands mis-schedule. |
A CPU-bound band-2 task that never yields is demoted below a freshly-ready band-2 interactive task. |
| T10-04 | build | P2 | Extend hosted ctest to mirror the PE-parser contract (Result / string / syscall_error / cvt / text_hash / d3dcompiler / damage_rect / wild_address / disk_path / vfs_resolve / registry_path already wired). PE parser is kernel-only — use the algorithmic-contract pattern (re-state the routine inline, assert canonical cases) as primitives grow self-contained. |
Host ctest covers Result + PE parser + VFS + registry + string helpers without QEMU. |
The kernel-side primitive is in tree for each; what's missing is the per-call wiring.
- Lands: virtio-console multiport (
VIRTIO_CONSOLE_F_MULTIPORT+ control-queue protocol); virtio-balloon inflate/deflate policy (the "when do we agree to give up memory?" half — spec dispatch is straightforward); virtio-input statusq for LED / force-feedback delivery (eventq + EV_REL + EV_ABS already landed — virtio-tablet absolute coordinates are converted toMousePacketdeltas at the driver boundary so the unified one-source-of-truth pointer API stays intact); IRQ wire-up across rng/net/console/balloon/input — virtio-blk landed MSI-X IRQ completion + 10 in-flight request slots (2026-06-10); the transport helper (VirtioQueueMsixVectorSet) and the BME enable are shared, so per-class wire-up is now the thin part. (Every per-class probe v0 + RX/TX poll tasks landed.)
CI wiring landed.
.github/workflows/build.ymlnow has afuzzjob (sibling ofcheck-rust/build-debug) that runsFUZZ_SECONDS=90 tools/test/fuzz-all.shon every push/PR, uploadingcrash-*artifacts on failure. The optional cron long-run (FUZZ_SECONDS=900+ persisted corpus cache) remains a future follow-up, not a blocker.
Untrusted-input byte parsers still without a harness, in
rough bug-probability order (hand-written C++ bit/TLV parsers
first — that is where every memory-safety bug found so far
lived; the Rust-backed parsers held up). All follow the
established tests/fuzz/ pattern (host harness + host_shim/
stubs + a seeds/gen_*_seeds.py); the codec/cert ones are pure
bytes → struct and need less shimming than the FS probes.
- Bluetooth HCI/HID —
kernel/net/bluetooth/hci.h,hid.h. Untrusted radio peer.
Blocks on: nothing — independent slices, one parser each, same recipe. Pick the top unstruck bullet, land harness + (any) fix, strike the bullet in the same commit.
When a roadmap item lands:
- Delete its entry from this page in the same commit.
- Add a
Design-Decisionsentry (one per non-trivial commit). - Update
Historyif the landing changes a project-level milestone. - Update the owning subsystem wiki page's "Known Limits".
If an item is wrong-sized for a single commit, write a slice plan into the relevant subsystem page and keep a one-line index pointer here — not a landed-work paragraph.
The standing work queue, in priority order. Items are ordered by how much real software or hardware each unlocks per unit of work, not by how interesting they are. Detailed sections for several already exist elsewhere in this file; this is the running order, not a replacement for them.
Convention: [dep: N] means item N should land first. PROOF names the
artefact that decides whether it actually works — without one an item is not
done, it is merely written.
SEH + C++ exception handling.LANDED 2026-07-26 (T6-05). Acceptance met:pe-runtimeQEMU profile passes scalar catch, class-reference catch, cross-frame destructor unwind, catch-all, TLS, SEH, and synchronization with 51/51 boot self-tests. Remaining extensions (copy-ctor catch objects, FH4, ESTypeList, rethrow) are incremental — the core contract is delivered.LANDED 2026-07-28. The walker,.rsrc(PE resource) parser.FindResource*/LoadResource/LockResource/SizeofResource/FreeResource/EnumResource*and a realLoadStringWship on both bitnesses;user32!LoadStringWwent from 282 32-bit SysWOW64 binaries wanting it (82 counting.exeonly) to 0. Seewiki/subsystems/PE-Resources.md. Two consumers were deliberately NOT built, because each needs a sink that does not exist — building the decoder first would be dead code:Icons / cursors / bitmapsLANDED 2026-07-29.LoadIcon,LoadCursor,LoadImageare REAL on both bitnesses.SYS_GDI_CREATE_CURSOR_RGBA(224) takes RGBA pixels + hotspot.duet_res_decode_iconwalks RT_GROUP_ICON -> RT_ICON, decodes BIH + bottom-up DIB (32/24/8/4/1bpp) + AND-mask -> BGRA.icon_smokePE fixture in the ring3 battery.AcceleratorsLANDED 2026-07-29.LoadAcceleratorsA/WparseRT_ACCELERATORfrom the PE's.rsrcsection.TranslateAcceleratorA/Wmatch VK + modifier state and postWM_COMMAND.KeyCode-> Win32 VK translation table inkernel/subsystems/win32/keycode_vk.h.accel_testPE fixture in the ring3 battery.
Side-by-side DLL loading.LANDED 2026-07-29.Process::sxs_dirtracks the exe directory;SYS_DLL_LOAD_FROM_PATHfalls through to a side-by-side resolver; recursive import resolution + security guard scanning of disk-loaded DLLs shipped. BattleBit.exe + UnityPlayer.dll (26 MiB, 67 imports) loads and runs. SeePE-Loader.md.Dialog manager.LANDED 2026-07-29. Template-drivenDialogBoxIndirectParamA/W,CreateDialogIndirectParamA/W, modal loop withEndDialog,IsDialogMessage,GetDlgItem,SetDlgItemTextA/W,GetDlgItemTextA/W,SendDlgItemMessageA/W,CheckDlgButton,IsDlgButtonChecked.dialog_smokePE fixture in the ring3 battery.Real COM.LANDED 2026-07-29. Registry-backed CLSID→DLL resolution,CoInitializeEx,CoCreateInstance,CoRegisterClassObject,IUnknown/QueryInterface, in-proc servers, apartment model. SeeWin32-Surface-Status.md.Delay-load imports.LANDED 2026-07-29. The premise was wrong twice over:__delayLoadHelper2is linked INTO the image bydelayimp.lib, not provided by the OS, and routing through it would have gone throughGetProcAddress, which deliberately reports a miss for no-op thunks. The loader binds directory 13 eagerly through the static-import ladder instead — seePE-Loader.md.PE TLS callbacks.ALREADY LANDED (T6-01, predates this backlog).SetupStaticTlscopies the.tlstemplate, wiresTEB.ThreadLocalStoragePointerand_tls_index, and runsAddressOfCallBacksin ring 3 via a generated trampoline on both process and thread attach. Remaining gap is DETACH — seePE-Loader.md.SxS / assembly manifests.LANDED 2026-07-30. RT_MANIFEST resource parser (ParseManifestFromPe), execution level / DPI awareness / SxS dependency extraction. Wired intoProcess::manifestat spawn.Console completeness.LANDED 2026-07-29. RealGetStdHandle,WriteConsoleA/W,ReadConsoleA/W,GetConsoleMode/SetConsoleMode,GetConsoleScreenBufferInfo,SetConsoleCursorPosition,FillConsoleOutputCharacter,SetConsoleTextAttribute.Fibers.LANDED 2026-07-29. RealConvertThreadToFiber,CreateFiber,SwitchToFiber,DeleteFiber,GetFiberData,GetCurrentFiber, FLS (FlsAlloc/FlsFree/FlsGetValue/FlsSetValue) with per-fiber storage + generation counters.fiber_smokePE fixture in the ring3 battery.- .NET spike. [dep: 3, 5 — both cleared] DEFERRED by owner (2026-07-29) — see the ".NET / CLR hosting" section above. Do NOT write a CLR. Determine whether CoreCLR can be hosted as a guest PE. PROOF: a managed hello-world runs, or a written account of the exact blocker.
Off-screen surfaces.LANDED 2026-07-29. The premise was wrong: memory DCs, compatible bitmaps and BitBlt already existed. What was missing was pixel-DATA transfer (SYS_GDI_SET_DIBITS/_GET_DIBITS, 214 / 215) and, more seriously, ownership --- surfaces had no owner, no per-process bound, and were never reclaimed at exit. Remaining GDI DIB gaps are listed inWin32-Surface-Status.md.- GDI completeness. [dep: 12] Paths landed earlier (per-DC
recorder: Begin/End/Close/Stroke/Fill/StrokeAndFill/Abort/GetPath/
PathToRegion); regions (exact
CombineRgnrect-list algebra + the clip-selection family) and transfer modes (SetROP2viaSYS_GDI_SET_ROP2) landed 2026-08-05. Raster ops completed the same day: all 16R2_*codes now apply per-pixel on memory DCs throughRop2Apply(kernel/subsystems/win32/gdi_surface_math.h), honoured by the line, rect-fill, rect-outline,PatBltand ellipse paths and host-tested code-by-code intests/host/test_gdi32_rop2.cpp. Scan-line interior fill also landed:FillPath/StrokeAndFillPathscan-convert the recorded path and emit horizontal spans, with ALTERNATE (even-odd) and WINDING (nonzero) rules, inuserland/libs/gdi32/gdi32_fill.h(host-tested bytests/host/test_gdi32_fill.cpp). Residue:- Window-DC ROP2 honours only
R2_BLACK/R2_WHITE, applied as a colour transform — the compositor display list carries a colour but no raster-op channel. - Clip enforcement for outlines, lines and text is whole-shape
/ whole-run bounding-box reject rather than per-pixel; a
partially clipped segment draws in full.
FillRectis the exception and does clip per-piece. - No curve recording:
PolyBezier/Arc/ArcToare not path-aware, soFlattenPathhas nothing to flatten and succeeds trivially on a closed path.WidenPathremains a STUB — no pen-width tracking, no outline offsetting. - Fixed pools bound the surface:
GDI_PATH_SLOTSopen paths,GDI_PATH_MAX_PTSpoints,GDI_RGN_SLOTSregions andGDI_RGN_MAX_RECTSrects each, with exhaustion surfacing as a failed call and an over-capCombineRgncollapsing to the bounding box.
- Window-DC ROP2 honours only
- D3D9. Large back catalogue; simpler than 11/12.
- D3D11 completeness and D3D12 beyond the current thunk layer.
- DirectWrite / Direct2D. [dep: 13]
- Vulkan ICD completeness — real pipelines, render passes, swapchain.
- Hardware video decode.
- XAudio2 / WASAPI real mixing. [dep: 21]
- Audio depth — USB audio class, HDMI/DP audio, per-stream volume, resampling.
- DirectInput / XInput bound to real HID devices. The
XInput half is REAL end-to-end as of 2026-08-05. The
hid_gamepaddriver is wired through xHCI — descriptor parse claims non-boot HID interfaces as gamepad candidates, Report-descriptor layout extraction binds confirmed Gamepad/Joystick devices into the 4-slot XInput-shaped state table, and the poll task injects interrupt-IN reports;SYS_GAMEPAD_STATE(230) copies a fixed 44-byte wire snapshot out under a statickCapInputgate;xinput1_4.dllmaps that wire toXINPUT_STATE/XINPUT_CAPABILITIES. Covered bytests/host/test_hid_gamepad.cpp, the gamepad arm ofXhciDescriptorSelfTestat boot, and thexinput_smokePE fixture in the ring-3 battery. Remaining:- Rumble.
XInputSetStateaccepts and drops vibration requests; the HID driver needs an interrupt-OUT report writer before it can do otherwise. - Battery plumbing. Every pad reports wired/full because no battery level reaches the slot table.
XInputGetKeystrokeneeds aVK_PAD_*keystroke queue, andXInputEnable's "mute while unfocused" latch is not tracked at all.- DirectInput8 proper. Joystick/gamepad enumeration and
force-feedback effects in
dinput8.dllare still STUBs; the slot table they would enumerate now exists, so this is wiring rather than new driver work.
- Rumble.
- Power management. The S3 core LANDED 2026-07-29: real-mode wake
trampoline at physical 0x9000 (
arch/x86_64/acpi_wakeup.{S,cpp}), CPU architectural save/restore across the power loss, the FACS waking-vector handshake, generic\_Sxsleep-package decode, and the per-driver Suspend/Resume + veto contract inkernel/power/suspend.{h,cpp}. A full cycle is proven under QEMU + SeaBIOS bytools/test/s3-cycle-smoke.sh. What remains:ResumePlatformre-MAPS MMIO as well as re-programming the controllers (LapicInit / IoApicInit / HpetInit each callmm::MapMmioagain). The arena is a bump allocator with no free, so every cycle leaks arena and strands cached pointers to the old VA; ans3test=1boot faults later in the VirtIO probe. Fix by splitting a re-program-only entry point out of each Init.- NVMe / AHCI / e1000 register
PowerSuspendVetoat attach, so a machine that probed them declines S3. Each needs a quiesce + controller re-init pair to become a participant. - SMP:
PowerSuspendCheckrefuses with more than one CPU online — no AP park/resume path exists. - OVMF never re-enters the waking vector (SeaBIOS does), so UEFI S3 resume is unproven.
- S0ix is untouched.
- CPU frequency scaling — EPP + idle governors. [PARTIAL — the
P-state selection half LANDED 2026-07-29] Reading (Intel ratios,
AMD Zen
MSR_PSTATE_DEFdecode, APERF/MPERF effective frequency) and selecting an operating point are both in tree: Intel HWP (IA32_HWP_REQUEST) preferred where firmware enabled it, legacyIA32_PERF_CTLotherwise, AMDMSR_PSTATE_CTLindex select, all behindcpufreq=tune+kCapPowerTune+ a platform-advertised window, never voltage, never a syscall. See Power-Management "CPU P-state Control". What remains:- EPP / energy-performance preference. The HWP path preserves firmware's EPP and never chooses one. Needs its own policy rationale before it becomes a knob.
- Enabling HWP where firmware left it off.
IA32_PM_ENABLEbit 0 is write-once until reset, so the decision belongs with the EPP work rather than with a single set-the-ratio call. - Idle governors / C-state selection. Untouched; idle stays at firmware default.
- Broadcast to all CPUs.
CpuFreqSetTargetwrites the MSR on the calling CPU only; per-core P-state parts keep their other cores unchanged. - Hardware validation. QEMU answers none of these MSRs under TCG or KVM, so the write path has never executed. It needs a real-silicon first boot.
LANDED. Extable-guardedReadMsrSafe.rdmsrbeside the existingwrmsrtemplate; every MSR consumer now probes instead of predicting, and the static vendor+hypervisor gates are gone from thermal, cpufreq and RAPL.- Thermal + battery policy. [dep: 25 — cleared] The CPU
temperature READ exists for both vendors now (AMD via SMN, still
unvalidated on hardware). What remains is the policy: periodic
resampling into the heartbeat, throttle thresholds, and battery
_BST/_BIFevaluation. - TPM 2.0 — seal / unseal. [PARTIAL — the rest of item 27 LANDED]
The TIS/FIFO transport, presence detection, ACPI TPM2 interface
check, PCR extend + read, and the hardware RNG are in and verified
live against
swtpm; so is the permanent refusal of the identity half (EK export, AIKs, quote signing), enforced by a transport allow-list plus the absence of any raw passthrough. SeeTPM. What remains is sealing itself: the marshalling forTPM2_CreatePrimary/Create/Load/Unsealand the trial/policy session handling that binds a sealed blob to a PCR policy. The command codes are already on the allow-list. When a guest-facing seal syscall lands it must be cap-gated and derive key material per-application, salted per install, so two applications cannot correlate on a shared identifier even locally. - Full-disk encryption. [dep: 27's seal/unseal] Key sealed to PCR state, so no passphrase on every boot.
Measured boot as a LOCAL tripwire.LANDED. PCR 10/11 carry the kernel identity and command line; the composite over PCR 0-7, 10 and 11 is read back from the chip and compared against a baseline pinned withtpm.baseline=. A mismatch warns and never refuses to boot — refusing would be the lock-out behaviour the design set out to avoid. Detection only: nothing signs a PCR set and nothing sends one anywhere. Remaining limitation, recorded as a// GAP:— the baseline shares storage with the thing it measures, which needs the writable persistent store to fix properly.- Secure Boot chain verification.
- Bluetooth. HCI, L2CAP, then HID and A2DP.
- Precision touchpad HID — gestures, palm rejection.
- UVC camera.
- Multi-monitor + hotplug, per-head modeset.
- SD/MMC, Thunderbolt/USB4, more NICs (2.5G Realtek, Intel I225), fingerprint / sensors.
- Real GPU engine-busy sampling. AMD
mmGRBM_STATUSbit 31 is already mapped but read ONCE at probe — a single read of a level-triggered bit is not a duty cycle; it needs a periodic sampler. Intel: RC6 residency or RING head/tail. NVIDIA:NV_PGRAPH_STATUS. Virtual adapters must report NOT APPLICABLE, never 0%. PROOF: unvalidatable in QEMU — needs a bare-metal boot.
- Native journaling FS with checksums; COW snapshots.
- NTFS write (read-only today), ext4 write.
- exFAT cluster-chain growth — writes are bounded today.
- TRIM/discard, NVMe namespace management, software RAID.
- File-change notification (
ReadDirectoryChangesW/ inotify). - Memory-mapped file completeness.
- SMB client — the biggest single interop win on a home network.
- Disk quotas, NFS client.
- TLS 1.3 — a branch exists (X25519 + RSA-PSS done, protocol layer remaining); finish and merge it.
- IPv6 dual-stack.
- Firewall / packet filter, NAT + bridging.
- WireGuard, QUIC / HTTP3, DNS-over-HTTPS, mDNS.
- Wi-Fi depth — WPA3, roaming, iwlwifi.
- WaitQueue detach primitive. One missing scheduler ABI
(
WaitQueueBlockLocked) blocks three filed findings (R1-14regions_lock, R1-15 PS/2 ring, C4 unkillable blocked threads). Building it once retires all three. - Root-cause the intermittent
sync/spinlockself-deadlock seen ~1 boot in 5 during the userland elf-loader OOM self-tests. - CET / shadow stacks, per-entry IBRS, KCFI.
- NUMA awareness, huge pages, memory compression.
- io_uring-style async I/O.
- Real-time scheduling class, cgroup-style resource limits.
- Checkpoint / restore of a running process.
- Kernel live patching.
- Crash reporting with symbolisation; sampling profiler.
- PE/ELF interop as a first-class feature. DuetOS runs both as peers on ONE kernel, ONE VFS, ONE TCP stack. A Linux binary piping into a Windows binary in the same process tree is something Wine cannot do (no Linux kernel ABI) and WSL cannot do natively (two kernels). Most of the substrate exists; what is missing is deliberate plumbing and a demo.
- Hypervisor. [dep: 23] Run Linux/Windows guests;
tools/vmmalready drives WHP on the host side. - Package manager / installer, multi-user + fast switching, remote desktop, accessibility (screen reader), i18n.
- An item is done when its PROOF artefact runs, not when it compiles.
- Anything returning a constant carries
// STUB:; real-but-limited carries// GAP: <what> - <when>. A present-but-lying export is worse than a missing one —LoadStringWis deliberately unexported for exactly this reason. - Prefer extending an existing subsystem over adding one.
- Re-run
tools/test/pe-compat-survey.pyafter Tier 1 items: it measures which real binaries each change actually unblocked, turning "what next" into a measurement instead of a guess.