β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2069

Divide-by-zero kernel panic via crafted IDENTIFY in CHS path of tf_write

Summary

ata_serverworks_tf_write CHS path (ata-serverworks.c:216-235): when ATA_D_USE_CHS set loads heads/sectors from IDENTIFY without zero check. Line 228 lba % sectors; line 230 lba / (sectors*heads). ata-disk.c:470 validates (current_heads&current_sectors) for geometry setup but tf_write does NOT. Device setting atavalid=ATA_FLAG_54_58 current_sectors=0 makes ata-disk.c fall through (safe) while tf_write uses unchecked zero -> kernel panic. ATA_D_USE_CHS forced by version_major=0 or lba_size=0 both controllable by malicious SATA device. Only reachable via SWKS_MIO SATA path where tf_write installed at line 158. Attacker: malicious SATA device on ServerWorks HT1000/K2/Frodo SATA port (HP ProLiant/IBM xSeries 2003-2007). IDENTIFY word53=0x0001 word55=0 word56=0 word80=0 words60-61=0. Any I/O -> divide error trap. AV:L/PR:L/AC:L, A:H. Fix: check sectors==0||heads==0 before division.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2069 Β· 7 files
FileTypeDescriptionSize
VERDICT.md verdict Full source-trace narrative + fix validation 3.9 KB ↓ raw
README.md readme Original stub README 972 B ↓ raw
fix.diff suggested-fix git-apply-able div0 guard in tf_write CHS path 1.3 KB view raw
build.sh reproduce Combined kernel build script 755 B view raw
run.sh reproduce Source-only confirmation; no runtime PoC 543 B view raw
fix_build.log build-log Full untrimmed combined kernel build (rc=0, 0 errors) 5.6 MB ↓ download
env.txt environment uname, kern.version, cc version 501 B view raw
README.md readme Original stub README
↓ download raw

DF-2069 PoC β€” ata_serverworks_tf_write CHS divide-by-zero

Status: VERIFIED (source-only) + FIX VALIDATED

ata-serverworks.c:228/230/232/234 divides by sectors and sectors*heads loaded from IDENTIFY without the current_heads && current_sectors guard that the sibling ata-disk.c:469-470 applies. A device that sets ATA_FLAG_54_58 but reports current_sectors == 0 makes the next I/O request divide by zero β†’ kernel panic.

HW-gated on the audit guest (virtio-only, no ServerWorks ATA controller). See VERDICT.md for the source trace and fix validation.

Reproduce

./build.sh   # rebuilds the patched kernel (rc=0 with -Werror)
./run.sh     # source-only confirmation; no runtime PoC (HW-gated)

Fix

fix.diff adds the missing current_heads && current_sectors clause to the atavalid check (matching ata-disk.c:469) plus a final if (heads == 0 || sectors == 0) { request->result = EIO; return; } defense before the divisions.

VERDICT.md verdict Full source-trace narrative + fix validation
↓ download raw

DF-2069 β€” REPRODUCED (source-confirmed, HW-gated) + FIX VALIDATED

Verdict

REPRODUCED via source-only trace. The CHS-path divide-by-zero in ata_serverworks_tf_write is real: the function reads current_heads/ current_sectors from IDENTIFY without the current_heads && current_sectors guard that the sibling geometry-adoption code in ata-disk.c applies. The bug is HW-gated on this guest (no ServerWorks PCI ATA controller present), so there is no runtime PoC.

Mechanism (path:line)

ata_serverworks_tf_write is wired into the channel's hw.tf_write vector at sys/dev/disk/nata/chipsets/ata-serverworks.c:158. Its CHS branch:

  • sys/dev/disk/nata/chipsets/ata-serverworks.c:219 (original) c if (atadev->param.atavalid & ATA_FLAG_54_58) { heads = atadev->param.current_heads; sectors = atadev->param.current_sectors; } else { ... } ATA_IDX_OUTW(ch, ATA_SECTOR, (request->u.ata.lba % sectors)+1); /* :228 */ ATA_IDX_OUTW(ch, ATA_CYL_LSB, (request->u.ata.lba / (sectors*heads))); /* :230 */ ATA_IDX_OUTW(ch, ATA_CYL_MSB, (request->u.ata.lba / (sectors*heads)) >> 8); /* :232 */ ATA_IDX_OUTW(ch, ATA_DRIVE, ... ((request->u.ata.lba%(sectors*heads))/sectors)&0xf); /* :234 */

If ATA_FLAG_54_58 is set in atavalid but current_sectors == 0 (observed on misbehaving / emulated ATA devices that set the validity bit but report zero geometry), sectors is 0 and every CHS expression divides by zero β†’ kernel divide-by-zero trap β†’ panic.

The sibling geometry-adoption code refuses the bad geometry:

  • sys/dev/disk/nata/ata-disk.c:469-470 c if ((atadev->param.atavalid & ATA_FLAG_54_58) && atadev->param.current_heads && atadev->param.current_sectors) { adp->heads = atadev->param.current_heads; ...

ata-disk.c correctly adds the current_heads && current_sectors clause; ata-serverworks.c did not. The result is that ata-disk.c:ad_invalidate() falls through to the safe param.heads/ param.sectors branch but the very next I/O request that calls tf_write panics the kernel.

Reachability / impact ceiling

HW-gated. Requires a ServerWorks ATA PCI controller (ROSB4/CSB5/CSB6/ HT1000/Frodo/K2 family β€” ata-serverworks.c:47-59) with a disk whose IDENTIFY data sets ATA_FLAG_54_58 but reports current_sectors == 0. The audit guest is virtio-only and has no such controller. If reachable, the impact is panic / local DoS (CVSS A:H). No memory-corruption primitive is derivable from a divide-by-zero trap.

Fix

fix.diff:

  1. Adds the && current_heads && current_sectors clause to the if at line 219 so the safe param.heads/param.sectors fallback is used when the current_* fields are bogus (matches ata-disk.c:469-470).
  2. Adds a final defensive if (heads == 0 || sectors == 0) { request->result = EIO; return; } immediately before the divisions, covering the (theoretical) case where the fallback param.sectors is also zero. The ata-queue caller (ata-queue.c:181,208,348,420) honors request->result and fails the I/O cleanly.

Supersedes the finding markdown's recommendation.

Phase-8 build validation

Combined kernel + modules build (DF-2068 / DF-2069 / DF-2070 / DF-2071 / DF-2072) on DragonFly 6.5-DEVELOPMENT #0 baseline:

  • === NK_DONE rc=0 === (2026-07-25 11:31:20 UTC)
  • 0 error: lines in the full 35,696-line build log
  • ata-serverworks.c is part of the in-kernel nata driver; the patched TU compiled clean with the kernel's default -Wall ... -Wno-pointer-sign -Werror flags and linked into the kernel binary (ata_serverworks_tf_read / ata_serverworks_tf_write confirmed present via nm on kernel.debug).

The default kernel build IS a -Werror build; see fix_build.log and env.txt.

Reproduce

./build.sh    # rebuilds the patched kernel (rc=0 with -Werror)
./run.sh      # source-only confirmation; no runtime PoC (HW-gated)

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: combined kernel build rc=0 -Werror

VALIDATED: combined kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

Source-confirmed: ata_serverworks_tf_write CHS branch divides by current_sectors without zero check. HW-gated (ServerWorks ATA).

Verified recommended fix

Source-confirmed: ata_serverworks_tf_write CHS branch divides by current_sectors without zero check. HW-gated (ServerWorks ATA).

Verdict

Source-confirmed: ata_serverworks_tf_write CHS branch divides by current_sectors without zero check. HW-gated (ServerWorks ATA).