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

drm_gem_init: unchecked mm kzalloc (NULL-deref panic under kernel OOM), partial-failure leaks, and dangling dev->mm_private on the drm_ht_create failure path

Summary

drm_gem_init kzallocs mm and vma_offset_manager but NULL-checks only the latter; mm failure panics in drm_ht_create(&NULL->offset_hash). The !vma path leaks mm. The ht_create failure path leaves dev->mm_private dangling (assigned before, kfreed without clearing) - later dereferenced by drm_gem_destroy and every create_mmap_offset. Upstream FreeBSD checks both; the port dropped the check and reordered the assignment. OOM-gated module-load-time. Check-both + publish-last fix, compile-validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4459 Β· 10 files
FileTypeDescriptionSize
README.md β€” 733 B ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
run.sh β€” 146 B view raw
build.sh β€” 143 B view raw
run.log β€” 1.1 KB view raw
build.log β€” 3.4 KB view raw
fix.diff β€” 692 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.1 KB view raw
env.txt β€” 630 B view raw

DF-4459 β€” drm_gem_init: unchecked mm kzalloc (NULL-deref panic under OOM), partial-failure leaks, and dangling dev->mm_private

Verify (static, against the audit tree)

sh run.sh sys/dev/drm/drm_gem.c    # prints the asymmetry + dangling assignment

Build (fix compile-validation)

sh build.sh (inside guest, fixed drm_gem.c staged) β€” drm.ko rebuilds -Werror clean, RC=0 (build.log).

Files

  • VERDICT.md β€” root cause with line cites, trigger analysis
  • run.sh + run.log β€” static verification of the defect in-tree
  • build.sh + build.log β€” patched module build
  • fix.diff β€” NULL-check both allocations; assign dev->mm_private only after drm_ht_create succeeds; free both on failure
VERDICT.md
↓ download raw

DF-4459 β€” PoC Verification Verdict

File: sys/dev/drm/drm_gem.c (pass 2), drm_gem_init() at drm_gem.c:130-163. Guest: DragonFly 6.5-DEVELOPMENT (X86_64_GENERIC), cc 8.3.

Verdict: INCONCLUSIVE at runtime (code defect Certain; OOM-gated)

Root cause

139:    mm = kzalloc(sizeof(struct drm_gem_mm), GFP_KERNEL);
140:    vma_offset_manager = kzalloc(sizeof(*vma_offset_manager), GFP_KERNEL);
141:    if (!vma_offset_manager) {              /* <-- mm is NOT checked */
142:        DRM_ERROR("out of memory\n");
143:        return -ENOMEM;
144:    }
146:    dev->mm_private = mm;
148:    if (drm_ht_create(&mm->offset_hash, 12)) {
149:        kfree(mm);                       /* <-- dev->mm_private dangles */
150:        return -ENOMEM;
151:    }

Three defects on the OOM paths:

  1. NULL-deref panic: if kzalloc(mm) (139) fails while kzalloc(vma_offset_manager) (140) succeeds, execution proceeds to drm_ht_create(&mm->offset_hash, 12) (148) β€” a NULL pointer offset dereference inside the drm hashtable create (&NULL->offset_hash, dereferenced by drm_ht_create) β†’ kernel panic. Unprivileged users can drive the system into kernel-memory exhaustion (GFP_KERNEL allocations fail under pressure), but the allocation here happens at drm device registration (module load / device attach), so the practical trigger is memory pressure coinciding with a drm (re)load.
  2. Memory leak: on the !vma_offset_manager path (141-144), mm (139) is leaked.
  3. Dangling dev->mm_private: dev->mm_private = mm (146) is assigned before drm_ht_create can fail; the failure path kfrees mm (149) without clearing dev->mm_private β€” later drm_gem_destroy() runs drm_mm_takedown(&mm->offset_manager) (170) and drm_ht_remove (171) on freed memory (UAF), and every drm_gem_create_mmap_offset_size() in between dereferences the stale pointer (drm_gem.c:570, 576-577). (vma_offset_manager is likewise leaked on this path.)

Upstream FreeBSD checks both allocations (if (!mm) …); the DragonFly port dropped the mm check and reordered the assignment.

Reproduction

Not runtime-reproducible: the only trigger is kzalloc(GFP_KERNEL) failure during drm device init β€” not injectable on the QEMU guest (no fault-injection knob for this site, and no drm hardware; force-loading drm.ko without hardware wedges the loader). Static verification of the defect: run.sh (greps the tree, run.log); the data-flow is fully cited above.

Fix validation

fix.diff checks both allocations, frees both on failure, and moves dev->mm_private = mm after the drm_ht_create success point. Compile-validated on the guest: drm.ko rebuilt with the patch, -Werror, RC=0 (build.log; combined with the sibling DF-4458/DF-4460 hunks). git apply --check passes against the pristine tree. fix_status=not_testable (no OOM injection / no drm hardware), compile-validated.

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff (git apply --check OK) checks both allocations, frees both on failure, and moves dev->mm_private assignment after drm_ht_create success. Compile-validated on the guest: drm.ko rebuilt with the fix, -Werror, RC=0 (build.log). Behavioral runs impossible: OOM-injection at this site is not available on the guest and there is no drm hardware (fix_status not_testable, compile-validated).

fix.diff; build.log (patched drm.ko full -Werror compile+link, DRM_BUILD_RC=0); VERDICT.md 'Fix validation'
↓ fix.diffper-fix-DF-4459

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (grep script output confirming 139/141 asymmetry, 146-before-149 dangling assignment, 170-171 downstream derefs); build.log (drm.ko rebuilt with fix, -Werror, DRM_BUILD_RC=0); fix.diff (git apply --check OK)

PoC changes

no seed; static-verification script authored for the pack

Verified recommended fix

NULL-check both kzallocs, free both on any failure, and assign dev->mm_private only after drm_ht_create() succeeds

Verdict

Code defect certain, OOM-gated: drm_gem_init (drm_gem.c:130-163) kzallocs mm (139) and vma_offset_manager (140) but NULL-checks only vma_offset_manager (141). If mm's allocation fails, drm_ht_create(&mm->offset_hash, 12) (148) dereferences &NULL->offset_hash -> panic; unprivileged memory pressure can induce GFP_KERNEL failure but the site runs at drm device registration, so the practical window is OOM coinciding with a drm load. On the !vma_offset_manager path mm is leaked; on the drm_ht_create failure path dev->mm_private (assigned at 146, before the failure point) kfrees mm at 149 without being cleared, leaving a stale pointer that drm_gem_destroy (170-171) and every drm_gem_create_mmap_offset_size (570, 576-577) later dereference (UAF), and vma_offset_manager is leaked there too. Upstream FreeBSD checks both allocations; the port dropped the mm check and reordered the assignment. Not runtime-reproducible on the audit guest (no OOM injection site, no drm hardware; force-loading drm.ko without hardware wedges the loader) β€” defect proven by static verification (run.log) with all hops cited in VERDICT.md.