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

Intel graphics stolen-memory reservation is compiled out; drm/i915 trusts the unreserved firmware values and its only conflict guard is a no-op rman stub

Summary

intel_graphics_stolen computes the stolen base/size at init386 but the RESERVATION call is commented out and phys_avail_reserve does not exist anywhere in the tree (live-proven grep) - while i915_module.c still publishes the range and i915_gem_stolen adopts it as GPU-exclusive; the only overlap check (devm_request_mem_region) is a DFly self-scoped no-op rman stub that always succeeds. On buggy-BIOS Intel platforms (~200 gen3+ device IDs) the kernel page allocator and the GPU own the SAME DRAM pages: GPU DMA silently corrupts live kernel memory - exactly the misbehavior the file's own comment says it exists to prevent, shipped disabled. QEMU hard blocker documented (no Intel VGA device model - Bochs 1234:1111 fails the vendor gate), not_reproduced with code-certain root cause. Zero-size-the-resource fix (activates i915's own safe fallback) git-applies cleanly; complete follow-up is implementing the reservation KPI.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5581 Β· 7 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 7.1 KB ↓ raw
probe.sh β€” 1.2 KB view raw
run.log β€” 353 B view raw
fix.diff β€” 1.6 KB view raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 4.6 KB view raw

DF-5581 β€” Intel graphics stolen-memory reservation is compiled out; drm/i915 trusts unreserved firmware values

What to run

dfbsd-qemu/vm.sh status            # must be 'up' (vm.sh up otherwise)
sh probe.sh                        # on the host: the equivalent commands are
                                   # vm.sh run_user / vm.sh run_root sequences

The decisive run captured in run.log:

dfbsd-qemu/vm.sh run_user  'id; sysctl hw.intel_graphics_stolen_base hw.intel_graphics_stolen_size'
dfbsd-qemu/vm.sh run_root  'pciconf -l | grep pci0:0:2:0; \
                            grep -rn phys_avail_reserve /usr/src/sys/ --include=*.c --include=*.h; \
                            kldstat | grep i915'

Expected output (on the QEMU rig)

  • hw.intel_graphics_stolen_base: 0 / hw.intel_graphics_stolen_size: 0, readable by uid=1001 β€” the sysctl surface is world-readable; the quirk is inert here because the VGA at 0:2:0 is Bochs/QEMU (chip=0x11111234, vendor 0x1234), which the vendor gate at pci_early_quirks.c:288 rejects.
  • The full-tree grep returns exactly ONE hit: the commented-out call at pci_early_quirks.c:309 β€” no reservation mechanism exists anywhere in sys/.
  • i915: not loaded (no Intel GPU).

Why this is the finding, not a negative

The finding is a code-proven defect, not a guest-runtime one:

  1. pci_early_quirks() (called from init386(), machdep.c:2760, before cninit()) computes the Intel stolen-memory base/size from firmware-programmed config registers β€” the stated purpose is to reserve that range (pci_early_quirks.c:264-271).
  2. The reservation is commented out (:308-310) and phys_avail_reserve does not exist anywhere in the tree (live-proven on the guest's full /usr/src; include also commented at :35-36).
  3. i915_module.c:18-30 copies the values into intel_graphics_stolen_res; i915_gem_stolen.c:414-417 adopts them as the GPU-exclusive dsm range and allocates GPU objects from it.
  4. The only overlap guard, devm_request_mem_region, is a DragonFly stub (i915_gem_stolen.c:85-102) that manages a static rman containing only the requested region itself β€” it cannot detect any conflict and always succeeds. The Linux conflict semantics are gone.

Net effect: on the exact buggy-BIOS platforms the quirk exists for (firmware did not hide stolen memory from the OS memory map β€” or BSM/GMS disagree with the memory map), the kernel page allocator and the i915 stolen allocator own the same physical pages. GPU writes to stolen objects silently corrupt live kernel memory.

The QEMU rig cannot manifest this: QEMU has no Intel VGA device model, so 0:2:0 can never read 8086:<table id> without patching QEMU itself β€” a documented hard blocker (see VERDICT.md). Code-reading is decisive here.

VERDICT.md
↓ download raw

DF-5581 VERDICT β€” not_reproduced on rig (hardware absent); root cause code-certain

Status: not_reproduced on the available QEMU rig β€” the triggering hardware (Intel gen3+ integrated GPU at PCI 0:2:0) does not exist in QEMU and cannot be emulated without patching QEMU itself. The root cause is proven with line-accurate code reading plus live guest probes of the observable surface.

The finding (code-certain)

intel_graphics_stolen() runs at init386() time (sys/platform/pc64/x86_64/machdep.c:2760, before cninit()), reads the firmware-programmed BSM (0x5c / gen11 0xc0/0xc4) and GMCH (0x50/0x52) registers of the Intel iGPU at 0:2:0, and stores base/size into intel_graphics_stolen_base/size (pci_early_quirks.c:303-304).

The function's own comment (:264-271) states its purpose: buggy BIOSes do not reserve the GPU's stolen memory, so the OS can claim it before the GPU driver loads; the function is supposed to reserve it. It does not:

  • The only reservation call is commented out (pci_early_quirks.c:308-310).
  • phys_avail_reserve does not exist anywhere in the tree β€” live-proven on the guest over the full /usr/src: exactly one hit, the comment itself (run.log). The x86/physmem.h include is commented out too (:35-36).

The consumer side (why "inert globals" are not the end of the story)

  1. sys/dev/drm/i915/i915_module.c:18-30 (i915_init, SI_SUB_DRIVERS): intel_graphics_stolen_res = DEFINE_RES_MEM(intel_graphics_stolen_base, intel_graphics_stolen_size) and prints both values.
  2. sys/dev/drm/i915/i915_gem_stolen.c:414-417 (i915_gem_init_stolen): non-zero size β‡’ dev_priv->dsm = intel_graphics_stolen_res β€” the driver adopts the range as GPU-exclusive and allocates GPU objects (ringbuffers, contexts, framebuffers…) from it via drm_mm.
  3. The only overlap check on the whole path is devm_request_mem_region() at i915_gem_stolen.c:177-189 β€” whose DragonFly implementation (i915_gem_stolen.c:85-102) builds a static rman scoped to exactly [start, start+n) (the region being requested), rman_manage_regions that same range, and returns success. It cannot see any other resource; the "conflict detected" branch is unreachable in practice. Upstream Linux semantics (fail if any kernel/driver resource occupies the range) are not present.

Impact chain (conditional, stated exactly)

Preconditions: Intel gen3+ iGPU at 0:2:0 (the table pci_early_quirks.c:227-262 covers ~200 Intel device IDs), i915 drm module in use, AND firmware that fails to hide the stolen range from the OS memory map (the exact bug class this quirk exists for β€” real-world occurrences are documented in the Linux early-quirks history this code was ported from), or BSM/GMS register values inconsistent with the memory map.

Consequence: the kernel physical page allocator and the i915 stolen allocator own the same DRAM pages. The GPU then DMAs over live kernel data structures β†’ silent kernel memory corruption (integrity/confidentiality of kernel memory from the GPU's perspective, availability certain). No unprivileged-local software trigger exists: the inputs are firmware-programmed config registers; local unprivileged users cannot reach them (config access requires root via /dev/pci, which is FWRITE-gated).

Severity: Medium (kernel memory corruption consequence, but requires the specific hardware + misbehaving firmware precondition; not attacker-actioned from an unprivileged shell). Confidence: certain for the code gap; the population of affected machines is the uncertainty.

What was run on the guest (run.log)

  • sysctl hw.intel_graphics_stolen_base hw.intel_graphics_stolen_size as uid=1001 β†’ both readable (world-readable sysctl surface), value 0.
  • pciconf -l β†’ vgapci0@pci0:0:2:0: class=0x030000 chip=0x11111234 β€” passes the class gate (pci_early_quirks.c:291-292), fails the vendor gate (:287-289, vendor 0x1234 β‰  0x8086) β†’ quirk correctly inert on this rig; i915 not loaded.
  • Full /usr/src grep for phys_avail_reserve β†’ only the comment at pci_early_quirks.c:309.

Attempts: 3 distinct guest probes (sysctl / pciconf / tree grep), all consistent with the code analysis.

Why no escalation chain was pursued

The corruption primitive is GPU-DMA-based and requires an Intel iGPU device model at 0:2:0. QEMU ships none (VGA = 1234:1111, vmware-svga = 15ad:0405, virtio-gpu = 1af4:1050); making 0:2:0 read 8086:<id> requires patching QEMU device models (out of scope for this rig). There is no unprivileged-local software path into the inputs. "No gadget / hardening blocks me" does NOT apply here β€” the blocker is hardware availability, and it is total on this rig.

Fix

fix.diff: stop advertising the unreserved range to drm/i915 (zero-size the resource in i915_init), which activates the driver's own documented safe fallback (i915_gem_stolen.c:414-415: zero size β‡’ stolen memory disabled β€” the same fallback Linux uses under DMAR/vGPU). Kernel-side diagnostic sysctls keep the raw firmware values. The complete fix (implement the phys_avail_reserve KPI at init386 and re-enable advertising) is the follow-up maintainer work; the diff removes the unsafe trust now.

Fix validation: fix_status=not_testable — the baseline harmful behavior cannot be reproduced on this rig (no Intel GPU), so a baseline→patched comparison is impossible here. The patched consumer path was verified by code: DEFINE_RES_MEM(0,0) ⇒ end=-1 ⇒ resource_size()==0 (sys/dev/drm/include/linux/pci.h:621-637) ⇒ early return 0.

Negative sweep (classes considered and killed)

  • Table matching (:299-306): bounded by nitems(), all entries fully initialized by INTEL_VGA_DEVICE (vendor 0x8086, device, ops); per-entry .vendor is ignored but pre-validated once at :288 β€” equivalent, safe.
  • Early cfgreg access: every read uses fixed (bus,slot,func) ∈ {(0,2,0), (0,0,0)} and reg ∈ {0x00,0x02,0x0a,0x50,0x52,0x5c,0xc0,0xc4} with bytes ∈ {2,4} aligned β€” all pass pci_cfgenable bounds (pci_cfgreg.c:181-196); the ECAM width/alignment asymmetry is DF-5570 (sibling file), not reachable from this file's call sites.
  • Integer overflow: sizes are vm_paddr_t (64-bit) Γ— max 0xEFΒ·32MiB β‰ˆ 7.5GiB β€” no overflow; MiB() shifts in unsigned long.
  • Uninitialized reads / type confusion: ops only dereferenced after table match; table fully const-initialized; no writes to config space anywhere in this file.
  • Races: single-threaded BSP boot before SMP start; globals written once, consumed at SI_SUB_DRIVERS β€” no race window.
  • Info leak: hw.intel_graphics_stolen_base/_size are world-readable sysctls exposing a firmware-programmed physical address β€” noted as an Info-grade hardening nit (not filed; value is not a kernel pointer, no demonstrated impact; also inert at 0 on rigs without Intel GPU).

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

Baseline harmful behavior cannot be reproduced on this rig (no Intel iGPU), so a baseline-vs-patched comparison is impossible here. fix.diff is a minimal consumer gate whose semantics were verified by code: DEFINE_RES_MEM(0,0) gives end=-1 hence resource_size()==0 (sys/dev/drm/include/linux/pci.h:621-637), and i915_gem_init_stolen returns early (stolen disabled) on zero size (i915_gem_stolen.c:414-415) - the same fallback Linux uses under DMAR/vGPU. diff git-applies cleanly from repo root; sys/ untouched.

findings/poc/DF-5581/fix.diff (git apply --check OK from repo root); sys/dev/drm/include/linux/pci.h:626-637 (DEFINE_RES_MEM/resource_size); sys/dev/drm/i915/i915_gem_stolen.c:414-415 (zero-size fallback)
↓ fix.diffper-fix-DF-5581

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5581/run.log (unpriv sysctl read of both globals as uid=1001; pciconf showing 0:2:0 = class 0x0300 chip 0x11111234 so the vendor gate rejects it on this rig; full-tree grep: phys_avail_reserve appears ONLY as the comment at pci_early_quirks.c:309; i915 not loaded) + VERDICT.md chain with the no-op devm_request_mem_region stub at i915_gem_stolen.c:85-102 and the trust sink at i915_gem_stolen.c:414-417

PoC changes

no seeded PoC existed; authored probe.sh (guest verification probes) from scratch - the finding's trigger class is firmware/hardware, so evidence = boot-rig probes + line-accurate code reading

Verified recommended fix

Implement the phys_avail reservation KPI at init386 and reserve the stolen range; until then stop advertising the unreserved range to drm/i915 (fix.diff: zero-size intel_graphics_stolen_res, activating i915_gem_init_stolen's zero-size fallback)

Verdict

Root cause is code-certain: intel_graphics_stolen() runs at init386 (machdep.c:2760) and computes the Intel stolen range from firmware-programmed config registers, but its stated purpose - reserving that range - is compiled out (pci_early_quirks.c:308-310; phys_avail_reserve exists nowhere in the tree, live-proven by full-/usr/src grep on the guest). drm/i915 then blindly adopts the values as a GPU-exclusive range (i915_module.c:18-30, i915_gem_stolen.c:414-417) and its only overlap guard, devm_request_mem_region, is a DragonFly stub that manages a rman scoped to the requested region itself and can never detect a conflict (i915_gem_stolen.c:85-102). On buggy-BIOS Intel platforms (firmware failed to hide stolen memory from the OS map) the kernel page allocator and the GPU stolen allocator own the same physical pages, so GPU objects silently corrupt live kernel memory. Not reproduced on the QEMU rig because QEMU has no Intel iGPU device model: 0:2:0 is Bochs VGA 1234:1111, fails the vendor gate at pci_early_quirks.c:288, sysctls stay 0, i915 not loaded. No unprivileged-local software trigger exists (inputs are firmware-owned config registers).