acd_fixate() integer divide-by-zero panic when all acd_get_cap() capability reads failed at attach (cur_write_speed stays 0)
Summary
acd_get_cap assigns cur_write_speed = max(..,177) ONLY inside the success branch of its 5-attempt MODE SENSE(0x2a) loop; if all five attempts fail at attach the M_ZERO softc keeps cur_write_speed == 0 forever. The first CDRIOCFIXATE that passes CLOSE TRACK executes timeout / (cur_write_speed / 177) with divisor 0 -> x86 #DE -> kernel panic. REPRODUCED on the audit guest via device-state emulation (capfail mode: boot trace shows the 5 failing attempts; ioctl reaches the division): 'Fatal trap 18: integer divide fault', 'Stopped at acd_ioctl+0x1842: idivl'; fix validated (division executes with clamped divisor, guest survives). Divisor-clamp fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4185 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| panic.txt | β | 1.8 KB | view raw | |
| panic_serial_full.log | β | 105.9 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw | |
| manifest.json | β | 873 B | view raw |
DF-4185 β acd_fixate() integer divide-by-zero panic via cur_write_speed == 0
- File:
sys/dev/disk/nata/atapi-cd.c(pass 2) - Class: kernel panic / local DoS from device-state-dependent division (CWE-369)
- Reachability:
CDRIOCFIXATEon/dev/acd0(root / RESTRICTEDROOT per the disk-layer gate), against a drive whose capability page reads all failed at attach (sleepy/ill USB ODD, erroring firmware, or a crafted device).
Root cause
acd_get_cap() (sys/dev/disk/nata/atapi-cd.c:1696-1717) retries the
capabilities MODE SENSE 5 times; only on success does it run
cdp->cap.cur_write_speed = max(ntohs(...), 177);. If all five attempts fail,
the M_ZERO-allocated acd_softc leaves cap.cur_write_speed == 0 forever
(no other write site). Later,
/* atapi-cd.c:1158-1160 */
if ((error = acd_test_ready(dev)) != EBUSY) {
timeout = timeout / (cdp->cap.cur_write_speed / 177); /* 0/177 == 0 */
divides by cur_write_speed / 177 == 0 β x86 idiv trap 18 β kernel panic.
Reproduction
Crafted/failed-device emulation on the audit guest (DF-3690-family method;
hook in ata_atapicmd, driver source unmodified): df_audit.capfail=1 makes
every MODE SENSE page 0x2a (capabilities) fail during acd_attach, so
cur_write_speed stays 0 exactly as with a real failing drive. df_audit.mode=1
makes the drive accept the burner-only commands (MODE SENSE/SELECT page 0x05,
CLOSE TRACK) so fixate proceeds to the division.
./build.sh && ./run.sh # same trigger binary as DF-4184
Observed (panic.txt, full serial in panic_serial_full.log):
Fatal trap 18: integer divide fault while in kernel mode cpuid = 5; lapic id = 5 instruction pointer = 0x8:0xffffffff80340232 current process = 831 Stopped at acd_ioctl+0x1842: idivl %ecx,%eax <- inlined acd_fixate
Guest died to DDB; ssh sessions killed mid-command.
Fix validation
fix.diff clamps the divisor (write_speed < 177 -> 177). Validated on the
guest with the identical emulated attack (df_audit.capfail=1):
pre-fix β trap 18 panic; post-fix (DF-4185/fix_run.log sample at
../DF-4184/fix.2.log) β the division path executes (TEST_UNIT_READY
err=5 != EBUSY traced) and the ioctl returns EIO after the normal wait,
no panic, guest stays up.
Contents
panic.txtβ decisive serial-console excerpt (trap + backtrace)panic_serial_full.logβ full serial log of the panic bootfix.diffβ divisor clamp (subset of DF-4184's diff)- trigger binary/source shared with
../DF-4184/
DF-4185 VERDICT
Status: reproduced (impact: panic β kernel panic / local DoS; no memory
corruption: trap 18 is a faulting divide, not a write primitive)
Confidence: certain.
Narrative
acd_get_cap() populates cap.cur_write_speed only inside the success branch
of its 5-attempt MODE SENSE(0x2a) loop (atapi-cd.c:1706-1716); on total
failure the zero-initialized softc keeps cur_write_speed == 0 for the life
of the device. The guard max(...,177) everyone remembers only protects the
success path. acd_fixate() later computes timeout / (cur_write_speed / 177)
(atapi-cd.c:1159) β with speed 0 the divisor is 0 and the kernel takes an
integer-divide fault.
Reproduced on the audit guest with the DF-3690-family device-state emulation:
df_audit.capfail=1 fails the capability senses at attach (hook converts every
page-0x2a MODE SENSE to EIO β the trace shows the 5 failing attempts at boot),
df_audit.mode=1 lets the burner-only commands succeed so acd_fixate walks
to the division. One ioctl(CDRIOCFIXATE) from the trigger binary produced:
Fatal trap 18: integer divide fault while in kernel mode Stopped at acd_ioctl+0x1842: idivl %ecx,%eax
(acd_ioctl+0x1842 = inlined acd_fixate; RIP 0xffffffff80340232.) The guest
went to DDB; in-flight ssh died β the honest panic signature. Same run also
re-captured the DF-4184 over-read sample (see panic_serial_full.log), which
is the full acd_fixate trace up to the trap.
Why "panic" and nothing more
idiv with zero divisor raises #DE before any result is written; there is no
attacker-controlled quotient/remainder, no OOB write. Impact ceiling: local
DoS (repeatable per fixate call whenever the speed field is 0).
Preconditions (honest)
- All five capability-page senses fail at
acd_attach(real-world: drive erroring/timing out during probe; crafted device; some USB enclosures) β OR any future path that leaves the field 0. CDRIOCFIXATEcallable:/dev/acd0is root:operator 0640 and the DF disk layer gates opens withSYSCAP_RESTRICTEDROOT(subr_disk.c:1072, verified live: plain operator-group user gets EPERM), so the caller is root/RESTRICTEDROOT software; a drive that failed cap-read at attach but later accepts write-param mode pages + CLOSE TRACK (burner-family behavior or crafted device) walks the whole path.
Fix validation
Same emulation kernel + fix.diff divisor clamp, capfail=1, pl=50:
the traced sequence reaches TEST_UNIT_READY err=5 (!= EBUSY) β i.e. the
division executed β and the ioctl returns EIO after the normal fixate wait;
no trap, guest uptime clean. Baseline identical configuration = trap 18.
Logs: panic.txt (baseline) vs ../DF-4184/fix.2.log (patched).
Path references
sys/dev/disk/nata/atapi-cd.c:1158-1160β the divisionsys/dev/disk/nata/atapi-cd.c:1706-1716β speed set only on cap-sense successsys/dev/disk/nata/atapi-cd.c:144β softcM_INTWAIT | M_ZERO(speed 0 default)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedfix.diff divisor clamp applied on the same emulation kernel with the same attack (capfail=1, valid page_length): the traced run reaches TEST_UNIT_READY != EBUSY (the division executes) and completes the ioctl without any trap, guest stays up; identical configuration on the unpatched kernel panics with trap 18
baseline: panic.txt ('Fatal trap 18', 'idivl %ecx,%eax'); patched: ../DF-4184/fix.2.log ('survived (no panic)', 'hook ccb0=00 ... err=5' showing the != EBUSY division path taken)
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt (Fatal trap 18 + idivl backtrace from the serial console); panic_serial_full.log (full acd_fixate command trace up to the trap, incl. the DF-4184 over-read re-capture); ../DF-4184/fix.2.log (patched kernel, same attack, division path executed, no trap)
PoC changes
trigger binary shared with DF-4184; emulation tunables df_audit.capfail=1 (fail all page-0x2a senses at attach, emulating a failing/crafted drive) + df_audit.mode=1 (accept burner-only commands so acd_fixate reaches the division)
Verified recommended fix
clamp the divisor in acd_fixate (write_speed < 177 -> 177); alternatively default cur_write_speed to 177 in acd_get_cap's failure path
Verdict
acd_fixate() divides by (cdp->cap.cur_write_speed / 177); acd_get_cap() only assigns cur_write_speed (>=177) on a successful capabilities MODE SENSE, so a device whose five cap reads fail at attach leaves the field 0 and the very first CDRIOCFIXATE that gets past CLOSE TRACK takes an integer-divide fault: reproduced as 'Fatal trap 18: integer divide fault', 'Stopped at acd_ioctl+0x1842: idivl %ecx,%eax' on the audit guest using device-state emulation (cap senses forced to fail at attach, burner commands accepted), driver source unmodified. Impact is a kernel panic / local DoS (faulting divide, no write primitive). Caller must pass the disk-layer RESTRICTEDROOT gate, so the practical trigger is root/RESTRICTEDROOT burning software against a failing or crafted drive.
No comments yet.