Adapter/vidsw/vidcdevsw array lifecycle races in fb.c: vid_realloc_array frees outside vga_token, tokenless readers, and a vid_register free-slot index race β UAF read/write window and cross-driver type confusion
Summary
The global adapter/vidsw/vidcdevsw arrays are freed and swapped under per-CPU crit only, and vid_register scans/reallocs BEFORE taking vga_token; tokenless readers (vid_find_adapter, vid_get_adapter via console ioctls) can deref the freed array cross-CPU, unregister's NULL write can land in the old array (UAF write), and concurrent registrations can claim the same slot - type-confusing fb_commonioctl's indirect dispatch. Needs >=2 adapters/kmod; stock guest has a single serially-registered vga0 (unreachable). Serialize-the-lifecycle-under-vga_token fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4644 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| fb_dispatch_probe.c | β | 2.3 KB | view raw | |
| probe_run.log | β | 974 B | view raw | |
| verdict.json | β | 3.2 KB | view raw | |
| manifest.json | β | 687 B | view raw |
DF-4644 β fb.c adapter/vidsw/vidcdevsw array lifecycle is not consistently
serialized (UAF window on vid_realloc_array + register index race)
- File:
sys/dev/video/fb/fb.c:76-122, 150-181, 183-200, 230-245, 285-291 - Severity: Low (race window real, trigger platform-gated)
- Status: untested β triggering requires β₯2 video adapters registering/
unregistering concurrently (second card at attach, or a loadable fb driver
module); the stock x86 guest has exactly one built-in
vga0and FB_INSTALL_CDEV is not compiled (LINT64-only), so the race cannot be driven on the audit guest.
The defects
- Array realloc frees outside the serialization token.
vid_realloc_array()(fb.c:76-122) allocates new arrays, copies, thenkfree(adapter)/kfree(vidsw)/kfree(vidcdevsw)(fb.c:104-108) and swaps the global pointers (fb.c:110-114) β all undercrit_enter()only, which is per-CPU and does not stop other CPUs. It is called fromvid_register()(fb.c:162) beforelwkt_gettoken(&vga_token)(fb.c:168). - Tokenless readers.
vid_find_adapter()(fb.c:235-243) scansadapter[i]with no token;vid_get_adapter()(fb.c:288-290) readsadapter[index]with no token;vid_allocate()(fb.c:253-261) scans/updates undercrit_enter()only. A concurrent realloc on another CPU frees the array underneath them β use-after-free reads. Notevid_unregister()does take the token (fb.c:186-198) and writesadapter[va_index] = NULL(fb.c:196) β a write that can land in the freed old array if realloc swapped it in between β use-after-free write window. - Concurrent registration index collision. Two CPUs in
vid_register()both scan for a free slot (fb.c:157-160) before either holds the token (fb.c:168); both can claim the sameindexand both writeadapter[index]/vidsw[index](fb.c:172-173). The loser's adapter keeps ava_indexowned by the winner β laterfb_commonioctl()dispatch(*vidsw[adp->va_index]->get_info)(...)(fb.c:706, 714, 723) calls one driver's switch on another driver'svideo_adapter_tβ type confusion.
On the stock x86 guest only one adapter ever registers (vga0, built-in,
boot-time), so vid_realloc_array() never even runs (adapters stays 1). The race
is reachable only on multi-adapter or module-load configurations.
Supporting guest probe (dispatch sanity, not a race PoC)
fb_dispatch_probe.c + probe_run.log in this directory were run as root on the
audit guest against /dev/ttyv0 to validate the surrounding dispatch analysis:
FBIO_ADPINFO returns every field kernel-written with va_reserved fully zeroed
(by the ioctl layer's IOC_OUT bzero, sys/kern/sys_generic.c:685-690) β no
uninitialized-leak class beyond the known DF-1786 pointer fields β and
FBIO_MODEINFO rejects out-of-range mode ids (999, -5, 0x7fffffff) with ENODEV,
confirming vga_get_info's get_mode_param() bounds (vga.c:527-537) hold at
runtime.
Fix
Serialize the whole array lifecycle with vga_token: take the token in
vid_register/vid_realloc_array/vid_find_adapter/vid_get_adapter/
vid_allocate (or a dedicated lock), and re-scan the free slot after acquiring
it.
DF-4644 β VERDICT
Not verified on the guest (untested). The race is demonstrated by code inspection; driving it requires concurrent adapter registration/unregistration, which the audit guest cannot produce:
vm.sh run_rootfacts: onlyvgapci0(QEMU virtual video) exists;/dev/vgadoes not exist becauseFB_INSTALL_CDEVis defined only in LINT64 (sys/config/LINT64:2181;optionsmapping sys/conf/options:404), so fb.c's cdev block (fb.c:317-551: fb_attach/fb_detach/genfb*) is not compiled into the running X86_64_GENERIC kernel at all.- vga is built into the kernel (sys/config/X86_64_GENERIC:155
device vga0 at isa?) and registers exactly one adapter during boot, serially.adaptersnever exceeds 1, sovid_realloc_array()never executes on this system. - No loadable fb driver modules are present to add a second adapter at runtime.
Proven defect summary (path:line)
vid_realloc_arrayfb.c:76-122: frees the old global arrays (fb.c:104-108) and swapsadapter/vidsw/vidcdevswpointers (fb.c:110-114) undercrit_enter()(fb.c:89) β per-CPU only. Called fromvid_registerfb.c:162 BEFORE the token is taken at fb.c:168, so even token-taking readers are exposed to the free/swap.- Tokenless readers/writers:
-
vid_find_adapterfb.c:235-243 (reader; also called fromvid_allocatefb.c:254 under onlycrit_enterfb.c:253), -vid_get_adapterfb.c:288-290 (reader; reachable from the console ioctl path scvidctl.c:474), -vid_unregisterfb.c:186-198 (writer under token; its write at fb.c:196 can hit the freed old array if a concurrent realloc on another CPU swapped it out mid-flight) β UAF write window. vid_registerfree-slot scan fb.c:157-160 happens before the token fb.c:168; two concurrent registrations can claim the same index, both writingadapter[index]/vidsw[index](fb.c:172-173); the loser's adapter then has ava_indexmapping to the winner's vidsw β type-confused dispatch throughfb_commonioctlfb.c:706/714/723 ((*vidsw[adp->va_index]->get_info)(adp, ...)).
Impact ceiling: UAF read/write on a small pointer array (kernel heap info-leak or constrained wild write through a stale pointer slot) and cross-driver type confusion. Realistic exposure is low: multi-adapter x86 or kmod fb drivers only; no such configuration ships by default.
Runtime sanity evidence actually gathered (guest, root)
See fb_dispatch_probe.c / probe_run.log:
- FBIO_ADAPTER β index 0; FBIO_ADPINFO β name "vga", all fields written,
va_reserved 0/64 bytes untouched (ioctl layer zeroing) β confirms no
uninitialized-stack leak in the ADPINFO fill beyond the known DF-1786 pointer
fields (va_window=0x800b8000 observed).
- FBIO_MODEINFO with vi_mode β {0,2,999,-5,0x7fffffff} β ENODEV (EINVAL from
vga_get_info mapped by fb.c:709-710); no crash; bounds hold.
- FBIO_FINDMODE 80x25 β mode 24.
Recommended fix (fb.c)
--- a/sys/dev/video/fb/fb.c
+++ b/sys/dev/video/fb/fb.c
@@ -154,6 +154,8 @@ vid_register(video_adapter_t *adp)
const video_driver_t *p;
int index;
+ lwkt_gettoken(&vga_token); /* serialize whole array lifecycle */
+retry:
for (index = 0; index < adapters; ++index) {
if (adapter[index] == NULL)
break;
@@ -174,12 +176,15 @@ vid_register(video_adapter_t *adp)
p = *list;
if (strcmp(p->name, adp->va_name) == 0) {
adapter[index] = adp;
vidsw[index] = p->vidsw;
lwkt_reltoken(&vga_token);
return index;
}
}
lwkt_reltoken(&vga_token);
return -1;
plus: move the vid_realloc_array() call under the token (and have it not take
the token itself), and take the token in vid_find_adapter/vid_get_adapter/
vid_allocate. (Exact diff to be finalized by the maintainer; no fix was built
or validated at runtime for this finding β fix_status: not_testable.)
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableNo runtime verification possible (single-adapter guest); fix sketch in VERDICT.md.
VERDICT.md fix section.
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (defect map with fb.c line cites + guest constraints + fix sketch); fb_dispatch_probe.c + probe_run.log (live dispatch sanity on /dev/ttyv0); README.md
PoC changes
n/a β no PoC seed; a dispatch-sanity probe (fb_dispatch_probe.c) was written and run instead to ground the negative results around the finding
Verified recommended fix
Serialize the adapter-array lifecycle with vga_token: take the token across vid_register (including the free-slot scan and the vid_realloc_array call) and in vid_find_adapter/vid_get_adapter/vid_allocate.
Verdict
Race window proven by inspection, not drivable on the audit guest. vid_realloc_array (fb.c:76-122) frees and swaps the global adapter/vidsw/vidcdevsw arrays under only per-CPU crit_enter, outside vga_token (called from vid_register fb.c:162 before the token at fb.c:168), while tokenless readers vid_find_adapter (fb.c:235-243), vid_get_adapter (fb.c:288-290, reachable from console ioctls via scvidctl.c:474) and the token-holding writer vid_unregister (fb.c:196) run on other CPUs β a UAF read/write window on the freed array. Additionally vid_register's free-slot scan (fb.c:157-160) precedes the token, so concurrent registrations can claim the same index and cross-wire adapter/vidsw slots, giving type-confused dispatch in fb_commonioctl (fb.c:706/714/723). Guest constraints: single built-in vga0 (adapters never exceeds 1, realloc never runs), FB_INSTALL_CDEV LINT64-only so the cdev block is not compiled, and no loadable fb modules β no concurrent registration exists to race with. Dispatch surroundings were validated live on the guest (fb_dispatch_probe.c/probe_run.log): ADPINFO fields all kernel-written + va_reserved zeroed (no uninit-leak class; only known DF-1786 pointer leak), out-of-range FBIO_MODEINFO ids rejected ENODEV.
No comments yet.