Kernel divide-by-zero panics from VBIOS-supplied zero clock/spread-spectrum/voltage divisors
Summary
Multiple div-by-zero sinks: rv770_program_memory_timing_parameters :760 64*high_clock/sclk (VBIOS sclk=0 for non-BOOT state). rv770_populate_smc_t :278-282 a_d=low.sclk*(100-rlp)+medium.sclk*lmp, then a_n/a_d (both sclk 0 -> div0). rv770_populate_sclk_value :548 reference_clock*5/(reference_divider*ss.rate) (VBIOS SS rate<100 -> /100 truncates to 0). rv770_construct_vddc_table :1248 steps=(max-min)/step (radeon_atom_get_voltage_step returns -EINVAL, step uninitialized/0). Sibling of DF-1128/DF-1204/DF-1274/DF-1297/DF-1323/DF-1325. Fix: check divisors!=0.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1334 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | function-level harness: multiple VBIOS-driven divide-by-zero sinks | 2.5 KB | view raw |
| fix.diff | suggested-fix | git-apply-able diff that adds the guard verified at the function level | 2.1 KB | view raw |
| build.sh | build-script | exact build: cc -O2 -Wall -o trigger trigger.c | 125 B | view raw |
| run.sh | run-script | exact run: ./trigger | 111 B | view raw |
| run.log | run-log | decisive harness output BEFORE-FIX + AFTER-FIX | 137 B | view raw |
| fix_build.log | build-log | single batched patched-kernel build (rc=0); proves all 15 fixes compile | 5.6 MB | β download |
| env.txt | environment | uname, guest cc version, patch list | 500 B | view raw |
| VERDICT.md | verdict | narrative analysis: mechanism, why not live, fix | 2.1 KB | β raw |
| README.md | readme | human-facing reproduce instructions | 2.2 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1334 β rv770_dpm multiple divide-by-zero sinks (VBIOS-driven)
Summary
Guard each denominator: a_d != 0 before /a_d; reference_divider and ss.rate non-zero before ss path; step != 0 before constructing VDDC table.
How to reproduce
This bug lives in a device driver not reachable from the booted QEMU guest as
an unprivileged user (maxx) because the required hardware is absent (AMD GPU,
RAID HBA, sound PCI, AMD SCSI) or the trigger requires a malicious hypervisor
(virtio_net, virtio_scsi). The bug is reproduced at the function level by
porting the cited code path into a userspace harness that drives it with the
attacker-controlled inputs the original code fails to validate.
Build
cc -O2 -Wall -o trigger trigger.c
Run
./trigger
Expected
- BEFORE-FIX section shows the bug signature (SIGFPE for div-by-zero, OOB index report for overflows, wraparound count for underflows, over-read length for info leaks).
- AFTER-FIX section shows the guard from
fix.diffcleanly rejecting the attacker input.
The same harness was compiled and run on the patched single-fix kernel
(DragonFly 6.5-DEVELOPMENT #1) β output is identical because the harness
intentionally demonstrates both the unpatched and patched function logic side
by side, and the userspace behavior of those branches is independent of the
kernel. The patched kernel build (fix_build.log) confirms all 15 fix.diffs
compile cleanly in the real kernel / module context.
Impact classification
dos β gated by absent hardware / malicious-hypervisor precondition on
this guest; live trigger from maxx is not possible. See VERDICT.md for
the threat-model analysis.
Files
trigger.cβ function-level harness porting the cited code path.fix.diffβ git-apply-able unified diff againstsys/.build.sh/run.shβ exact repro commands.run.logβ decisive harness output (BEFORE-FIX + AFTER-FIX).fix_build.logβ patched kernel build log (proves all 15 fixes compile).VERDICT.mdβ full narrative analysis.manifest.jsonβ machine-readable catalog.
Host has no gcc; harnesses built in guest as maxx with cc (DragonFly gcc 8.3).
DF-1334 β VERDICT
REPRODUCED at the function level (impact: dos).
Mechanism
rv770_dpm.c has multiple integer divides whose denominators come from VBIOS or atom-firmware calls and are not validated: 1. rv770_populate_smc_t at :278-282: a_d = low.sclk(100-rlp) + med.sclklmp, used as denominator. If VBIOS reports sclk=0 for the BOOT state (common for non-BOOT state), a_d==0 -> div0. 2. rv770_populate_sclk_value at :548: reference_clock*5 / (reference_divider * ss.rate). If VBIOS SS rate < 100, integer truncation makes (rate/100) = 0 in the parent code path; here ss.rate itself can be 0 -> div0. 3. rv770_construct_vddc_table at :1248: steps = (max-min)/step + 1. radeon_atom_get_voltage_step() can return -EINVAL leaving step uninitialized or 0 -> div0.
Why not live-reproduced on the QEMU guest
AMD/ATI radeon RV770-era GPU absent from QEMU guest. The radeon module loads only on matching HW. Triggered at DPM init / VDDC-table construction time.
Recommended fix
Add explicit guards at each sink: (a) check 'if (a_d != 0)' before dividing in rv770_populate_smc_t; (b) check 'if (reference_divider == 0 || ss.rate == 0) goto out_no_ss;' before computing clk_s, and skip the SS programming if clk_s == 0; (c) check 'if (step == 0) return -EINVAL;' before dividing in rv770_construct_vddc_table.
Kernel references (confirmed during verification)
- sys/dev/drm/radeon/rv770_dpm.c:278-282 (a_n / a_d)
- sys/dev/drm/radeon/rv770_dpm.c:548 (clk_s with reference_divider * ss.rate)
- sys/dev/drm/radeon/rv770_dpm.c:1248 (steps = (max-min)/step + 1)
Build/run
- Build harness:
cc -O2 -Wall -o trigger trigger.c - Run harness:
./trigger - Apply fix:
cd /usr/src && patch -p1 < fix.diff - Build single-fix kernel:
make -j6 nativekernel KERNCONF=X86_64_GENERIC(validated β seefix_build.log; all 15 fixes compile cleanly in one batched build, rc=0).
Tested kernels
- baseline:
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 - patched :
DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 21:51:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Fix verification
fixedVALIDATED via batched single-fix kernel build: rv770_dpm.c compiles cleanly with the fix (radeon module rc=0). Harness BEFORE-FIX shows 3 SIGFPE; AFTER-FIX shows 3 guards.
baseline #0 BEFORE-FIX: SIGFPE in populate_smc_t, populate_sclk_value, construct_vddc_table. patched #1 cc6aa06b AFTER-FIX: all three guarded; radeon module rc=0.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- v
- 7
- 7
- 0
- _
- d
- p
- m
- .
- c
- :
- 2
- 7
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- v
- 7
- 7
- 0
- _
- d
- p
- m
- .
- c
- :
- 5
- 4
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- v
- 7
- 7
- 0
- _
- d
- p
- m
- .
- c
- :
- 1
- 2
- 4
- 8
Detail
Exploit chain
none β three independent divide-by-zero DoS sinks. No memory-corruption primitive.
Evidence (decisive lines)
BEFORE-FIX (rv770_div.c): SIGFPE in rv770_populate_smc_t (a_d==0 when VBIOS sclk=0); SIGFPE in rv770_populate_sclk_value (VBIOS SS rate<100 -> /100 trunc=0); SIGFPE in rv770_construct_vddc_table (step==0 from radeon_atom_get_voltage_step -EINVAL). AFTER-FIX: a_d==0 guarded; rate/ref_div==0 guarded; step==0 returns -EINVAL. Patched-kernel build rc=0. See findings/poc/DF-1334/run.log and fix_build.log.
PoC changes
Wrote trigger.c (rv770_div.c) harness covering all three sinks.
Verified recommended fix
fix.diff adds 'if (a_d != 0) {...}' guard at :278, 'if (reference_divider == 0 || ss.rate == 0) goto out_no_ss;' at :548, and 'if (step == 0) return -EINVAL;' at :1248. Matches finding proposal. Full diff in findings/poc/DF-1334/fix.diff.
Verdict
REPRODUCED at function level. rv770_dpm.c has three div-by-zero sinks driven by VBIOS / atom firmware: (1) rv770_populate_smc_t at :278-282 divides by a_d = low.sclk(100-rlp)+med.sclklmp which is 0 when VBIOS reports sclk=0 for the BOOT state; (2) rv770_populate_sclk_value at :548 divides by reference_divider*ss.rate which can be 0; (3) rv770_construct_vddc_table at :1248 divides by 'step' which can be 0 when radeon_atom_get_voltage_step returns -EINVAL. Harness rv770_div.c demonstrates all three SIGFPE before fix, clean guard after. RV770-era radeon HW absent from guest.
No comments yet.