CDRIOCSETBLOCKSIZE accepts block_size=0 -> divide-by-zero kernel panic
Summary
CDRIOCSETBLOCKSIZE stores user-supplied int directly into cdp->block_size with no validation then immediately calls acd_set_ioparm which computes rounddown(cdp->iomax cdp->block_size) macro divides by second argument. block_size=0 triggers integer divide-by-zero trap in kernel context. Any operator-group user single ioctl deterministic panic no disc required. O_RDONLY sufficient no FWRITE check on ioctl. [VERIFIED: deterministic privileged panic via ioctl on stock kernel; the operator-group unpriv arm REFUTED - raw disk open is SYSCAP_RESTRICTEDROOT-gated on current master]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2504 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc_setblocksize.c | β | 2.0 KB | view raw | |
| build.sh | β | 145 B | view raw | |
| run.sh | β | 282 B | view raw | |
| run.log | β | 1.8 KB | view raw | |
| panic.txt | β | 761 B | view raw | |
| unpriv_open_test.log | β | 1.9 KB | view raw | |
| env.txt | β | 1.2 KB | view raw | |
| fix.diff | β | 348 B | view raw | |
| fix_run.log | β | 984 B | view raw | |
| verdict.json | β | 3.9 KB | view raw |
Fix verification
fixedfixed
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.
Confirmed kernel references
Detail
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 (decisive lines)
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).
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.
Verified recommended fix
In acd_ioctl's CDRIOCSETBLOCKSIZE case reject non-positive (ideally non-2048/2352) block sizes with EINVAL before storing and calling acd_set_ioparm.
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.
No comments yet.