β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2208

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2208 Β· 19 files
FileTypeDescriptionSize
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; _kmalloc without M_NULLOK panics 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).

VERDICT.md
↓ download raw

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 β€” unconditional return 0: callers (drm_connector.c:444-446) can never see an allocation failure.
  • Contrast: drm_sysfs_minor_alloc() at drm_sysfs.c:93-95 does check kzalloc and returns ERR_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:5251 connector->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) == 8 in 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:

  1. GFP_KERNEL is (M_WAITOK | M_CACHEALIGN) β€” sys/dev/drm/include/linux/gfp.h:16. No M_NULLOK.
  2. kzalloc(size, flags) β†’ kmalloc(size, M_DRM, flags | M_ZERO) β€” sys/dev/drm/include/linux/slab.h:35.
  3. _kmalloc() (sys/kern/kern_slaballoc.c): - zone-limit path (:869-878): returns NULL only with M_NULLOK; otherwise panic("%s: malloc limit exceeded"). - new-slab path kmem_slab_alloc() (:1722-1724): kernel_map exhaustion returns NULL only with M_NULLOK; otherwise panic("kmem_slab_alloc(): kernel_map ran out of space!"). - page-starvation path (:1786-1800): with M_WAITOK the allocator vm_wait()s and retries forever β€” it never returns NULL.
  4. kvasprintf() (sys/dev/drm/drm_dragonfly.c:65-82) allocates with the caller flags (GFP_KERNEL) and its if (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 via drm_connector.c:445-446.
  • Real-module compile validation: fix.diff applied to the guest /usr/src tree; the production drm KLD module rebuilt clean with -Werror (fix_module_build.log, drm.ko relinked, zero errors β€” the only "error" grep hit is the -Werror flag string itself). Full-kernel runtime validation is not meaningful on this guest: it has no DRM device (/dev/dri absent, 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 run
  • build.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 build
  • df22_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

fixed
baseline reproduced→ patch + rebuild →patched clean

A/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.
↓ fix.diffper-fix-DF-2208

Confirmed kernel references

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) does not define va_list in the merged kmod namespace -- switched to ; (2) the DF-2209 reclaim fix (128-byte class for the planted string) was needed because a same-class helper allocation stole the freed slot; (3) fire case moved behind sysctl kern.df2208_fire so the non-panicking matrix completes before the deliberate fatal trap; guest was reset with-src after the panic run and after fix validation.

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.