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

Wild OOB kernel read in btc_find_voltage when a VBIOS voltage object has zero LUT entries: entries[0xFFFFFFFF] β‰ˆ +32 GiB past eg_pi (btc twin of DF-3649/3956)

Summary

radeon_atom_get_voltage_table returns SUCCESS with count==0 for a zero-entry V2/V3 voltage object; cypress_construct_voltage_tables only trims counts>32; btc_find_voltage then evaluates entries[count-1] with u32 count==0 -> index 0xFFFFFFFF x 8-byte entries -> wild read -> panic during the first power-state change. Sibling guards exist in cypress but btc forgot them. Guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3974 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
verdict.json β€” 2.2 KB view raw
manifest.json β€” 747 B view raw
fix.diff β€” 355 B view raw

DF-3974 β€” Wild OOB kernel read in btc_find_voltage when a VBIOS voltage object has zero LUT entries (count==0 β†’ entries[0xFFFFFFFF], +32 GiB offset)

  • File: sys/dev/drm/radeon/btc_dpm.c:1293-1303 (sink), reached via btc_apply_voltage_delta_rules (btc_dpm.c:1305-1328) from btc_apply_state_adjust_rules (2233-2238)
  • Class: CWE-125/CWE-191 (Medium) β€” family: DF-3649 (ci CAC count-0), DF-3956 (kv vid_mapping count-0). This is the btc empty-voltage-table twin.

Build / Run / Expected

Hardware-gated (Barts/Turks/Caicos + VBIOS whose VDDCI or VDDC voltage object carries a zero entry count). Panic (page fault on unmapped read) or silent garbage voltage pick.

Root cause

  1. radeon_atom_get_voltage_table() β€” sys/dev/drm/radeon/radeon_atombios.c:3712-3805 β€” returns 0 (success) with voltage_table->count == 0 when the V2 object's ucNumOfVoltageEntries == 0 (loop skipped, count assigned at 3758) or the V3 GPIO object's ucGpioEntryNum == 0 (3789).
  2. cypress_construct_voltage_tables() β€” cypress_dpm.c:1487-1513 β€” only trims when count > MAX_NO_VREG_STEPS; count==0 passes through as success.
  3. btc_find_voltage() β€” btc_dpm.c:1293: c for (i = 0; i < table->count; i++) { ... } return table->entries[table->count - 1].value; /* count==0 β†’ entries[0xFFFFFFFF] */ count is u32 (radeon_mode.h:681); entries is entries[32] of 8-byte entries (radeon_mode.h:671-685). Index 0xFFFFFFFF zero-extends β†’ byte offset 0x7_FFFF_FFF8 (~32 GiB) past eg_pi->vddci_voltage_table (embedded in the kmalloc'd evergreen_power_info) β†’ wild read β†’ page fault β†’ panic; if something is somehow mapped there, an unrelated 16-bit value becomes the programmed VDDCI (hardware misregulation).
  4. Reachability: btc_apply_voltage_delta_rules (1305) is called on every btc_dpm_pre_set_power_state β†’ btc_apply_state_adjust_rules (2233-2238) with the parsed non-zero vddc/vddci values; the guard at 1312 only rejects zero voltages, not zero table counts. The vddci branch (1315-1320) fires when *vddc - *vddci > vddc_vddci_delta with an empty vddci table; the vddc branch (1321-1326) symmetrically.

Note cypress_populate_smc_voltage_tables (cypress_dpm.c:1534/1552) does guard with if (...count) β€” the btc path simply forgot the same guard.

VERDICT.md
↓ download raw

VERDICT β€” DF-3974

status: inconclusive (hardware-gated; source-level certainty: certain)

Why

Same reasoning as DF-3972: the guest has no radeon GPU (env.txt), and the zero-entry voltage object must be present in the card's VBIOS VoltageObjectInfo table. The code path, however, is fully deterministic once such a VBIOS is parsed:

radeon_dpm_enable (btc_dpm.c:2373)
 └─ 2392-2399 cypress_construct_voltage_tables
     └─ radeon_atom_get_voltage_table(VDDCI)  β†’ ret 0, count = 0     [radeon_atombios.c:3789]
        (radeon_atom_get_voltage_table succeeds with zero entries; only entry-count > 32 rejected)
...first power-state change:
radeon_pm_compute_clocks β†’ radeon_dpm_change_power_state_locked (radeon_pm.c:1137)
 └─ btc_dpm_pre_set_power_state (btc_dpm.c:2287)
     └─ btc_apply_state_adjust_rules (btc_dpm.c:2092)
         └─ 2233 btc_apply_voltage_delta_rules
             └─ 1315-1319 *vddc - *vddci > vddc_vddci_delta (200, hardcoded at 2696)
                 └─ btc_find_voltage(&eg_pi->vddci_voltage_table /* count==0 */, ...)  [1317]
                     └─ 1302 return table->entries[0xFFFFFFFF].value
                         β†’ load at eg_pi + offsetof(vddci_voltage_table) + 0x7FFFFFFF8 β†’ #PF

The u32 count type (radeon_mode.h:681) makes count - 1 = 0xFFFFFFFF; the 8-byte entry stride (radeon_mode.h:673-677) puts the faulting address ~32 GiB past the allocation β€” a wild read that cannot land on anything mapped in the kernel's direct map on this guest's 4 GiB RAM, i.e. a guaranteed panic rather than a lucky garbage pick. Exactly the DF-3649/DF-3956 family shape.

Preconditions beyond hardware: pi->voltage_control true (a VDDC GPIO voltage object must exist β€” 2392 gates the whole construct on it) with β‰₯1 entries, and eg_pi->vddci_control true (btc_dpm.c:2636) with a VDDCI object whose entry count field is 0 β€” all plausible in a crafted ROM.

Fix validation

not_testable (no hardware). fix.diff: early-out on count==0 in btc_find_voltage (mirror of the if (count) guards used in cypress_populate_smc_voltage_tables).

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_testable

fix.diff authored (count==0 early-out); not build-testable without a Barts-class GPU.

findings/poc/DF-3974/fix.diff
↓ fix.diffper-fix-DF-3974

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md deterministic trace; radeon_mode.h:671-685 (u32 count, entries[32], 8-byte entries); the sibling guards at cypress_dpm.c:1534/1552 prove the intended pattern

PoC changes

No PoC: hardware-gated (crafted VBIOS voltage object on Barts-class hardware).

Verified recommended fix

Guard table->count == 0 in btc_find_voltage (return voltage unchanged), mirroring the count guards in cypress_populate_smc_voltage_tables.

Verdict

btc_find_voltage (btc_dpm.c:1293-1303) indexes entries[count-1] with a u32 count; radeon_atom_get_voltage_table returns success with count==0 for a VBIOS voltage object whose LUT entry count is zero (radeon_atombios.c:3758/3789), and cypress_construct_voltage_tables only trims counts > 32 β€” so a crafted VDDCI/VDDC object yields entries[0xFFFFFFFF] β‰ˆ +32 GiB wild read β†’ kernel page fault during the first power-state change (btc_apply_state_adjust_rules β†’ btc_apply_voltage_delta_rules, btc_dpm.c:2233/1317). Hardware-gated: no radeon GPU in the guest, so inconclusive-by-hardware with source-level certainty; exact twin of DF-3649/DF-3956.