⬢ DragonFlyBSD Kernel Audit
DF-4679 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4679",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "likely",
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 2,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df4679 && sh build.sh",
  "run_cmd": "./i915_stolen_harness && ./i915_stolen_harness fixed",
  "code_hash": "61aa391c6d1cbf02d98d7c7293e6adb7a5dc5339b9b6845723e8ff16a9dadacd",
  "verdict": "REPRODUCED at the verbatim-arithmetic level on the DragonFly guest (no Intel GPU exists on this QEMU guest, so the driver-attach path itself cannot run -- the exact inline helpers from include/linux/pci.h:621-642 and both register masks are transcribed and grep-verified against the guests /usr/src at build time, see SOURCE_LINES_VERIFIED in build.log): when the BIOS-programmed CTG/ELK (g4x_get_stolen_reserved, i915_gem_stolen.c:228) or GEN6 (bdw_get_stolen_reserved, :365) STOLEN_RESERVED register yields a reserved base ABOVE stolen_top, *size = stolen_top - *base wraps mod 2^64 (resource_size_t is uint64_t, linux/types.h:35). DEFINE_RES_MEM then computes dsm_reserved.end = base + wrapped_size - 1 == stolen_top - 1 == dsm.end, so resource_contains(&dsm, &dsm_reserved) at :485 PASSES despite the reservation starting above stolen -- the only sanity check between the register read and drm_mm_init. reserved_total = stolen_top - reserved_base (:493) then wraps huge and stolen_usable_size (:499-500) INFLATES far above the real stolen size: measured 2867855360 bytes = 85.47x a real 32 MiB region (G4X, base 0x0ABC0000) and 3220176896 = 95.97x (BDW, base 0xFFF00000), worst-case allocation physical address dsm.start+usable-1 landing 0x7F000000 past dsm.end. drm_mm_init(0, usable) at :503 then hands out offsets beyond stolen, and i915_pages_create_for_stolen (:516,:536) builds sg_dma_address = dsm.start + offset pointing at physical memory outside stolen -- the GEM_BUG_ON(range_overflows(...)) at :516 is a no-op in release builds (CONFIG_DRM_I915_DEBUG_GEM undefined, i915_gem.h:38-61). A sane-firmware control case behaves normally (usable 0.97x real), isolating the wrap as the cause. Fix validated at the same level: with fix.diffs reserved_base > stolen_top extension of the existing :475 sanity check, both garbage cases produce usable == real and allocations stay inside stolen. Threat model: requires malicious/buggy firmware programming the MCHBAR/chipset register (or root writing it before driver load) -- not reachable by an unprivileged local user on a correct BIOS; hence likely overall confidence in kernel manifestation and dos/corruption framing rather than certain.",
  "exploit_chain": "buggy/malicious BIOS (or root via /dev/mem pre-attach) programs STOLEN_RESERVED base above stolen_top on G4X/ILK or non-LP BDW -> u64 size wrap -> containment check bypassed at :485 -> stolen_usable_size inflated (measured 85-96x) -> drm_mm stolen allocator returns offsets beyond dsm.end -> stolen objects get sg_dma_address outside stolen (GEM_BUG_ON inert) -> GPU DMA reads/writes arbitrary adjacent physical memory (kernel memory included) -> silent corruption or panic. No unprivileged trigger; no uid0 route.",
  "evidence": "run.log: DF-4679 REPRODUCED x2 with 85.47x/95.97x inflation, worst-case alloc phys ... OUTSIDE stolen, control case normal; fix_run.log (FIXED build): FIX VERIFIED ... usable (33554432) <= real (33554432) for both garbage cases; build.log SOURCE_LINES_VERIFIED; fix.diff (git apply --check clean).",
  "kernel_refs": [
    "sys/dev/drm/i915/i915_gem_stolen.c:201-229",
    "sys/dev/drm/i915/i915_gem_stolen.c:349-366",
    "sys/dev/drm/i915/i915_gem_stolen.c:470-503",
    "sys/dev/drm/i915/i915_gem_stolen.c:516",
    "sys/dev/drm/i915/i915_gem_stolen.c:536",
    "sys/dev/drm/include/linux/pci.h:626-642",
    "sys/dev/drm/include/linux/types.h:35",
    "sys/dev/drm/i915/i915_gem.h:38-61",
    "sys/dev/drm/i915/i915_reg.h:3507",
    "sys/dev/drm/i915/i915_reg.h:401"
  ],
  "poc_changes": "Self-authored (no seed): logic-level harness transcribing the two size formulas and the init tail; a first draft had a control-case register-value slip (0x02C00<<16 vs intended 0x2C0<<16) that was corrected -- the kernel-modeled arithmetic itself never misbehaved.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Validated at the same verbatim-arithmetic level on the guest: fix.diff extends the existing :475 sanity check to if (!reserved_base || reserved_base > stolen_top) (falling back to the existing ignore-reservation semantics). Baseline build inflates usable_size 85-96x and places worst-case allocations outside stolen; FIXED build (same guard logic compiled in via ./i915_stolen_harness fixed) yields usable == real and allocations inside stolen for both garbage cases, control case unchanged. fix.diff is git-apply-able against the pristine tree (git apply --check clean) and was never applied to sys/. Full kernel-build validation is not possible on this guest (no Intel GPU to attach; the guard is a 2-line branch in driver-init arithmetic already covered exactly by the harness).",
  "fix_evidence": "run.log vs fix_run.log (BASELINE vs FIXED, same three register cases); fix.diff in pack.",
  "attempts": 2,
  "notes": "Same size = top - base pattern exists in g4x (:228) and bdw (:365) only; vlv/gen6/gen7/chv/icl use masked register bases with fixed size fields or top-minus-size, which cannot wrap this way (checked). Upstream Linux carries the identical g4x/bdw code; the fix applies there verbatim. Sibling i915_gem_stolen.c finding DF-4678 (DF shim) is a separate defense-gap issue.",
  "recommended_fix": "Extend the :475 sanity check to also reject reserved_base > stolen_top before computing dsm_reserved/reserved_total (fix.diff)."
}