DF-2352 / verdict.json
{ "finding_id": "DF-2352", "status": "reproduced", "verdict": "REPRODUCED \u2014 deterministic divide-by-zero kernel panic triggered by an UNPRIVILEGED local user through the full production ioctl dispatch. Kernel log first proves the parse half: production uvc_drv_parse_data left a bFrameIntervalType==0 frames interval slots at min=0 max=0 step=0 although the descriptor carried 2000000/10000000/3333 (the driver never reads dFrameInterval[] for continuous frames, uvc_drv.c:1922). Then uid-1001 ioctl(VIDIOC_S_PARM) on /dev/video0 (0666, production uvc_v4l2_reg) executed uvc_v4l2_ioctl -> uvc_drv_set_streampar -> uvc_drv_try_frame_interval continuous branch: Fatal trap 18 (integer divide fault), stopped at uvc_drv_try_frame_interval.isra.2+0x60: divl %r8d,%eax \u2014 the /step at uvc_drv.c:504. The division precedes any USB traffic (uvc_drv.c:1386-1387), so a single unprivileged ioctl suffices. FIX VALIDATED: patched parser reads min/max/step (slots printed 2000000/10000000/3333), step!=0 guard present; full unprivileged S_PARM path returns 0 with no panic.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "Malicious webcam descriptor with continuous-interval frame -> attach parse leaves step==0 (M_ZERO slots) -> cur_frm = that frame via production uvc_drv_init_cur_fmt_frm (device answers GET_DEF/GET_CUR: attacker-controlled) -> any local user opens /dev/video0 (0666) -> VIDIOC_S_PARM -> production dispatch divides by step==0 -> x86 #DE -> kernel panic. Trigger needs no privilege and no device interaction at trigger time.", "evidence": "panic.txt: slots after production uvc_drv_parse_data: min=0 max=0 step=0 + (descriptor carried min=2000000 max=10000000 step=3333), uvc_v4l2_set_streamparm, Setting frame interval to 1/30 (333333)., Fatal trap 18: integer divide fault while in kernel mode, Stopped at uvc_drv_try_frame_interval.isra.2+0x60: divl %r8d,%eax. fix_run.log: step slot=3333, returned 3333200 -- NO DIV-BY-ZERO, trigger_sparm: ioctl returned 0 errno=0.", "kernel_refs": [ "sys/bus/u4b/uvc/uvc_drv.c:1917", "sys/bus/u4b/uvc/uvc_drv.c:1922", "sys/bus/u4b/uvc/uvc_drv.c:500", "sys/bus/u4b/uvc/uvc_drv.c:504", "sys/bus/u4b/uvc/uvc_drv.c:1386", "sys/bus/u4b/uvc/uvc_v4l2.c:503", "sys/bus/u4b/uvc/uvc_v4l2.c:783" ], "poc_changes": "No PoC seed existed. Built from scratch. trigger_sparm.c mirrors the kernels own sys/contrib/v4l/videodev2.h ioctl encoding \u2014 DragonFlys VIDIOC_S_PARM is _IOWR(V, 22, ...) (NOT Linuxs 75); structs mirrored byte-exact. The rig needed the descriptor-blob corrections noted in DF-2351 (shared blob builder).", "attempts": 1, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 300, "guest_dirty": 0, "build_cmd": "./build.sh (setup.sh: uvc_rig.ko via make in guest rig dir; cc -O2 -o /tmp/trigger_sparm trigger_sparm.c)", "run_cmd": "kldload uvc_rig.ko; sysctl hw.usb.uvcrig=2352 (arms /dev/video0 0666); run_user /tmp/trigger_sparm -> panic (see run.sh)", "code_hash": "34e72cb25322fdb5c2a8a81f5fb7bcb858b6a435ba38e818bbf681f853b9bc56", "notes": "Evidence class: production-handler KLD dispatch \u2014 the entire path from the 0666 cdev through uvc_v4l2_ioctl to the faulting division is unmodified in-tree code; only the USB transport and the devices attach-time answers are synthesized (no emulated UVC webcam available for QEMU). Post-fix, the follow-on probe request against the fake transport fails gracefully (USB_ERR_NOT_CONFIGURED) \u2014 a rig artifact; a production device answers it. Guest left up and clean.", "recommended_fix": "Read dFrameInterval min/max/step DWORDs for bFrameIntervalType==0 frames in uvc_drv_parse_data (when bLength permits, else 1/1/1) and guard step==0 (plus min/max clamps) in uvc_drv_try_frame_interval", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "With fix.diff applied (guest /usr/src, module rebuilt -Werror clean): direct call prints step slot=3333 and uvc_drv_try_frame_interval returns 3333200 without #DE; the full unprivileged VIDIOC_S_PARM path returns 0 and the guest stays up (probe fails gracefully against the rigs fake transport).", "fix_evidence": "fix_run.log: RIG2352direct: step slot=3333 ... returned 3333200 -- NO DIV-BY-ZERO; slots after production uvc_drv_parse_data: min=2000000 max=10000000 step=3333; trigger_sparm: ioctl returned 0 errno=0; fix_build.log: clean patched build." } |