Unchecked M_NOWAIT contigmalloc in dcons_drv_init: NULL buffer passed to dcons_init() -> NULL+0x14 store panic at kldload
Summary
The 16KB shared console buffer is allocated M_NOWAIT and passed to dcons_init unchecked - the first header store writes NULL+0x14 (dcons.c:147) -> Fatal trap 12 during kldload under memory pressure. dg.buf also dereferenced on MOD_UNLOAD. Sink REPRODUCED on guest via identical exported dcons_init(NULL,...) call (fatal trap va=0x14); negative control clean. NULL-check + skip-attach + unload-guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4781 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| dconsnull.c | β | 2.6 KB | view raw | |
| Makefile | β | 96 B | β download | |
| build.sh | β | 123 B | view raw | |
| run.sh | β | 141 B | view raw | |
| build.log | β | 940 B | view raw | |
| run.log | β | 976 B | view raw | |
| panic.txt | β | 2.1 KB | view raw | |
| env.txt | β | 359 B | view raw | |
| fix.diff | β | 910 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
PoC DF-4781 β unchecked contigmalloc in dcons_drv_init β NULL-deref panic at kldload (REPRODUCED)
What this proves
dcons_drv_init() (sys/dev/misc/dcons/dcons_os.c:462-464) does not check
contigmalloc() for NULL before calling dcons_init(dg.buf, dg.size, sc).
The very first thing dcons_init() does is write the shared-buffer header:
dcons_init_port(): buf->osize[port] = htonl(osize); /* dcons.c:147 */
so a failed allocation is an immediate NULL+0x14 store β Fatal trap 12
at kldload dcons time.
The trigger is contigmalloc(16KB, M_DEVBUF, M_NOWAIT /* flags=0 */, ...)
failing under kernel-memory pressure (it is called with M_NOWAIT, so it can
legitimately return NULL).
Reproduce (guest, root)
cd /root/dconsrig && make kldload dcons # baseline: module loads fine kenv dconsnull.mode=1 # simulate the allocation failure kldload ./dconsnull.ko # -> panic (Fatal trap 12, va=0x14)
dconsnull.c calls the same exported dcons_init() entry that
dcons_drv_init() calls on the allocation-failure path.
Expected output (serial console)
dconsnull: loaded, mode=1 dconsnull: DF-4781: calling dcons_init(NULL, 16384, sc) -- unchecked contigmalloc sink Fatal user address access from kernel mode from kldload at ... Fatal trap 12: page fault while in kernel mode fault virtual address = 0x14 fault code = supervisor write data, page not present Stopped at dcons_init_port+0x83: movl %edi,0x14(%rcx)
0x14 is offsetof(struct dcons_buf, osize[0]) β exactly the store that
runs on the unchecked-NULL path.
Negative control
kenv dconsnull.mode=0 runs the identical dcons_init()+dcons_putc()
calls on a valid 16KB buffer and prints
dconsnull: NEGCTRL init+putc OK β the crash is specific to the NULL.
DF-4781 verdict β REPRODUCED (panic primitive; natural trigger = allocation failure)
Reproduced?
Yes β the crash sink is reproduced directly on the DragonFly 6.5-DEVELOPMENT
guest (kernel #0 Thu Jul 2 06:02:54 UTC 2026). Mode-1 of dconsnull.ko
invokes the exact function dcons_drv_init() calls after an unchecked
contigmalloc() failure β dcons_init(NULL, 16384, sc) β and the kernel
takes Fatal trap 12 at dcons_init_port+0x83 (movl %edi,0x14(%rcx)),
fault address 0x14 = &((struct dcons_buf *)0)->osize[0]
(dcons.h:49-64, store at dcons.c:147).
The natural trigger β contigmalloc(..., M_DEVBUF, 0 /* M_NOWAIT */, ...)
returning NULL during kldload dcons under kernel-memory pressure β was not
forced on the guest (no fault-injection hook for contigmalloc); the sink
itself is proven and the path from the unchecked allocation to the sink is
three lines of straight-line code (dcons_os.c:462-464 β dcons.c:147).
Root cause chain:
- dcons_os.c:462
dg.buf = contigmalloc(dg.size, M_DEVBUF, 0, 0x10000, 0xffffffff, PAGE_SIZE, 0ul)β M_NOWAIT, unchecked. - dcons_os.c:464
dcons_init(dg.buf, dg.size, sc)β no NULL guard. - dcons.c:147
buf->osize[port] = htonl(osize)β NULL+0x14 store β fatal trap.
Also on the same failure path: if dcons_init were survivable,
dcons_attach() still runs (dcons_os.c:559 ignores dcons_drv_init's
return), and MOD_UNLOAD would later execute dg.buf->magic = 0 /
contigfree(dg.buf, ...) (dcons_os.c:584-586) on the same NULL.
Trigger privilege: root (kldload). Impact: kernel panic (DoS), no
corruption beyond the faulting store. Severity Low.
Evidence
panic.txtβ full serial-console panic (Fatal trap 12, va 0x14,dcons_init_port+0x83).dconsnull.cmode 0 vs mode 1 β negative control vs sink.
Fix
Check the allocation and refuse to attach (see fix.diff):
bail out with drv_init = -1 and a dg.buf == NULL guard on the unload
path (dg.buf->magic = 0 / contigfree), and make MOD_LOAD skip
dcons_attach() when init failed.
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 validated: baseline is a NULL-deref panic with no corruption primitive; fix is a straightforward guard and was not kernel-rebuilt on the guest
fix.diff (three hunks: NULL-check + attach guard + unload guard)
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt (Fatal trap 12, fault va=0x14, dcons_init_port+0x83 store); run.log (mode-0 NEGCTRL OK vs mode-1 panic); dconsnull.c (sink = exported dcons_init, same call dcons_os.c:464 makes)
PoC changes
Seed sketch did not exist; harness written from scratch: kenv-selectable modes (0=negative control, 1=dcons_init(NULL), 2=dcons_putc(NULL)); needed local copy of dcons.h and #include
Verified recommended fix
Check contigmalloc for NULL and fail dcons_drv_init; skip dcons_attach on failure; NULL-guard dg.buf on MOD_UNLOAD.
Verdict
Unchecked M_NOWAIT contigmalloc in dcons_drv_init (dcons_os.c:462-464) feeds NULL straight into dcons_init(), whose first header store (buf->osize[0], dcons.c:147) faults: Fatal trap 12, va=0x14, Stopped at dcons_init_port+0x83 (movl %edi,0x14(%rcx)) β reproduced via a KLD invoking the identical exported sink (mode 1) with a negative control (mode 0) proving the crash is NULL-specific. Natural trigger is kldload dcons under kernel-memory pressure (allocation failure not fault-injected on the guest); root-only, panic/DoS, no further corruption.
No comments yet.