Missing NULL checks on kzalloc/kasprintf returns in drm_sysfs_connector_add cause NULL-deref panic under OOM
Summary
drm_sysfs_connector_add() calls kzalloc() at line 39 and immediately dereferences result at line 40 without NULL check. If kzalloc fails under OOM connector->kdev is NULL and write to connector->kdev->kobj.name at line 40 triggers kernel page fault/panic. Same line also calls kasprintf() whose return assigned to kobj.name without NULL check -- if kasprintf fails kobj.name is NULL any later dereference (intel_dp.c:5251 connector->kdev->kobj.name) panics. Function unconditionally returns 0 so callers cannot detect failure. Contrast drm_sysfs_minor_alloc() at 93-95 correctly checks kzalloc and returns ERR_PTR(-ENOMEM). Trigger: unprivileged local user induces kernel memory pressure during DRM connector registration window (GPU hotplug DP MST topology change driver reload kldload/kldunload or initial driver attach at boot). Deref is write to fixed offset within struct device faulting at low virtual address panic not controllable corruption. [VERIFIED pass2: FALSE POSITIVE for the filed OOM trigger - code shape real and mechanically proven via injected NULLs (silent success with planted NULL name, Fatal trap 12 at drm_sysfs_connector_add+0x58) BUT GFP_KERNEL is M_WAITOK which panics or blocks in the allocator and never returns NULL on DFly, so the OOM precondition is unreachable at platform level; kvasprintf NULL check is dead code; downgrade to Info hardening; NULL-check fix-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2208 Β· 19 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 6.0 KB | β raw | |
| rig_head.c | β | 5.2 KB | view raw | |
| rig_tail.c | β | 14.4 KB | view raw | |
| df22_rig.c | β | 21.0 KB | view raw | |
| build.sh | β | 97 B | view raw | |
| run.sh | β | 206 B | view raw | |
| fire.sh | β | 746 B | view raw | |
| fix.diff | β | 2.2 KB | view raw | |
| build.log | β | 6.0 KB | view raw | |
| run.log | β | 3.5 KB | view raw | |
| run.2.log | β | 3.5 KB | view raw | |
| run_dmesg.log | β | 3.5 KB | view raw | |
| panic.txt | β | 892 B | view raw | |
| fire_serial_full.log | β | 4.4 KB | view raw | |
| fix_module_build.log | β | 2.7 KB | view raw | |
| env.txt | β | 636 B | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 6.3 KB | view raw |
DF-2208 β Missing NULL checks in drm_sysfs_connector_add (sys/dev/drm/drm_sysfs.c:39-42)
VERDICT: false_positive for the filed OOM trigger on DragonFly
(impact none); the missing-check code shape is real and was proven
mechanically, including an actual Fatal trap 12 at
drm_sysfs_connector_add+0x58 when the NULL is injected.
Rig class
Verbatim-code kernel-compiled KLD harness (established drm rig pattern,
DF-2214 precedent): the UNMODIFIED production function is extracted from
the guest /usr/src tree (sha256-pinned, byte-identical to the audited
tree), compiled kernel-resident with production-faithful allocator
shims whose only delta is optional allocation-fault injection at the two
cited call sites, and driven with fabricated drm_connector state.
Build (on the guest, as root)
cd /root/df22/DF-2208 && ./build.sh
Verifies tree hash 331166b1β¦ == audited tree, extracts add (lines
31-46) and remove (48-54) by line-range AND pattern (both must match,
hashes pinned), applies fix.diff to a scratch copy for the renamed
*_fixed variants, assembles df22_rig.c and builds df22_rig.ko
with kernel-context flags (-D_KERNEL -nostdinc -mcmodel=kernel β¦, the
DF-2214 flag set that avoids the link_elf_obj relatab loader panic).
Run
cd /root/df22/DF-2208 && ./run.sh # non-panicking matrix -> run.log
cd /root/df22/DF-2208 && ./fire.sh # THE PANIC: guest dies by design
run.sh: kldload, matrix at load time, kldunload, dmesg capture.
fire.sh: kldload then sysctl kern.df2208_fire=1 β injects
kzalloc==NULL at drm_sysfs.c:39 and calls the verbatim add; line 40
writes at NULL+8 and the kernel takes a fatal page fault. Afterwards
reset the guest from the host: dfbsd-qemu/vm.sh reset with-src.
Expected output
- run.log CASE B:
ret=0 kdev=β¦ kobj.name=0 β¦ SILENT NULL PLANTED - run.log CASE E (fixed):
ret=-12 (-ENOMEM) kdev=0 β¦ NO fault - panic.txt (fire):
Fatal trap 12 β¦ fault virtual address = 0x8 β¦ Stopped at drm_sysfs_connector_add+0x58: movq %rax,0x8(%r12) - Why the filed trigger is dead on DFly:
GFP_KERNEL == M_WAITOK|M_CACHEALIGN;_kmallocwithoutM_NULLOKpanics or blocks, never returns NULL (kern_slaballoc.c:869-878, 1722-1724, 1786-1800). Full chain in VERDICT.md.
Files
See VERDICT.md for the artifact map (run.log, run.2.log, run_dmesg.log, build.log, panic.txt, fire_serial_full.log, fix_module_build.log, df22_rig.c, env.txt, fix.diff). Paired finding: DF-2209 (same file, same rig, same combined fix).
DF-2208 β VERDICT
File: sys/dev/drm/drm_sysfs.c:39-42 (drm_sysfs_connector_add)
Claim: kzalloc/kasprintf results used without NULL checks; OOM during
connector registration β NULL-deref write at line 40 β panic.
Bottom line
false_positive for the filed threat model (OOM-triggered NULL deref), with the code-shape claim mechanically PROVEN including an actual kernel panic when the NULL is injected.
Line-accurate confirmation
drm_sysfs.c:39βconnector->kdev = kzalloc(sizeof(struct device), GFP_KERNEL);no NULL check.drm_sysfs.c:40-42βconnector->kdev->kobj.name = kasprintf(...)β immediate dereference of the unchecked kzalloc result; the kasprintf result is stored unchecked as well.drm_sysfs.c:45β unconditionalreturn 0: callers (drm_connector.c:444-446) can never see an allocation failure.- Contrast:
drm_sysfs_minor_alloc()atdrm_sysfs.c:93-95does check kzalloc and returnsERR_PTR(-ENOMEM).
Mechanical reproduction (verbatim-code KLD rig, DF-2214 pattern)
The UNMODIFIED production function (tree lines 31-46, sha256-pinned,
extracted on the guest from a tree byte-identical to the audited tree)
was compiled kernel-resident with production-faithful allocator shims
(kzallocβkmalloc(size, M, flags|M_ZERO) per slab.h:35; kasprintf
per drm_dragonfly.c:65-94) whose ONLY delta is optional fault injection
at exactly the two cited call sites.
- CASE B (kasprintf NULL injected, run.log):
ret=0,kdev != NULL,kobj.name == NULLβ the function reports SUCCESS with a NULL name planted; the first consumer deref (intel_dp.c:5251connector->kdev->kobj.name) would fault. - CASE C (kzalloc NULL injected, panic.txt):
sysctl kern.df2208_fire=1β verbatim add writes at NULL+8:Fatal trap 12: page fault while in kernel mode fault virtual address = 0x8 fault code = supervisor write data, page not present Stopped at drm_sysfs_connector_add+0x58: movq %rax,0x8(%r12)offsetof(struct device, kobj.name) == 8in both production layout (linux/device.h:44-47, linux/kobject.h:50-51) and the rig layout.
Why false_positive on DragonFly: the OOM precondition cannot occur
The filed trigger is "unprivileged local user induces kernel memory pressure β kzalloc/kasprintf return NULL". On DragonFly that NULL can never come back from these calls:
GFP_KERNELis(M_WAITOK | M_CACHEALIGN)βsys/dev/drm/include/linux/gfp.h:16. NoM_NULLOK.kzalloc(size, flags)βkmalloc(size, M_DRM, flags | M_ZERO)βsys/dev/drm/include/linux/slab.h:35._kmalloc()(sys/kern/kern_slaballoc.c): - zone-limit path (:869-878): returns NULL only withM_NULLOK; otherwisepanic("%s: malloc limit exceeded"). - new-slab pathkmem_slab_alloc()(:1722-1724): kernel_map exhaustion returns NULL only withM_NULLOK; otherwisepanic("kmem_slab_alloc(): kernel_map ran out of space!"). - page-starvation path (:1786-1800): withM_WAITOKthe allocatorvm_wait()s and retries forever β it never returns NULL.kvasprintf()(sys/dev/drm/drm_dragonfly.c:65-82) allocates with the caller flags (GFP_KERNEL) and itsif (str == NULL) return NULL;at :78-79 is dead code for GFP_KERNEL callers on DFly.
Under the claimed OOM conditions the kernel therefore panics INSIDE the
allocator (or blocks) before drm_sysfs.c:40 can ever execute with a
NULL kdev. The cited deref is unreachable through the cited trigger
on this platform. (On Linux, kzalloc(GFP_KERNEL) CAN return NULL, so
the upstream-pattern bug is real there; the DFly port simply cannot
reach it.)
Privesc assessment (standing directive)
Not a memory-corruption primitive on DragonFly: the unreachable write
is to a fixed near-NULL offset (NULL+8), value a kasprintf string
pointer β no control surface even if it were reachable. No uid=0
route; not applicable beyond the panic DoS shape that itself cannot be
triggered as filed.
Fix validation (A/B)
fix.diff adds the two NULL checks with -ENOMEM returns (and, in
the remove half shared with DF-2209, NULLs connector->kdev and frees
the name string).
- In-rig A/B (same KLD): with identical injections the FIXED
variant returns
ret=-12 (-ENOMEM),kdev=NULL, no fault, no silent NULL planted (run.log CASE E). Callers fail loudly viadrm_connector.c:445-446. - Real-module compile validation: fix.diff applied to the guest
/usr/srctree; the productiondrmKLD module rebuilt clean with-Werror(fix_module_build.log, drm.ko relinked, zero errors β the only "error" grep hit is the-Werrorflag string itself). Full-kernel runtime validation is not meaningful on this guest: it has no DRM device (/dev/driabsent, drm.ko not attached) and runtime kldload of the stock drm module GPFs on this guest, so the production path cannot execute there β same justification as the DF-2214 precedent (verbatim kernel-resident code is the executed artifact).
Side observation (filed into the fix)
drm_sysfs_connector_remove() frees only connector->kdev; the
kasprintf'd kobj.name string leaks per connector add/remove cycle
(run.log CASE A: alloc=2 free=1). fix.diff frees it.
Artifacts
run.log,run.2.logβ decisive + repeat matrix runs (fresh guest)run_dmesg.logβ full dmesg of the decisive runbuild.logβ full untrimmed build+run transcript (provenance chain, hashes, cc 8.3 kernel-context build)panic.txt,fire_serial_full.logβ the CASE C fatal trap from the serial console (guest died by design; reset afterwards)fix_module_build.logβ patched real drm module builddf22_rig.cβ assembled source as compiled (baseline + fixed)env.txtβ guest environment
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
fixedA/B validated in the same verbatim KLD: with identical kzalloc/kasprintf NULL injections the FIXED add returns ret=-ENOMEM with connector->kdev==NULL and plants nothing (run.log CASE E), so drm_connector_register (drm_connector.c:445-446) can fail loudly; the injected-NULL write fault no longer exists on the fixed path. Additionally fix.diff was applied to the guest /usr/src tree and the production drm KLD module rebuilt clean with -Werror (fix_module_build.log, drm.ko relinked 780368 bytes). Full-kernel runtime validation is not meaningful on this guest: no DRM device (/dev/dri absent, drm.ko unattached, runtime kldload of stock drm GPFs on this guest) -- DF-2214 precedent.
run.log CASE E lines: [kzalloc-NULL] ret=-12 (-ENOMEM) kdev=0 -- NO fault; [kasprintf-NULL] ret=-12 kdev=0 kobj.name=0 -- kdev freed and NULLed, NO silent NULL planted. fix_module_build.log: cc ... -Werror -c drm_sysfs.c + final ld -r -o drm.ko, rc=0, zero compile errors.
Confirmed kernel references
- sys/dev/drm/drm_sysfs.c:39
- sys/dev/drm/drm_sysfs.c:40
- sys/dev/drm/drm_sysfs.c:45
- sys/dev/drm/drm_sysfs.c:93-95
- sys/dev/drm/include/linux/gfp.h:16
- sys/dev/drm/include/linux/slab.h:35
- sys/dev/drm/drm_dragonfly.c:77-79
- sys/kern/kern_slaballoc.c:869-878
- sys/kern/kern_slaballoc.c:1722-1724
- sys/kern/kern_slaballoc.c:1786-1800
- sys/dev/drm/drm_connector.c:444-446
- sys/dev/drm/i915/intel_dp.c:5251
Detail
Evidence (decisive lines)
panic.txt: Fatal trap 12, fault virtual address 0x8, supervisor write data, Stopped at drm_sysfs_connector_add+0x58: movq %rax,0x8(%r12) -- the cited write through the injected-NULL kzalloc result at drm_sysfs.c:40. run.log CASE B: ret=0 kdev!=NULL kobj.name=0 SILENT NULL PLANTED. run.log CASE E: FIXED variant ret=-12 kdev=0 no fault. Reachability kill chain: sys/dev/drm/include/linux/gfp.h:16 (GFP_KERNEL = M_WAITOK|M_CACHEALIGN), sys/dev/drm/include/linux/slab.h:35 (kzalloc -> kmalloc M_DRM), sys/kern/kern_slaballoc.c:869-878 (NULL only with M_NULLOK, else panic malloc limit exceeded), :1722-1724 (NULL only with M_NULLOK, else panic kernel_map ran out of space), :1786-1800 (M_WAITOK vm_wait retry, never NULL), sys/dev/drm/drm_dragonfly.c:77-79 (kvasprintf NULL check dead for GFP_KERNEL on DFly). fix_module_build.log: patched real drm module rebuilt clean with -Werror.
PoC changes
No seed pack existed; rig authored from scratch in verify mode following the DF-2214 verbatim-KLD pattern. Iterations: (1)
Verified recommended fix
Check kzalloc and kasprintf results in drm_sysfs_connector_add and return -ENOMEM (freeing kdev and NULLing it on the kasprintf failure) -- see fix.diff hunk 1; remove-half (shared with DF-2209) in hunk 2.
Verdict
FALSE POSITIVE for the filed threat model on DragonFly, with the code-shape claim mechanically proven. The missing NULL checks are real at drm_sysfs.c:39-42 (kzalloc result assigned then immediately dereferenced at :40; kasprintf result stored unchecked; unconditional return 0 at :45): a verbatim-code KLD rig compiled the UNMODIFIED production function kernel-resident (tree lines 31-46, sha256-pinned, guest tree byte-identical to the audited tree) and (a) with kasprintf==NULL injected it returned ret==0 with kdev->kobj.name==NULL planted (SILENT NULL PLANTED, run.log CASE B), (b) with kzalloc==NULL injected via sysctl kern.df2208_fire=1 it executed the cited write and the kernel died: Fatal trap 12, fault virtual address 0x8, supervisor write, Stopped at drm_sysfs_connector_add+0x58 movq %rax,0x8(%r12) (panic.txt; offsetof(struct device, kobj.name)==8 matches production layout). HOWEVER the filed trigger (unprivileged user induces OOM so kzalloc/kasprintf return NULL) cannot occur on this platform: GFP_KERNEL is M_WAITOK|M_CACHEALIGN (gfp.h:16) and _kmalloc without M_NULLOK either panics (kern_slaballoc.c:869-878 zone limit, :1722-1724 kernel_map exhaustion) or blocks retrying forever (:1786-1800 page starvation) -- it never returns NULL; kvasprintf drm_dragonfly.c:78-79 checks NULL only because the Linux original can fail. Under the claimed OOM conditions the kernel panics INSIDE the allocator before drm_sysfs.c:40 can run with NULL. The missing checks remain correct hardening (and are required if allocator semantics or gfp flags ever change), and the fix is A/B validated: the fixed variant returns -ENOMEM with kdev==NULL under identical injections, and fix.diff applied to the guest /usr/src rebuilt the production drm KLD clean with -Werror.
No comments yet.