Divide-by-zero in XPT_CALC_GEOMETRY from unvalidated block_size (local DoS / panic)
Summary
XPT_CALC_GEOMETRY handler computes size_mb=ccg->volume_size/((1024L*1024L)/ccg->block_size) with no check block_size non-zero or inner division non-zero. block_size fed directly from SCSI READ CAPACITY response (scsi_da.c:1831 scsi_4btoul(rdcap->length)) no validation or set arbitrarily by pass(4) CAMIOCOMMAND. block_size==0 inner division by-zero. block_size>1MB inner quotient 0 outer division by-zero. Either panics kernel.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2429 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| dfbt_rig.c | β | 26.9 KB | view raw | |
| dfbt_ctl.c | β | 8.0 KB | view raw | |
| Makefile | β | 127 B | β download | |
| push_rig.sh | β | 975 B | view raw | |
| build.sh | β | 185 B | view raw | |
| run.sh | β | 90 B | view raw | |
| run.log | β | 362 B | view raw | |
| panic.txt | β | 2.3 KB | view raw | |
| env.txt | β | 249 B | view raw | |
| fix.diff | β | 6.0 KB | view raw | |
| manifest.json | β | 779 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-2429 β Divide-by-zero in bt(4) XPT_CALC_GEOMETRY from device-fed block_size
Verdict: REPRODUCED (kernel panic via the production handler)
The QEMU guest has no BusLogic MultiMaster HBA, so the compiled-in bt(4)
driver (bt at X86_64_GENERIC:102; btaction/btdone/btreset/
bttimeout present in /boot/kernel/kernel) never attaches. The rig
(dfbt_rig.c) stands in only for the missing PCI hardware:
- resolves the REAL static production handlers from the running kernel
image (no KASLR; host-side
nm+ runtime prologue byte-verification of all six handlers before any dispatch); - registers a REAL
cam_simwhosesim_actionIS productionbtaction, over a fabricated post-attach softc (real lockmgr lock, real bus_dma tags, real DMA memory for CCBs/mailboxes); - drives the REAL CAM XPT layer with
xpt_action()on realxpt_alloc_ccb'd CCBs; - scripts a 4-byte virtual register file behind
bt->port(X86_64_BUS_SPACE_MEMtag β plain volatile memory, see sys/cpu/x86_64/include/bus_dma.h:134-142).
Build
./push_rig.sh # scp sources, build kld + ctl, kldload, prime # then verify prologue magic words against the expected constants
Run
sh run.sh # = run_df2429.sh
Expected output
calcgeom 0 4194304survives: heads=0 spt=0 cyl=0 status=0x1 β the CAM layer filtersblock_size == 0at cam_xpt.c:3063-3071, so the ==0 variant NEVER reaches bt.c (partial false-positive in the original finding; documented in VERDICT.md).calcgeom 2097152 4194304(block_size = 2 MiB > 1 MiB): the filter passes (both fields non-zero), productionbtaction's XPT_CALC_GEOMETRY case computes(1024*1024)/block_size == 0and then dividesvolume_sizeby it (bt.c:1157-1158) β Fatal trap 18: integer divide fault,Stopped at btaction+0x4ff: divq %rsi,%eax(see panic.txt).
Threat model
ccg->block_size is fed unvalidated from SCSI READ CAPACITY
(scsi_da.c:1831 scsi_4btoul(rdcap->length) β scsi_da.c:2302
ccg->block_size = dp->secsize), so a lying/hostile SCSI target behind
a real bt(4) HBA reporting secsize > 1 MiB panics the kernel (local DoS
from device-supplied data).
DF-2429 VERDICT β XPT_CALC_GEOMETRY divide-by-zero in bt(4)
Bottom line
REPRODUCED β kernel panic (Fatal trap 18, integer divide fault) inside
the production btaction handler, dispatched through the real CAM XPT
layer. One honest correction to the finding: the block_size == 0
variant named in the summary never reaches bt.c, because the CAM layer
filters it (cam_xpt.c:3063-3071, "Filter out garbage"). The second
variant β block_size > 1 MiB, which zeroes the inner quotient
(1024*1024)/block_size and then divides volume_size by zero β passes
that filter and panics exactly as claimed.
How it was reproduced
Rig = dfbt_rig.c (details in README.md): real production btaction
resolved from /boot/kernel/kernel (no KASLR; prologue bytes verified),
real registered cam_sim + real xpt_action dispatch over a fabricated
post-attach softc. xpt_action(XPT_CALC_GEOMETRY) calls
sim->sim_action directly (cam_xpt.c:3073), so the CCB reaches
production btaction unmodified.
Run (run.log, panic.txt):
$R calcgeom 0 4194304 [rig] CALCGEOM: xpt_action(block_size=0 volume_size=4194304) [rig] CALCGEOM survived: heads=0 spt=0 cyl=0 status=0x1 <- CAM filter $R calcgeom 2097152 4194304 [rig] CALCGEOM: xpt_action(block_size=2097152 volume_size=4194304) Fatal trap 18: integer divide fault while in kernel mode instruction pointer = 0x8:0xffffffff80430bcf kernel: type 18 trap, code=0 Stopped at btaction+0x4ff: divq %rsi,%eax
btaction+0x4ff disassembles to mov %rcx,%rax; divq %rsi;
cmp $0x3ff,%eax β the size_mb = volume_size / ((1024*1024)/block_size)
computation at bt.c:1157-1158 followed by the size_mb >= 1024 test at
bt.c:1160. The divisor %rsi holds (1024*1024)/2097152 == 0.
Root cause (path:line)
- sys/dev/disk/buslogic/bt.c:1157-1158 β division by a device-fed
block_size-derived value with no zero check in the driver. - sys/bus/cam/cam_xpt.c:3063-3071 β the CAM-layer filter covers only
block_size == 0/volume_size == 0, notblock_size > 1 MiB. - Feed path: sys/bus/cam/scsi/scsi_da.c:1831 (READ CAPACITY length β
block_size) β scsi_da.c:2302 (ccg->block_size = dp->secsize).
Impact
Kernel panic (local DoS) from a hostile/lying SCSI target's READ CAPACITY data on real bt(4) hardware. Precondition: a BusLogic MultiMaster HBA with an attached target reporting secsize > 1 MiB (pass(4) CAMIOCOMMAND with a crafted CCB reaches the same handler on any CAM bus whose SIM is bt(4)).
Why the ==0 variant is filtered (negative result)
xpt_action(XPT_CALC_GEOMETRY) at cam_xpt.c:3062-3071 zeroes the
geometry and completes with CAM_REQ_CMP when block_size == 0 β the
demonstrated calcgeom 0 4194304 survived with status 0x1 (run.log).
A direct btaction dispatch with block_size == 0 would still divide by
zero (same bt.c:1157 line), but no in-tree caller can deliver it through
the XPT.
Fix validation
fix.diff guards block_size == 0 || block_size > (1024*1024) with
CAM_REQ_INVALID before the division. The patched bt.c + btreg.h were
compiled with the X86_64_GENERIC kernel's own flags and headers on the
guest: compiles clean under -Werror (FIXED-COMPILES-CLEAN,
bt.fixed.o produced). No runtime A/B kernel build: this is a panic
class finding, not memory corruption, so a fix kernel rebuild is not
mandated by the audit contract; the fix is a two-line guard directly on
the proven faulting line.
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
not_testableobject-compile validation only (panic class, not memory corruption; kernel A/B not mandated): patched bt.c+btreg.h compile clean with the X86_64_GENERIC kernel flags
fix.diff; guest build log line 'FIXED-COMPILES-CLEAN' with bt.fixed.o produced (cc -Werror, kernel headers)
Confirmed kernel references
Detail
Exploit chain
lying SCSI target reports secsize>1MiB in READ CAPACITY -> da(4) dasetgeom -> xpt_action(XPT_CALC_GEOMETRY) (filter passes, both fields nonzero) -> production btaction bt.c:1157 computes (1024*1024)/2097152==0 then volume_size/0 -> #DE -> kernel panic
Evidence (decisive lines)
panic.txt (Fatal trap 18, Stopped at btaction+0x4ff: divq %rsi,%eax; disassembly context in VERDICT.md); run.log (variant 1 block_size=0 survives with heads=0 status=0x1 proving the CAM filter; variant 2 panics)
PoC changes
original finding cited block_size==0 as the trigger; the CAM layer filters that case (proven live), so the PoC uses block_size=2097152 (>1MiB) which zeroes the inner quotient -- same division, same line, actually reachable
Verified recommended fix
Reject block_size==0 or >1MiB with CAM_REQ_INVALID before the division (fix.diff, object-compile validated under -Werror with kernel headers)
Verdict
REPRODUCED: production btaction XPT_CALC_GEOMETRY handler panics the kernel (Fatal trap 18 integer divide fault at btaction+0x4ff, the bt.c:1157 size_mb division) when xpt_action delivers block_size=2MiB (device-fed READ CAPACITY value that passes the CAM-layer block_size==0/volume_size==0 filter at cam_xpt.c:3063-3071). Honest correction: the block_size==0 variant cited first in the finding never reaches bt.c -- the CAM filter completes it with CAM_REQ_CMP (demonstrated live); only the >1MiB variant (inner quotient 0) panics. Local DoS/panic from hostile SCSI target data on real bt(4) hardware.
No comments yet.