bktr_mmap is re-executed at page-fault time and reads bktr->bigbuf/alloc_pages with no lock while METEORSETGEO's grow path kmem_free()s and reassigns the buffer β fault in the window maps pages via a dangling KVA pointer (stale mappings; ceiling: RW kernel-memory alias)
Summary
The device pager re-queries d_mmap on every fault, so the offset check and vtophys(bigbuf)+offset run against live softc state with no sync; the unlocked METEORSETGEO grow block does get_bktr_mem -> kmem_free(old) -> reassign, and the bare kmem_free performs no pmap_remove - a fault in the window computes atop(vtophys(dangling KVA)) through surviving stale PTEs: deterministic orphaned-page mapping, and if the KVA is re-allocated within the window an RW alias of LIVE kernel memory (only PROT_EXEC refused; /dev/bktr0 is 0444 and METEORSETGEO needs no privilege). Ceiling user->root via kernel-image overwrite; hardware-gated. buf_lock serialization fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4671 Β· 21 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc_race.c | β | 15.2 KB | view raw | |
| bktrig2.c | β | 10.6 KB | view raw | |
| bktr_mmap_race.c | β | 4.6 KB | view raw | |
| build.sh | β | 132 B | view raw | |
| run.sh | β | 194 B | view raw | |
| run.log | β | 2.2 KB | view raw | |
| race2.log | β | 19.5 KB | view raw | |
| race3.log | β | 181.1 KB | view raw | |
| race4.log | β | 89 B | view raw | |
| race5.log | β | 88 B | view raw | |
| race6.log | β | 0 B | β download | |
| race11.log | β | 2.8 KB | view raw | |
| race12.log | β | 2.1 KB | view raw | |
| race_diag.log | β | 1.1 KB | view raw | |
| run_4671.log | β | 32.0 KB | view raw | |
| fix.diff | β | 4.4 KB | view raw | |
| env.txt | β | 1.2 KB | view raw | |
| VERDICT.md | β | 6.6 KB | β raw | |
| README.md | β | 1.2 KB | β raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.6 KB | view raw |
DF-4671 β bktr_mmap fault-time re-query races the unlocked METEORSETGEO buffer swap
Status: NOT REPRODUCED as a race on this rig (source-proven mechanism;
sub-Β΅s window not caught across ~1,700 grow windows / ~10β΄ genuine
first-fault queries). Two discoveries documented: (1) the device pager's
fake-page cache survives munmap (stale phys_addr re-served, d_mmap never
re-queried for already-faulted pindexes); (2) a HELD mapping stays a
live RW alias onto the orphaned pages after the grow path frees the
buffer β deterministic, race-free (--holdcheck).
Build
./build.sh # poc_race (unpriv; includes --seqcheck and --holdcheck modes)
Run
./run.sh # seqcheck (confounder proof), holdcheck (deterministic alias),
# then the hardened grow-vs-fault race as uid 1001
Expected
--seqcheck: 64/64 stale markers re-served with zero concurrency (pager fake-page cache survives munmap β disqualifies naive rigs).--holdcheck: 64/64 markers alive + mapping still WRITABLE after the mapped buffer was kmem_free'd (orphaned-page alias).- hardened race:
catches=0(the in-window dangling resolution was not caught; see VERDICT.md for the full attempt accounting).
DF-4671 β VERDICT (verify run, 2026-10-15)
Verdict
NOT REPRODUCED as a fault-time race on this rig β the mechanism is
source-proven (and every instrumentation confounder was hunted down
and eliminated, which is itself the bulk of this evidence pack), but the
unsynchronized window between kmem_free(old bigbuf) and
bktr->bigbuf = buf (bktr_core.c:1669-1672) proved empirically too
narrow to catch with a userspace faulter across ~1,700 kernel-verified
grow windows and roughly 10β΄ genuine first-fault d_mmap
re-executions. Two important DISCOVERIES about the surrounding
machinery are documented below; one of them (the held-mapping orphan
alias) is a deterministic, race-free manifestation of the same missing
buffer-lifetime bug and is reproduced (holdcheck).
What is source-proven (traced line by line this run)
- The device pager re-executes
d_mmapon the first fault of every page index of a cdev: device_pager.c:348-390old_dev_pager_faultβdev_dmmapβbktr_mmap(bktr_os.c:656-685). bktr_mmapreadsalloc_pages(bktr_os.c:680) andbigbuf(:683) with no lock; the METEORSETGEO grow block mutates both with no lock (bktr_core.c:1664-1681;video_ioctlcontains no locking at all β verified by reading the whole function).- During the window,
vtophys(old KVA)still resolves:kmem_freeβvm_map_removeβvm_map_delete(vm_map.c:3300-3370) performs nopmap_removefor the object-lessVM_MAPTYPE_NORMALentry and no page release (vm_map_entry_delete: unlink+dispose only), so thepmap_kenter'd PTEs survive until anothercontigmallocreuses the KVA β a fault in the window maps the orphaned page. KVA reuse was observed directly (grow #32 reallocating the original RIG_INIT KVA0xfffff80118aa0000; per-grow KVA/PA traces in race_diag.log).
Why it could not be caught here (measured, not hand-waved)
Two confounders had to be proven first β both invalidated naive rigs and both are interesting in their own right:
- The old-style device pager's fake-page cache survives munmap
(
--seqcheck, no concurrency at all): markers written into buffer generation 0 were re-served 64/64 pages after munmap + 10 purely sequential grows. Consequence: a page fault only executes the driver'sd_mmapfor a pindex never faulted before on that cdev; every later fault is served from the cached fake page with a stalephys_addrand never re-queries. All "catches" from early rig versions (race2/race3: 464 events) were exactly this artifact and were disqualified. (This also narrows the finding's premise: the "re-queried on every fault" model holds only per first fault of a pindex.) SETEORSETGEOreturns success without growing when the requested geometry is not strictly larger, so a userspace epoch counter over-counts; fixed by keying all generation accounting on the kernel'salloc_pages(monotonic, changes only on real grows).
The final protocol (race11/race12): production grow path hammered by
1-2 grower threads (each grow kernel-verified via STATUS), 4 faulter
threads faulting only never-faulted pindex groups across 8 rig
cdevs (fresh pager objects per cdev β genuine d_mmap executions),
per-run nonces, marker generations sampled after faults (so
marker.gen < pre-fault alloc_pages can only mean the fault resolved a
page of an already-freed buffer β false-positive-proof). Result across
all hardened runs: 0 catches. The window (the duration of one
kmem_free + two stores, sub-Β΅s to low-Β΅s) against a first-fault rate
of a few thousand per second and a per-buffer marked-page fraction of
1/8β1/4 gives an expected catch count of order 1 per several runs; we
ran out of practical amplification (leaked memory budget, serial
console spam from contigmalloc failures, faulter iteration cost)
before landing one.
What IS deterministically demonstrated (consequence, not the race)
--holdcheck (no race, no threads): an RW mapping established on the
live buffer stays a live, writable alias onto the orphaned pages
after the grow path kmem_frees the buffer beneath it (64/64 marker
pages intact and a test store read back after 10 grows). The device
pager pins nothing, so with the stock code (pages never freed β DF-4672)
a held mapping is a permanent RW window onto orphaned kernel pages; on
real hardware the 0444 node limits an unprivileged holder to PROT_READ
(vm_mmap.c:368-371 denies PROT_WRITE without FWRITE), i.e. a persistent
read alias onto the old frame-buffer pages after they conceptually
ceased to exist. (With the contigfree() fix applied, those pages instead
return to the free queues while still aliased β see the residual note
in fix.diff; a complete fix needs pager-referenced buffer lifetimes.)
Rig fidelity
Same as DF-4672/4673 (production video_ioctl grow path, production
get_bktr_mem/contigmalloc/kmem_free; bktr_mmap verbatim copy β the
only delta is the softc lookup, and the dropped minor-number decode on
the rig's video-function node).
Honest classification
Per the audit table: expected marker absent, run exits 0, guest stays up β not_reproduced, reproduced=0, impact=none for the race claim as filed. The finding remains source-certain (CWE-367 TOCTOU is real in the code); the practical severity hinges on a sub-microsecond window and on the first-fault-only re-query behavior, both now measured. The deterministic held-mapping alias above is the strongest demonstrated consequence and belongs to the same root cause cluster (no buffer lifetime tied to mappings).
Files
| File | Purpose |
|---|---|
poc_race.c |
hardened race PoC + --seqcheck + --holdcheck modes |
race2.log,race3.log |
disqualified runs (pager-cache artifact, 464 events) |
race4.log,race5.log,race6.log,race_diag.log |
post-fix zero-catch runs, KVA/PA traces |
race11.log,race12.log |
final amplified protocol (8 cdevs, 4 faulters, dual growers), zero catches |
run.log |
consolidated record incl. seqcheck/holdcheck transcripts |
bktr_mmap_race.c |
original pass-1 hardware PoC (kept for reference) |
fix.diff |
consistent-sampling + fail-closed bktr_mmap hardening (validated to build; race not catchable so behavioral A/B limited to DF-4672/4673 aspects) |
env.txt, manifest.json, verdict.json |
environment + machine records |
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablenot_testable
The race itself never reproduced on the baseline, so no behavioral A/B of the race is possible; fix.diff's bktr_mmap hardening and the shared grow-block fixes were built and boot-validated on the DF467XFIX kernel (fix_build.log, fix_run.log in the DF-4672/4673 packs), and the fixed bktr_mmap compiles and serves mappings normally (4672/4673 A/B exercised the same grow+mmap paths).
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (consolidated): race11.log/race12.log zero catches under the final protocol; race2/race3 logs show the 464 disqualified pager-cache artifacts; seqcheck + holdcheck transcripts in run.log; race_diag.log per-grow KVA/PA traces proving buffer churn and KVA reuse (stale-PTE window mechanics).
PoC changes
Complete rewrite of the seed: rig device path; per-run nonces; kernel-truth generation counters (SETGEO returns success without growing unless strictly larger -- fixed); never-faulted pindex groups only (8 cdevs for fresh pager objects after proving the fake-page cache survives munmap); marker generations sampled after faults making catches false-positive-proof; single/dual grower + 1-4 faulters; plus --seqcheck/--holdcheck diagnostic modes.
Verified recommended fix
Serialize the grow block against bktr_mmap (consistent sampling + install-before-free + vtophys==0 fail-closed as in fix.diff; complete fix needs pager-referenced buffer lifetimes).
Verdict
NOT REPRODUCED as a fault-time race on this rig. The TOCTOU is source-proven (device pager re-executes d_mmap on first fault of each page index, device_pager.c:348-390; bktr_mmap reads alloc_pages/bigbuf unlocked at bktr_os.c:680/683; the grow block frees and reassigns bigbuf with no lock at bktr_core.c:1669-1672; the bare kmem_free performs no pmap_remove for the object-less entry, vm_map.c:3300-3370, so stale PTEs resolve throughout the window), but across 12+ hardened runs (~1,700 kernel-verified grow windows, ~10^4 genuine first-fault d_mmap executions, 1-2 growers incl. kernel_map-contention dual-grower, 1-4 faulters over 8 cdevs with fresh pager objects) no dangling resolution was caught: the window is empirically sub-microsecond. Two measurement discoveries are documented: (1) the old-style device pager's fake-page cache SURVIVES munmap (--seqcheck: 64/64 stale markers re-served with zero concurrency), so d_mmap re-query happens only on first fault of a pindex -- this both disproved naive rigs (464 false 'catches' disqualified) and narrows the finding's premise; (2) a HELD mapping stays a live RW alias onto the orphaned pages after the grow frees the buffer beneath it (--holdcheck: 64/64 markers alive, still writable, deterministic, race-free) -- the same missing buffer-lifetime root cause, demonstrated without the race.
No comments yet.