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

DragonFly devm_request_mem_region shim is conflict-blind: i915_adjust_stolen's stolen-region safety check is dead code (+ static-rman double-init/leak/off-by-one)

Summary

The DF shim reserves stolen in a private static rman no other driver consults and rman_manage_region does no overlap check, so the 'something is seriously wrong' -EBUSY path can never fire; combined with DF never carving stolen out of phys_avail, a BIOS leaving stolen inside e820-RAM yields silent kernel-page/GPU-DMA double ownership. Shim internals also buggy: second call re-inits the live rman (duplicate rman_head insert + leaked lock), rm_end off-by-one, res never freed. Firmware-gated; repair-the-shim fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4678 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
fix.diff β€” 1.2 KB view raw
env.txt β€” 977 B view raw
manifest.json β€” 809 B view raw
verdict.json β€” 5.5 KB view raw

DF-4678 β€” DragonFly devm_request_mem_region shim makes i915's stolen-region

conflict detection vacuous (+ static-rman/leak/off-by-one defects)

sys/dev/drm/i915/i915_gem_stolen.c:83-104 (the #ifdef __DragonFly__ shim) versus the conflict check at :156-188.

No runnable PoC on this guest (no Intel GPU β€” the shim cannot execute); this pack is the analysis record with line-accurate citations against the guest's own /usr/src copy (md5-verified, env.txt). verdict.json records status untested / impact none accordingly.

What the shim does (and doesn't)

static struct linux_resource * devm_request_mem_region(struct device *dev,
    resource_size_t start, resource_size_t n, const char *name)
{
    static struct rman stolen_rman;
    ...
    if (rman_init(&stolen_rman, -1))      /* private, static rman */
        return NULL;
    if (rman_manage_region(&stolen_rman, ...))
        return NULL;
    res = kmalloc(sizeof(*res), M_DRM, GFP_KERNEL);
    return res;                           /* always non-NULL on success */
}

Upstream, devm_request_mem_region() fails (NULL) when the range conflicts with another driver's reservation β€” the property i915_adjust_stolen() depends on: "if the region is already marked as busy, something is seriously wrong" β†’ -EBUSY β†’ stolen disabled (:182-187). On DragonFly:

  1. The check can never fire. The shim's private static rman is consulted by nobody; rman_manage_region() performs no overlap check at all (subr_rman.c:123-150, "NB: this interface is not robust ... multiple copies"). The -EBUSY path is dead code; i915 proceeds with stolen on any BIOS.
  2. DF also never reserves stolen from phys_avail β€” the phys_avail_reserve(intel_graphics_stolen_base, ...) call in sys/bus/pci/x86_64/pci_early_quirks.c:310-312 is commented out ("XXX: enable this once the KPI is available"). On firmware that leaves the stolen range inside e820-RAM, the kernel page allocator and the i915 stolen allocator can own the same physical pages β†’ silent GPU-DMA/kernel cross-corruption β€” the exact scenario the (now vacuous) check exists to catch.
  3. Latent shim defects: rm_end = start + n manages one page too many (rman ranges are inclusive); a second call re-runs rman_init() on the live static rman β†’ duplicate insert into the global rman_head + leaked rm_slock (subr_rman.c:87-116); the kmalloc'd res is never freed; and the retry at :175-177 passes resource_size(dsm) - 2, which underflows for a 1-page dsm.

Fix

fix.diff repairs the shim internals (one-time init, inclusive end, wrapped/empty-range refusal). True conflict detection needs kernel KPI the shim doesn't have β€” the durable fix is the early-boot phys_avail reservation (pci_early_quirks.c, cross-referenced). git apply --check clean; not applicable to run on this guest (fix_status: not_testable).

Related but separate: DF-1712 (unzeroed stolen fbdev, pass 1) and DF-4679 (reserved-base u64 wrap, same file).

VERDICT.md
↓ download raw

DF-4678 β€” VERDICT

Status: UNTESTED on the guest (no Intel GPU; the shim cannot execute) Β· impact: none directly demonstrable / silent kernel-memory corruption on affected firmware Β· confidence: certain (code facts) Β· fix: not_testable (git-apply clean, hardware-gated)

Facts (all line-verified against the guest's /usr/src, md5-matched)

  1. i915_adjust_stolen() (i915_gem_stolen.c:106-191) uses devm_request_mem_region() at :162 as its only "is this physical range really free" gate, with the comment "if the region is already marked as busy, something is seriously wrong" and the -EBUSY bail at :182-187 (GEN3 excepted).
  2. On DragonFly that call resolves to the local shim at :83-104: it creates a private static struct rman, rman_init + rman_manage_region, and returns a kmalloc'd token. It cannot observe any other driver's reservations, and rman_manage_region() itself does no overlap checking (sys/kern/subr_rman.c:123-150 β€” the source even warns about multiple copies). Result: the function returns non-NULL except on ENOMEM; the conflict check upstream relies on is structurally vacuous on DF.
  3. Compounding: DF reads the stolen base/size at early boot (sys/bus/pci/x86_64/pci_early_quirks.c:271-306) but the phys_avail_reserve() that would carve it out of the kernel's physical memory is commented out at :310-312 ("XXX: enable this once the KPI is available"). So on a BIOS that leaves stolen inside e820-RAM, nothing β€” neither phys_avail nor the shim β€” prevents the page allocator from handing out pages that i915 simultaneously programs the GPU to DMA over. That is silent kernel-memory corruption, configuration-gated (no unprivileged trigger; pass 1 marked pci_early_quirks.c clean, so this is recorded as cross-reference context from this file's side).
  4. Shim-internal defects (latent, hardening): stolen_rman.rm_end = start + n manages one page beyond the region (rman end is inclusive); calling the shim twice re-runs rman_init() on the live static β€” duplicate rman_head entry + leaked previous rm_slock (subr_rman.c:87-116); res (kmalloc) is never freed; the BIOS off-by-one retry at :175-177 underflows when resource_size(dsm) < 2.

Why not reproduced / not_testable

The guest has no Intel graphics device (/dev/dri absent; i915 never attaches), and the shim is pure kernel KPI (rman) β€” no faithful userspace emulation exists. The security claim rests on the (certain) source facts above plus the upstream semantics of devm_request_mem_region the driver code itself documents.

Fix

fix.diff (git-apply clean): single guarded rman_init, inclusive end bounds, refusal of n == 0 / wrapped ranges, keeps the kmalloc token behavior. This repairs items 4 but cannot manufacture item 1's missing kernel-wide conflict database β€” the durable fix belongs in pci_early_quirks.c (phys_avail reservation). Recommend filing that separately upstream of this driver.

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 repairs the shim's internal defects (single rman_init guarded by a static flag, inclusive rm_end, refusal of n==0 or wrapped ranges) and is git-apply-able against the pristine tree, but the fix cannot be exercised on this guest: the i915 device never attaches under QEMU and the shim is kernel-KPI-dependent (rman), so no userspace harness can faithfully run it. fix_status=not_testable for lack of hardware, not for doubt in the change; a full validation needs any physical G4X+/ILK/BDW+ Intel-gfx DragonFly machine.

fix.diff in pack (git apply --check clean against sys/); source citations above.
↓ fix.diffper-fix-DF-4678

Confirmed kernel references

Detail

Exploit chain

buggy BIOS reports stolen inside e820-RAM -> DF never reserves it from phys_avail (pci_early_quirks.c:310-312 XXX) AND i915_adjust_stolen's devm_request_mem_region cannot detect the conflict (shim always succeeds) -> kernel page allocator and i915 stolen objects own the same physical pages -> GPU DMA over live kernel memory. No unprivileged trigger; no uid0 route; not demonstrable on this guest (no i915 hardware).

Evidence (decisive lines)

Source citations in VERDICT.md: i915_gem_stolen.c:83-104 (shim), :156-188 (dead conflict path), :175-177 (retry with resource_size(dsm)-2 underflow when size<2); subr_rman.c:87-150 (no overlap check, inclusive ranges, rman_head re-insert); pci_early_quirks.c:56,303-312 (stolen detection + commented-out reservation); env.txt (guest /usr/src md5 match).

PoC changes

No PoC runnable: guest lacks Intel graphics. Analysis-only pack; code path cited line-by-line against the guest's own /usr/src (md5-verified).

Verified recommended fix

Repair the shim (one-time rman init, inclusive bounds, wrapped-range refusal) and, upstream of it, reserve the stolen range from phys_avail at early boot so the page allocator can never hand out stolen pages (fix.diff + pci_early_quirks.c cross-ref).

Verdict

NOT TESTED ON THE GUEST (status untested): this QEMU guest has no Intel graphics device, so the i915 attach path and the DragonFly devm_request_mem_region shim (i915_gem_stolen.c:83-104) cannot execute there; the finding is a source-audit fact verified line-by-line against the guest's /usr/src copy (md5 match, see env.txt). FACTS (certain): (1) the shim's private static 'struct rman stolen_rman' is invisible to every other driver and to the kernel's physical-memory bookkeeping, and rman_manage_region (subr_rman.c:123-150) performs no overlap check ('NB: this interface is not robust ... multiple copies') -- therefore i915_adjust_stolen's conflict detection (i915_gem_stolen.c:156-188), whose entire purpose is to refuse stolen use ('something is seriously wrong') when the range is already owned, is VACUOUS on DragonFly: devm_request_mem_region returns non-NULL unconditionally except on ENOMEM, the -EBUSY path at :182-187 is dead code, and the GEN3 'apparently this works' carve-out is meaningless. (2) DF also does not carve stolen out of phys_avail at early boot (the phys_avail_reserve() call in sys/bus/pci/x86_64/pci_early_quirks.c:310-312 is commented out 'XXX: enable this once the KPI is available'), so on firmware that leaves the stolen range inside e820-RAM, nothing prevents the kernel page allocator and the i915 stolen allocator from owning the same physical pages -- silent cross-corruption of kernel memory by GPU DMA (the exact double-ownership scenario the removed check exists to catch; upstream Linux guards it with e820 reservation + the real devm_request_mem_region). (3) Latent shim defects: rm_end = start + n manages one extra page (rman ranges are inclusive, subr_rman.c convention); a second call re-runs rman_init on the live static rman, re-inserting it into the global rman_head and leaking the old rm_slock (subr_rman.c:87-116); the kmalloc'd res is never freed. SECURITY IMPACT: config/firmware-gated (needs a BIOS that leaves stolen inside RAM -- no unprivileged trigger); consequence is silent kernel-memory corruption on such machines, so impact is framed as none-from-unprivileged / corruption-on-affected-firmware. fix.diff repairs the shim internals (one-time rman init, inclusive end, wrapped/empty-range refusal) but honest framing: true conflict detection requires kernel KPI the shim does not have; the durable fix is reserving stolen from phys_avail at early boot (pci_early_quirks.c) -- cross-referenced, out of this file's scope.