agp_i915_write_gtt programs GTT PTEs into the MMADR register BAR (sc_res[0]) instead of GTTADR (sc_res[1]): GPU-register corruption incl. PGTBL_CTL hijack at PTE index 0x808, and OOB writes past the mapped MMADR resource for PTE indices >= 32768
Summary
The i915 write_gtt uses sc_res[0] (MMADR, the 128KB register window) while the GTT lives in GTTADR (sc_res[1]) - proven by the file's own read path using res[1] and by FreeBSD upstream using res[1]; only the DragonFly copy uses res[0]. The i965 and g4x pairs are internally consistent; the i915 pair alone is mismatched (transcription divergence). Every GGTT bind writes PTE values into GPU register space: PTE index 0x808 lands exactly on PGTBL_CTL, reprogramming the GTT base to a GEM page phys|1 - a user who gets a page installed there can author GTT entries via mmap, giving the GPU arbitrary physical mappings (full compromise chain). With a 256MB aperture, indices 32768-65535 write 128KB past the mapped MMADR into adjacent kernel virtual memory. Hardware-gated (gen3 graphics). One-token BAR-index fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4255 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | β | 3.1 KB | view raw | |
| build.sh | β | 88 B | view raw | |
| run.sh | β | 150 B | view raw | |
| build.log | β | 65 B | view raw | |
| run.log | β | 65 B | view raw | |
| env.txt | β | 1007 B | view raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| fix.diff | β | 406 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 3.6 KB | view raw |
DF-4255 VERDICT β agp_i915_write_gtt writes GTT PTEs to the wrong BAR (MMADR, the register window) instead of GTTADR
Status: inconclusive on the audit guest (hardware-gated). Root cause certain at source level.
The bug
DragonFly sys/dev/agp/intel-gtt.c:998-1004:
static void
agp_i915_write_gtt(device_t dev, u_int index, uint32_t pte)
{
struct agp_i810_softc *sc;
sc = device_get_softc(dev);
bus_write_4(sc->sc_res[0], index * 4, pte); /* <-- WRONG BAR */
}
agp_i915_res_spec (intel-gtt.c:139-143) maps two BARs for the CHIP_I915
driver family (915G 0x2582, E7221 0x258A, 915GM 0x2592, 945G 0x2772,
945GM 0x27A2, 945GME 0x27AE β intel-gtt.c:318-347):
sc_res[0]= AGP_I915_MMADR (BAR 0x10) β the memory-mapped register window (128 KB on 915G/945G).sc_res[1]= AGP_I915_GTTADR (BAR 0x1C) β the GTT itself.
The file's own agp_i915_sync_gtt_pte (intel-gtt.c:1092-1098) reads the GTT
back from sc_res[1], and FreeBSD upstream β the direct ancestor of this
file β programs the PTE through sc_res[1] as well
(freebsd-src sys/dev/agp/agp_i810.c, agp_i915_write_gtt:
bus_write_4(sc->sc_res[1], index * 4, pte);). Only the DragonFly copy
writes sc_res[0]. This is a transcription divergence with security
consequences, not an intentional change: nothing else in the file treats
MMADR as containing the GTT for gen3.
Consequences on affected hardware
Reachable from unprivileged userspace: any GGTT bind on a gen3 part goes i915_ggtt_insert_entries (i915_gem_gtt.c:2703-2713) β intel_gtt_insert_sg_entries (intel-gtt.c:1415) β agp_i915_install_gtt_pte (intel-gtt.c:985) β agp_i915_write_gtt. Same for i915_ggtt_insert_page β intel_gtt_insert_page (a distinct function from the known DF-1476/DF-2401 argument swap; the argument order here is correct β the destination BAR is wrong).
- Register-space corruption (any aperture size): PTE index i is
written at MMADR + i*4, clobbering live GPU registers. PTE index 0x808
lands exactly on MMADR+0x2020 ==
AGP_I810_PGTBL_CTL(agpreg.h:198) β the GTT base pointer β reprogramming it to (GEM page phys | 1). A user that can get its GEM page installed as the GTT base effectively gets to author GTT entries from userspace (mmap'd GEM contents), i.e. arbitrary GPU-visible physical mappings β GPU DMA anywhere β full system compromise chain on real hardware. - Out-of-bounds write past the mapped resource (aperture > 128 MB): bus_space handles are KVA mappings sized to the BAR. With the common 256 MB aperture, gtt_mappable_entries = 65536, and PTE indices 32768..65535 write MMADR+128 KB .. MMADR+256 KB β the first 128 KB of which lies entirely past the end of the mapped MMADR resource, into whatever kernel virtual memory adjoins it. Contents are attacker-influenced (page frame numbers of user GEM pages | PTE flags).
Why inconclusive on this guest
The audit guest is QEMU/KVM without any Intel integrated graphics:
pciconf -l shows no 8086:2582/258A/2592/2772/27A2/27AE device and
agp/drm/i915 are not loaded (env.txt). The trigger cannot execute; the
code path is exclusively hardware-driven (device attach β DRM ioctls).
Fix
One-line: restore the upstream BAR index in agp_i915_write_gtt (fix.diff). Not validated on hardware for the same reason; the patched line matches FreeBSD upstream exactly.
References
- sys/dev/agp/intel-gtt.c:139-143 (res spec: MMADR + GTTADR)
- sys/dev/agp/intel-gtt.c:198-218 (i915 driver binds write_gtt)
- sys/dev/agp/intel-gtt.c:998-1004 (the wrong write, sc_res[0])
- sys/dev/agp/intel-gtt.c:1092-1098 (sync reads sc_res[1] β internal inconsistency)
- sys/dev/drm/i915/i915_gem_gtt.c:2703-2713, 3538-3540 (unpriv-reachable callers)
- FreeBSD sys/dev/agp/agp_i810.c agp_i915_write_gtt (upstream: sc_res[1])
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_testablefix.diff restores the upstream BAR index; cannot be validated on this guest for the same hardware-gating reason (no Intel GTT device)
findings/poc/DF-4255/fix.diff (one-line sc_res[0] -> sc_res[1] in agp_i915_write_gtt)
Confirmed kernel references
Detail
Exploit chain
On affected hardware: unprivileged GEM create + execbuf -> bind at rising GGTT offsets -> PTE writes clobber MMADR register space -> index 0x808 reprograms PGTBL_CTL to attacker GEM page -> user-authored GTT (GEM mmap) maps arbitrary physical pages for the GPU -> GPU DMA read/write anywhere -> uid=0. First stage (register clobber) is unconditional on use; not exercised here for lack of hardware.
Evidence (decisive lines)
VERDICT.md (full trace + upstream contrast); build.log/run.log (guest build succeeds, /dev/dri/card0 absent -> hardware-gated); trigger.c (bind walker); fix.diff (one-line BAR restore)
PoC changes
Vendored the uapi structs/ioctl numbers directly into trigger.c (guest kernel headers pull in linux/types.h -> asm/int-ll64.h which does not exist for userland); exact layouts taken from the guest's /usr/src i915_drm.h
Verified recommended fix
agp_i915_write_gtt: write the PTE through sc->sc_res[1] (GTTADR) as FreeBSD upstream does
Verdict
agp_i915_write_gtt (sys/dev/agp/intel-gtt.c:998-1004) writes GTT PTEs via bus_write_4(sc->sc_res[0], index*4, pte) β the MMADR register BAR β while the GTT actually lives in GTTADR (sc_res[1], mapped by agp_i915_res_spec at intel-gtt.c:139-143). The file's own agp_i915_sync_gtt_pte reads the GTT from sc_res[1] (intel-gtt.c:1092-1098) and FreeBSD upstream programs res[1], proving a transcription divergence. On the six CHIP_I915 chipsets (915G/E7221/915GM/945G/945GM/945GME) every GGTT bind from unprivileged GEM use (i915_ggtt_insert_entries -> intel_gtt_insert_sg_entries -> agp_i915_install_gtt_pte) therefore writes PTEs into GPU register space: index 0x808 lands exactly on MMADR+0x2020 == AGP_I810_PGTBL_CTL, handing the GTT base register a (GEM page phys | 1) value β a GPU-side arbitrary-physical-mapping primitive; with a 256MB aperture, indices 32768-65535 write 128KB past the end of the mapped MMADR resource into adjacent kernel virtual memory. Could not be executed: the audit guest is QEMU without any Intel graphics (env.txt: no 8086 display device, agp/drm/i915 not loaded) and the trigger fails at open('/dev/dri/card0') as expected (run.log). Source-level certainty; hardware-gated verification.
No comments yet.