i915: unauthenticated ADDFB2 u32 overflow in framebuffer offset/size math (tile-aligned geometry) defeats bo-bounds check β OOB GPU scanout read from wrong/adjacent GGTT mappings
Summary
intel_fb_offset_to_xy lacks the driver-side overflow guard upstream added in 70bbe53c6eac (v5.0): the core drm check uses the RAW plane height but the driver re-heights to tile alignment then computes offset/size in u32 - a crafted NV12/CCS fb with offsets[1] near 2^32 wraps the math, the final size check passes garbage-small, and scanout walks past the object's GGTT mapping (upstream author: 'we might scan out some data we're not supposed to see from other parts of the ggtt'). ADDFB2 is DRM_UNLOCKED-only (no AUTH/MASTER) - the only unauthenticated entry into this file; needs gen9+ NV12/CCS planes. File verified == upstream v4.20 + KPI shims + 2 backports (full differential in the pack) - every post-v4.20 upstream display fix is absent. No hardware in the rig - honestly untested with trigger PoC + backport fix authored.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5782 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| addfb2_offset_overflow.c | β | 5.1 KB | view raw | |
| build.sh | β | 117 B | view raw | |
| run.sh | β | 108 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| verdict.json | β | 4.0 KB | view raw | |
| env.txt | β | 450 B | view raw | |
| differential/dfly_vs_v4.20.full.diff | β | 25.6 KB | view raw | |
| differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt | β | 950 B | view raw |
DF-5782 β i915 fb offset/size u32 overflow (missing upstream 70bbe53c6eac guard)
What
intel_fb_offset_to_xy() in sys/dev/drm/i915/intel_display.c:2398 lacks the
driver-side "Catch potential overflows early" guard that upstream Linux added
in commit 70bbe53c6eac ("drm/i915: Make sure fb gtt offsets stay within
32bits", landed v5.0). The DragonFly copy is vendored Linux v4.20 (proven
by whole-file differential, see differential/), which predates that fix.
Build / Run / Expected (on gen9+ Intel hardware)
cc -O2 -o addfb2_offset_overflow addfb2_offset_overflow.c ./addfb2_offset_overflow
- vulnerable driver: crafted NV12/CCS ADDFB2 accepted (ioctl returns 0) where
fixed driver returns
-ERANGE("Bad offset 0x... or pitch ... for color plane") - igt testcases upstream uses:
kms_big_fb/{x,y}-tiled-addfb-size-offset-overflow
On this audit rig
QEMU guest has Bochs VGA only (pciconf chip 0x1234:1111, no i915 attach,
no drm kld). The trigger therefore cannot execute here; verification is by
static code path proof + upstream differential β see VERDICT.md.
Contents
addfb2_offset_overflow.cβ the trigger (card discovery + crafted ADDFB2)differential/dfly_vs_v4.20.nonmechanical.txtβ proof the DFly file == upstream v4.20 + KPI shims + 2 backports (no DFly-invented semantics)differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txtβ the exact missing fix hunkfix.diffβ standalone backport of the guard (explicit u64 math, no helper dependency)verdict.json,manifest.json,env.txt
VERDICT β DF-5782
Status: untested (no Intel GPU on the audit rig; verified by line-level static analysis + upstream differential). Impact ceiling: OOB GPU scanout read from wrong/adjacent GGTT mappings β info-leak-class to the display and/or hardware upset. Upstream author's own impact statement for this bug family (commit 4e05047d3e00, the other half of the same check, which IS present): "in the worst case we might anger the hw by attempting scanout past the end of the ggtt, or we might scan out some data that we're not supposed to see from other parts of the ggtt." The display engine cannot write; this is a read-side primitive disclosed through the visible screen content.
Proven chain (all line refs into sys/dev/drm/i915/intel_display.c unless noted)
-
Entry, unprivileged:
DRM_IOCTL_MODE_ADDFB2is registeredDRM_UNLOCKEDwith noDRM_AUTH/DRM_MASTERinsys/dev/drm/drm_ioctl.c:671β any local user holding an open/dev/dri/card0fd reachesintel_user_framebuffer_create()(intel_display.c:14770) βintel_framebuffer_init()(14544) βintel_fill_fb_info()(2487) βintel_fb_offset_to_xy()(2398). -
Core check passes:
sys/dev/drm/drm_framebuffer.c:205-209rejects(u64)height * pitch + offset > UINT_MAXusing the raw plane height. -
Driver re-heights and overflows: the driver aligns the plane height up to the tile height (
intel_fb_align_height1989-1996;intel_fill_fb_info2587DIV_ROUND_UP(y + height, tile_height)), and computes with u32 arithmetic: *intel_compute_aligned_offset:2360offset = (tile_rows * pitch_tiles + tiles) * tile_size;*intel_fill_fb_info:2635size = DIV_ROUND_UP((y + height) * fb->pitches[i] + x * cpp, tile_size);Withoffsets[1]β [0, 0xffffffff] (only plane 1:offsets[0]is forced to 0 at :14706) and an 8192-wide tiled NV12/CCS geometry, the tile-aligned product crosses 2^32 and wraps after the core check already passed. -
Final sanity check fooled: the wrapped
sizeis small, somul_u32_u32(max_size, tile_size) > obj->base.size(:2643) passes while the real plane extent exceeds the object β scanout runs past the object's GGTT mapping. -
The guard that should exist here (from upstream v5.0
70bbe53c6eac, absent in this tree β differential hunk stored indifferential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt):c height = drm_framebuffer_plane_height(fb->height, fb, color_plane); height = ALIGN(height, intel_tile_height(fb, color_plane)); /* Catch potential overflows early */ if (add_overflows_t(u32, mul_u32_u32(height, fb->pitches[color_plane]), fb->offsets[color_plane])) return -ERANGE;
Hardware gating (honest bound)
- NV12 path only on gen9 KBL/CFL/GLK/CNL (
intel_framebuffer_init:14690-14698 rejects NV12 on <gen9, SKL, BXT, β₯gen11). - CCS path needs
I915_FORMAT_MOD_{Y,Yf}_TILED_CCS(gen9+ with CCS planes). - Creating the malicious fb needs no master; displaying it (to realize the leak) needs a plane update (DRM master) β or a cooperating compositor importing attacker-specified geometry (DRI3/pixmap-from-fd with crafted stride/offset is the realistic vector on a multi-user desktop).
Why not executed here
Guest is QEMU with Bochs VGA (pciconf -lv: chip=0x11111234); the i915
driver never attaches and /dev/dri/card* is absent β the trigger's own
first step reports this (see env.txt). No panic/leak can be produced on this
rig. Classification: untested with static+differential proof, per task
instructions ("verification is code-reading + differential where arithmetic
classes appear (no hardware)").
Fix validation
fix.diff is the faithful backport of 70bbe53c6eac written with explicit
64-bit math (the DFly tree has add_overflows but not add_overflows_t, so
the diff avoids the helper). Not kernel-built here β no i915 hardware to
boot; fix_status=not_tested.
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 is a faithful standalone backport of upstream 70bbe53c6eac (explicit u64 math instead of add_overflows_t, which this tree lacks). Not built/tested: no Intel GPU on the rig, so baseline and patched kernels are both non-executable here; upstream igt kms_big_fb/*-addfb-size-offset-overflow covers both directions on real hardware.
fix.diff; differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt; upstream commit 70bbe53c6eac message ('framebuffer_check() in the core already does the same check, but as it doesn't know about tiling some things can slip through').
Confirmed kernel references
Detail
Exploit chain
open /dev/dri/card0 (no auth/master needed) -> DRM_IOCTL_MODE_ADDFB2 with NV12 (gen9 KBL/CFL/GLK/CNL) or CCS modifier, 8192-wide tiled geometry, offsets[1] near 2^32 -> core drm_framebuffer.c:208 check passes (raw height) -> driver tile-aligns height and wraps u32 products in intel_fb_offset_to_xy/intel_compute_aligned_offset/intel_fill_fb_info -> mul_u32_u32 final check fooled -> fb registered with size smaller than true extent -> when displayed, display engine scans out past object end into adjacent GGTT mappings (upstream igt: kms_big_fb/x-tiled-addfb-size-offset-overflow).
Evidence (decisive lines)
VERDICT.md (full chain with path:line); differential/upstream_4.20_to_v5.1.fb_overflow_hunk.txt (the exact missing guard); differential/dfly_vs_v4.20.full.diff (proves base == upstream v4.20, no DFly semantic divergence); addfb2_offset_overflow.c (trigger source); env.txt (Bochs-only rig).
PoC changes
n/a β trigger authored fresh for this pack (ADDFB2 ioctl, card discovery, crafted NV12/X-tiled geometry with offsets[1] near 2^32); could not be compiled against an i915 device on this rig.
Verified recommended fix
Backport upstream 70bbe53c6eac: reject fbs whose tile-aligned height*pitch+offsets[color_plane] exceeds u32 in intel_fb_offset_to_xy() (fix.diff).
Verdict
Vendored-v4.20 i915 lacks upstream 70bbe53c6eac: intel_fb_offset_to_xy() (intel_display.c:2398) has no driver-side u32-overflow guard for tile-aligned height*pitch+offsets[], so a crafted NV12/CCS ADDFB2 (unauthenticated ioctl per drm_ioctl.c:671) wraps the offset/size math in intel_compute_aligned_offset (:2360) and intel_fill_fb_info (:2635), defeating the final bo-size check (:2643) and enabling OOB GPU scanout reads past the object's GGTT mapping (info-leak-class to the display; upstream author's stated worst case). Proven by whole-file differential against upstream v4.20 (file is v4.20 + KPI shims + 2 backports) and by the upstream fix hunk; NOT executed because the audit rig has Bochs VGA only (no i915 attach) β code-reading + differential verification per task instructions.
No comments yet.