DF-2504 / verdict.json
{ "finding_id": "DF-2504", "status": "reproduced", "verdict": "Divide-by-zero CONFIRMED and reproduced on the real kernel via a single userland ioctl on the real /dev/acd0 (empty QEMU DVD-ROM, O_RDONLY, no disc): Fatal trap 18, Stopped at acd_set_ioparm+0x69: divl -- exactly rounddown(iomax,0) at atapi-cd.c:951 after :724 stores the unvalidated user int. Control roundtrip (2352) works; zero panics deterministically; single-fix kernel returns EINVAL and survives (fix validated). IMPORTANT THREAT-MODEL CORRECTION: the unprivileged-operator arm is REFUTED on current master -- opening /dev/acd0 (or /dev/cd0, or any raw disk device) requires SYSCAP_RESTRICTEDROOT (diskopen, sys/kern/subr_disk.c:1070-1073; EPERM demonstrated for operator-group user in unpriv_open_test.log), so the panic is only reachable by root/RESTRICTEDROOT principals. Effective severity: Low (privileged-user deterministic panic), not the filed Medium unpriv DoS.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "root/RESTRICTEDROOT: open(/dev/acd0, O_RDONLY) -> ioctl(CDRIOCSETBLOCKSIZE, 0) -> acd_ioctl atapi-cd.c:724 (no validation, no priv check) -> acd_set_ioparm :951 rounddown(65534, 0) -> fatal trap 18 -> kernel panic (DoS). No escalation: the primitive is a deterministic crash with fully controlled divisor sign/zero but no memory-safety impact.", "evidence": "panic.txt (Fatal trap 18 ... Stopped at acd_set_ioparm+0x69: divl %ecx,%eax from serial console); run.log (control 2352 ok + fatal run transcript); unpriv_open_test.log (operator-group EPERM at open on /dev/acd0, /dev/cd0, /dev/serno -- RESTRICTEDROOT gate); fix_run.log (patched kernel: EINVAL, survives).", "kernel_refs": [ "sys/dev/disk/nata/atapi-cd.c:723-726", "sys/dev/disk/nata/atapi-cd.c:951", "sys/sys/param.h:400", "sys/kern/subr_disk.c:1070-1073", "sys/sys/cdrio.h:109" ], "poc_changes": "Seed PoC sketch (none existed in the pack) rewritten as findings/poc/DF-2504/poc_setblocksize.c: correct CDRIOCSETBLOCKSIZE = _IOW(c,110,int) from sys/sys/cdrio.h, O_RDONLY open, control subcommand (2352 + CDRIOCGETBLOCKSIZE roundtrip) before the fatal 0 run.", "attempts": 4, "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": 1500, "guest_dirty": 0, "build_cmd": "cc -O -Wall -o poc_setblocksize poc_setblocksize.c", "run_cmd": "./poc_setblocksize /dev/acd0 2352 # control\n./poc_setblocksize /dev/acd0 # fatal: CDRIOCSETBLOCKSIZE(0)", "code_hash": "2ccd941d918c04b360a51315a02a3bf9cb05218e2728c6852255b1966f37b7f4", "notes": "Guest left clean: disk reverted to with-src snapshot and booted after validation. Related hardening observation (not filed): CDRIOCSETBLOCKSIZE also accepts negative and huge block sizes (rounddown math defined but si_iosize_max/si_bsize_phys nonsense); a range check (2048/2352 like acd_strategys switch) would be the thorough fix. No memory-corruption class here (divide trap only), so no exploit development beyond the panic per contract; uid0 route not applicable.", "recommended_fix": "In acd_ioctls CDRIOCSETBLOCKSIZE case reject non-positive (ideally non-2048/2352) block sizes with EINVAL before storing and calling acd_set_ioparm.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fixed", "fix_evidence": "fix_run.log: patched kernel #1 (Thu Oct 15 23:37:29 UTC 2026) -- control 2352 ok; CDRIOCSETBLOCKSIZE(0) returns Invalid argument (EINVAL), no trap, guest up. Baseline (kernel #0): same ioctl -> Fatal trap 18 at acd_set_ioparm+0x69 (panic.txt). 1/1 baseline panic vs 0/1 patched. fix.diff (EINVAL on <=0) applied to guest /usr/src only; audit sys/ tree untouched.", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Thu Oct 15 23:37:29 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64" } |