amdgpu_atombios_get_clock_info() accepts usReferenceClock==0 with no fixup (unlike every sibling field) β ppll.reference_freq==0 guarantees amdgpu_pll_compute divide-by-zero at first modeset (the rs400/r100 'reference_freq==0' lead, confirmed)
Summary
The same function fixups every neighbouring degenerate field (pll_out_min->64800, max_pixel_clock->40000, ...) but copies usReferenceClock verbatim - den=0 in pll_compute traps at :63 on the first modeset with any nonzero clock; atombios_crtc.c:875 divides by reference_freq/100 too. Guest harness: SIGFPE 3/3, gdb-pinned, control clean. Producer-side zero->2700 fixup.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5005 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| pll_harness.c | β | 10.7 KB | view raw | |
| pll_harness_fixed.c | β | 10.8 KB | view raw | |
| build.sh | β | 99 B | view raw | |
| run.sh | β | 120 B | view raw | |
| build.log | β | 161 B | view raw | |
| run.log | β | 295 B | view raw | |
| gdb_lines.txt | β | 1.6 KB | view raw | |
| env.txt | β | 1.1 KB | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-5005 β amdgpu_atombios_get_clock_info() accepts usReferenceClock==0 β guaranteed amdgpu_pll_compute div0
sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:592-593:
ppll->reference_freq = le16_to_cpu(firmware_info->info.usReferenceClock);
is copied from the VBIOS FirmwareInfo table without any zero check, even
though the same function fixes up every neighbouring field that can be 0:
pll_out_min β 64800 (:610-611), lcd_pll_out_min/max β pll_out_* (:603-608),
spll/mpll pll_out_min β 64800 (:642-643, :669-670), max_pixel_clock β
40000 (:708-709). reference_freq is the odd one out β this is exactly the
"latent reference_freq==0 div0" lead from the rs400/r100 family.
The value is installed into ppll[0] and cloned into every ppll[1..]
(:628-629); every pixel-clock programming path then feeds it to
amdgpu_pll_compute() (atombios_crtc.c:835-854 β amdgpu_pll.c:187
den = pll->reference_freq). With reference_freq == 0:
amdgpu_pll_reduce_ratio():gcd(nom, 0) = nomβden = 0; the denominator-minimum fixup then executesDIV_ROUND_UP(den_min, 0)β divide by zero panic at amdgpu_pll.c:63 on the first modeset with any nonzero clock;- (if the mode clock is also 0:
gcd(0,0) = 0β*nom /= 0at :51); - sibling sink: spread-spectrum step-size math divides by
pll->reference_freq / 100(atombios_crtc.c:875-879) βreference_freq < 100gives another zero divisor on the same path.
Same for the spll/mpll copies (:632-633, :659-660: usCoreReferenceClock,
usMemoryReferenceClock) which feed the DPM PLL calculators.
Reproduce
Same harness as DF-5004 (verbatim amdgpu_pll.c function copies):
cc -g -O0 -Wall -o pll_harness pll_harness.c ./pll_harness ref0 # SIGFPE at amdgpu_pll.c:63 (gdb_lines.txt) ./pll_harness control # sanity: 27 MHz reference completes fine
gdb pins the trap to pll_harness.c:114 == amdgpu_pll.c:63
(tmp = DIV_ROUND_UP(den_min, *den) with den == 0, frame shows
nom_min=4, den_min=11 β den_min is post_div_min=ceil(64800/6000)=11,
proving the zero is the reference frequency, not the mode clock).
Threat model
The zero comes from the VBIOS/ATOM firmware image (corrupt or malicious
VBIOS, or a driver bug leaving the field unset when get_clock_info is
skipped), not from runtime user input β unlike DF-2037/DF-5004. Impact is a
deterministic kernel panic at first display configuration (firmware
robustness / secure-boot supply-chain angle), hence Low.
Fix
fix.diff β give reference_freq the same ==0 fixup its sibling fields
already get (27 MHz default for ppll; spll/mpll similarly), plus harden the
consumer (amdgpu_pll.c guards, DF-5004's fix.diff).
DF-5005 VERDICT β REPRODUCED (function-level, deterministic); firmware-gated producer
Finding: amdgpu_atombios_get_clock_info() (sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:592-593)
copies usReferenceClock from the VBIOS FirmwareInfo table into
ppll->reference_freq (and clones it to all ppll[1..3] at :628-629) with
no zero check, unlike every sibling field in the same function that gets
an explicit ==0 fixup (pll_out_min :610-611, lcd_pll_out_min/max
:603-608, spll->pll_out_min :642-643, mpll->pll_out_min :669-670,
max_pixel_clock :708-709). This answers the rs400/r100 audit lead:
yes, reference_freq can be 0 from the fw-info parse and it reaches
amdgpu_pll_compute (atombios_crtc.c:835-854 picks ppll[0..2] and passes
the adjusted clock; amdgpu_pll.c:187 sets den = pll->reference_freq).
Reproduction (guest)
Same harness as DF-5004 β verbatim copies of amdgpu_pll.c:44-251; the ref0
case sets reference_freq = 0 with a normal 600 MHz-class mode clock
(freq=60000) and the stock amdgpu_atombios.c:618-625 PLL limits:
case ref0 : freq=60000 ref=0 ... -> TRAPPED signal 8 (SIGFPE) case control (ref=2700) -> COMPLETED dot=14850 fb=880 ref=2 post=80
gdb (gdb_lines.txt): #0 amdgpu_pll_reduce_ratio (nom=..., den=...,
nom_min=4, den_min=11) at pll_harness.c:114 β pll_harness.c:114 is the
verbatim tmp = DIV_ROUND_UP(den_min, *den); i.e. amdgpu_pll.c:63, and
den_min=11 is post_div_min = ceil(64800/6000) = 11, proving the zero
divisor is the reference frequency (the mode clock is nonzero: 6000).
Determinism: 3/3 in the DF-5004 stress runs (pll_harness ref0 exit 42
every time), plus the dedicated run in run.log.
Why it panics
den = 0 β gcd(nom, 0) = nom (sys/dev/drm/include/linux/gcd.h: b == 0 β
return a) β after the division den = 0 survives β the denominator-minimum
fixup runs DIV_ROUND_UP(den_min, 0) β #DE trap β kernel panic at the first
display configuration after boot/resume. If the mode clock is also 0 the
first gcd itself is gcd(0,0) = 0 and *nom /= 0 traps at amdgpu_pll.c:51.
Sibling sink: atombios_crtc.c:875-879 divides the spread-spectrum step size
by pll->reference_freq / 100, so any firmware value < 100 is a second
zero divisor on the same call chain.
Impact / threat
The trigger is a corrupt or malicious VBIOS/ATOM image (or a code path that
leaves the field unset), not runtime attacker input β hence Low severity
(deterministic panic = firmware-robustness / supply-chain issue; no memory
corruption, no escalation). The consumer-side hardening is DF-5004's
fix.diff; the producer fix is this finding's fix.diff (same ==0 fixup
pattern the file already uses for its neighbours; 27 MHz default for
ppll/spll, 100 MHz for mpll).
Fix validation
Producer-level: applying fix.diff replaces the 0 with 2700 β exactly the
control input, which completes with sane dividers (run.log). Consumer
guards (DF-5004 fix.diff) additionally make the unfixed producer complete
without trapping (see DF-5004 fix_run.log, ref0 case). Kernel-level
rebuild validation not possible on this guest (no AMD GPU β env.txt).
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
fixedproducer fix maps reference_freq 0 -> 2700, which is exactly the control input that completes with sane dividers (run.log); consumer guards (DF-5004 fix.diff) additionally complete the unfixed-producer case (DF-5004 fix_run.log ref0). Kernel-level rebuild validation not possible on this guest (no AMD GPU).
run.log control case; DF-5004/fix_run.log ref0 case
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (ref0 SIGFPE, control completes dot=14850 fb=880 ref=2 post=80); gdb_lines.txt (#0 amdgpu_pll_reduce_ratio ... at pll_harness.c:114, den_min=11); DF-5004/run.2.log (3/3 determinism); env.txt (no AMD GPU on guest)
PoC changes
no seed; harness shared with DF-5004 (verbatim amdgpu_pll.c copies, ref0 case exercises this producer)
Verified recommended fix
amdgpu_atombios.c: if (ppll->reference_freq == 0) ppll->reference_freq = 2700; (and equivalents for spll/mpll) β mirrors the existing ==0 fixups in the same function
Verdict
REPRODUCED (function-level, deterministic, on-guest): amdgpu_atombios.c:592-593 installs usReferenceClock from the VBIOS into ppll->reference_freq (cloned to all ppll[1..] at :628-629) with NO zero check even though the same function fixups pll_out_min (:610-611), lcd_pll_out_* (:603-608), spll/mpll pll_out_min (:642/:669) and max_pixel_clock (:708-709). With reference_freq==0, amdgpu_pll_compute (reached from atombios_crtc.c:854 with ppll[0..2]) sets den=0 (amdgpu_pll.c:187); gcd(nom,0)=nom leaves den=0 and DIV_ROUND_UP(den_min,0) traps at amdgpu_pll.c:63 β guest harness SIGFPE 3/3, gdb pins harness:114==amdgpu_pll.c:63 with den_min=11 (post_div_min for the 600MHz input), proving the zero divisor is the reference clock. gcd(0,0)=0 additionally traps :51 if the mode clock is also 0; atombios_crtc.c:875-879 (ss step size / (ref_freq/100)) is a sibling zero-divisor for ref<100. Gated on corrupt/malicious VBIOS (or skipped get_clock_info) rather than runtime user input -> Low severity, deterministic panic at first modeset, no corruption/escalation.
No comments yet.