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

construct() OOM failure path dereferences NULL current_state / NULL dc->ctx in destruct() β†’ GPU-probe NULL-deref panic instead of graceful fallback

Summary

Every one of construct's six allocation failures goes to fail -> destruct, but destruct unconditionally calls dc_release_state(dc->current_state) - kref_put through NULL when dc_create_state failed - and dereferences dc->ctx when the ctx kzalloc failed. ASSERT is WARN_ON-only so nothing arrests the deref; a single failed kzalloc during amdgpu DC probe panics where dc_create is designed to return NULL. OOM-gated boot/probe DoS. Guard-the-unconstructed fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4215 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 1.5 KB ↓ raw
env.txt β€” 992 B view raw
fix.diff β€” 629 B view raw
manifest.json β€” 758 B view raw
verdict.json β€” 1.9 KB view raw

DF-4215 β€” dc construct() OOM failure path dereferences NULL current_state / NULL ctx in destruct() β†’ probe-time panic

  • File: sys/dev/drm/amd/display/dc/core/dc.c
  • Lines: 519–538 (destruct), 569–620 (construct allocs + goto fail), 686–689
  • Class: NULL pointer dereference on error path (CWE-476/CWE-755)
  • Hardware gate: AMD GPU (amdgpu DC core); trigger = allocation failure during dc_create()

Root cause

construct() performs six kzallocs in sequence (bw_dceip 569, bw_vbios 577, dcn_soc 585, dcn_ip 593, dc_ctx 602, current_state = dc_create_state() 615). Every failure does goto fail β†’ destruct(dc) (686–689). But destruct() unconditionally dereferences the very things that may not exist yet:

static void destruct(struct dc *dc)
{
    dc_release_state(dc->current_state);       /* 521: NULL if 615 failed */
    ...
    if (dc->ctx->gpio_service)                 /* 528: NULL ctx if 602 failed */
        dal_gpio_service_destroy(&dc->ctx->gpio_service);
    if (dc->ctx->i2caux)                       /* 531 */
    ...
  • If the dc_ctx allocation (602–606) fails, dc->ctx == NULL β€” yet 521 runs first: dc_release_state(NULL) β†’ kref_put(&context->refcount, …) (dc.c:1103-1106) reads through the NULL pointer β†’ page-fault panic.
  • If dc_create_state() fails (615–620), dc->current_state == NULL β†’ same panic at 521.
  • Even if current_state were guarded, dc->ctx->gpio_service (528), dc->ctx->i2caux (531), dc->ctx->created_bios (534), kfree(dc->ctx) (537) all deref a possibly-NULL dc->ctx.

dc_create() (729–775) then propagates: construct_fail: kfree(dc); return NULL β€” so with the guards in place the driver degrades gracefully to "Display Core failed to initialize" (amdgpu_dm.c:440-444); without them, a single failed kzalloc during GPU probe panics the kernel.

Note ASSERT_CRITICAL/ASSERT are WARN_ON-only in this tree (os_types.h:59-77), so nothing else arrests the path first.

Verification status

untested β€” hardware-gated (guest's only GPU is the bochs VGA 1234:1111, env.txt; amdgpu DC never initializes, and an OOM at exactly this probe window would additionally be needed). Source-certain.

Fix

fix.diff: NULL-guard dc->current_state and every dc->ctx deref in destruct().

VERDICT.md
↓ download raw

VERDICT β€” DF-4215

status: untested (hardware-gated) Β· impact: panic (probe-time, OOM-gated) Β· confidence: certain (source)

Not reproducible on the audit guest: no AMD GPU (env.txt β€” only bochs 1234:1111), so dc_create()/construct() never execute; the trigger further requires one of six kzallocs to fail during probe.

Source proof: - construct() order: dceip (569) β†’ vbios (577) β†’ dcn_soc (585) β†’ dcn_ip (593) β†’ ctx (602) β†’ current_state (615); every failure goto fail β†’ destruct(dc) (dc.c:686-689). - destruct() line 521: dc_release_state(dc->current_state) with current_state == NULL when 615-620 failed, or when any earlier alloc failed (kzalloc'd dc still zero) β†’ kref_put(&context->refcount, …) (dc.c:1103-1106) is an inline read at NULL+offset β†’ page fault. - Even bypassing that, dc->ctx may be NULL (602-606 failure) and is deref'd at 528/531/534/537. - ASSERT/ASSERT_CRITICAL are WARN_ON-only (os_types.h:59-77) β€” no backstop.

Deterministic NULL-deref, but gated on OOM during GPU probe β†’ Low severity. Fix is defensive guards; not runtime-validated (no hardware) β†’ 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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

diff authored and git-apply-verified against the read-only sys/ tree; runtime fix validation impossible without the triggering hardware on the QEMU guest

findings/poc/DF-4215/fix.diff (git apply --check passed)
↓ fix.diffper-fix-DF-4215

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4215/VERDICT.md, findings/poc/DF-4215/env.txt (guest pciconf: no triggering hardware), findings/poc/DF-4215/fix.diff

PoC changes

no runnable PoC: hardware-gated path absent from the audit guest (env.txt)

Verified recommended fix

NULL-guard current_state and all dc->ctx derefs in destruct()

Verdict

construct() jumps to destruct() on any of six kzalloc failures, but destruct() unconditionally does dc_release_state(dc->current_state) (dc.c:521 β€” kref_put through NULL when current_state alloc at 615 failed) and derefs dc->ctx at 528/531/534/537 (NULL when the ctx alloc at 602 failed). A single OOM during amdgpu DC probe turns into a kernel NULL-deref panic instead of graceful 'Display Core failed to initialize'.