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

Heap OOB write past SISLANDS_SMC_STATETABLE.lowSMIO[32] via untrimmed SVI2 VDDCI voltage-table count in si_populate_smc_voltage_table β€” radeon twin of the lowSMIO half of amdgpu DF-3585

Summary

Mixed-VRM crafted VBIOS (VDDC GPIO_LUT with VDDCI SVID2, no VDDCI GPIO): si_construct_voltage_tables sets vddci_voltage_table.count = vddci_dependency_on_mclk.count (u8, up to 255) with NO trim (GPIO paths trim). Because voltage_control_svi2 is false, the else branch calls si_populate_smc_voltage_table, whose loop OR-writes table->lowSMIO[i] for i<count into the packed 3012-byte SISLANDS_SMC_STATETABLE embedded in the kzalloc'd si_power_info (lowSMIO is uint32[32]). count 33..255 writes up to 1020 bytes past lowSMIO[0], corrupting voltageMaskTable, phaseMaskTable, dpm2Params, the four SWSTATEs and dpmLevels - data uploaded verbatim to the GPU power-management microcontroller (wrong voltage masks/TDP limits/state clocks; under/overvoltage risk). Overflow contained within the single si_power_info allocation (max touched offset 1087 vs 3012) - hence Medium. The entries[] write of the same root cause is pass-1 DF-1136; this sink survives an entries-only clamp. VERIFIED: verbatim packed struct replicas; gcc itself warns 'iteration 32 invokes undefined behavior'; [A] lowSMIO[32]=0xa5a55a5a (OOB start) with voltageMaskTable/phaseMaskTable/dpm2Params.TDPLimit/initialState all corrupted; fixed-path [B] tail intact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3614 Β· 12 files
FileTypeDescriptionSize
harness.c β€” 10.4 KB view raw
build.sh β€” 66 B view raw
run.sh β€” 90 B view raw
build.log β€” 1.0 KB view raw
run.log β€” 1.5 KB view raw
fix.diff β€” 315 B view raw
fix_build.log β€” 1.6 KB view raw
fix_run.log β€” 250 B view raw
VERDICT.md β€” 4.6 KB ↓ raw
README.md β€” 2.2 KB ↓ raw
manifest.json β€” 844 B view raw
verdict.json β€” 4.3 KB view raw

DF-3614 β€” Heap OOB write past SISLANDS_SMC_STATETABLE.lowSMIO[32] via untrimmed SVI2 VDDCI voltage-table count in si_populate_smc_voltage_table

Severity: Medium Confidence: certain Status: reproduced (harness) Impact: dos

What

Mixed-VRM VBIOS: VDDC via GPIO_LUT (voltage_control true) + VDDCI via SVID2 (vddci_control false, vddci_control_svi2 true). si_construct_voltage_tables:3998-4004 fills vddci_voltage_table.count = vddci_dependency_on_mclk.count (u8, up to 255) with NO trim (the GPIO paths trim at :3973/:3993/:4020). si_populate_smc_voltage_tables then takes the non-SVI2-VDDC else branch (:4058 false since VDDC is GPIO) and at :4079-4080 calls si_populate_smc_voltage_table, whose loop at :4046 OR-writes table->lowSMIO[i] for i<count into the packed STATETABLE embedded in the kzalloc'd si_power_info (si_dpm.h:196). count 33..255 writes up to 1020 bytes past lowSMIO[0] (struct is 3012 B; overflow is contained within the si_power_info allocation) corrupting voltageMaskTable, phaseMaskTable, dpm2Params, initialState/ACPIState/ULVState/driverState and dpmLevels β€” data that si_init_smc_table uploads verbatim to the GPU SMC at :4779-4781, programming wrong voltage masks / TDP limits / state clocks (hardware misconfiguration, undervoltage/overvoltage risk). Harness with verbatim packed struct replicas from sislands_smc.h confirms the exact corrupted fields; gcc itself flags the replica loop (-Warray-bounds: 'iteration 32 invokes undefined behavior').

Reproduce

sh build.sh   # cc -O2 -Wall -o harness harness.c
sh run.sh     # prints [A] buggy-path behaviour, [B] fixed-path behaviour

Expected decisive output (see run.log): the [A] section shows the fault or corruption the kernel would take; the [B] section shows the behaviour with fix.diff applied.

No AMD GPU is attached to the audit QEMU guest (virtio-only), so the radeon DPM path cannot be exercised live; the harness replicates the exact kernel expressions with crafted-VBIOS inputs (house methodology, cf. DF-1137..1140).

Fix

fix.diff β€” validated: git apply --check clean; make si_dpm.o in sys/dev/drm/radeon with KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC compiles -Werror clean with all five pass-2 fixes stacked; harness [B] shows the primitive gone.

VERDICT.md
↓ download raw

DF-3614 β€” Verdict

Verdict: REPRODUCED (source-level + harness) β€” OOB write past lowSMIO[32], confined to the si_power_info allocation; corrupts SMC-bound power configuration

radeon twin of the lowSMIO sink half of amdgpu DF-3585. The entries[] half of the same root cause is pass-1 DF-1136; this sink is a distinct write primitive that survives an entries-only clamp.

Bug confirmation

Reachability β€” all three flags are VBIOS-derived (si_dpm_init):

si_dpm.c:7001-7003  pi->voltage_control      = VDDC advertises GPIO_LUT  (true)
si_dpm.c:7017-7023  eg_pi->vddci_control     = VDDCI GPIO_LUT            (false)
                     si_pi->vddci_control_svi2= VDDCI SVID2               (true)
si_dpm.c:7004-7011  si_pi->voltage_control_svi2 = VDDC SVID2              (false)

Data flow:

si_dpm.c:3967-3976  VDDC GPIO table fetched AND TRIMMED to 32
si_dpm.c:3998-4004  vddci_control_svi2 -> si_get_svi2_voltage_table(
                         &dyn_state.vddci_dependency_on_mclk, &vddci_voltage_table)
                     -> vddci_voltage_table.count = dependency count (u8, <=255)
                        NO TRIM (GPIO paths trim at :3973/:3993/:4020)
                     [entries[] overflow there == DF-1136; the COUNT survives]
si_dpm.c:4058       if (si_pi->voltage_control_svi2) { ... } else {        // else taken:
si_dpm.c:4079           if (eg_pi->vddci_voltage_table.count) {
si_dpm.c:4080               si_populate_smc_voltage_table(rdev,
                                    &eg_pi->vddci_voltage_table, table);
si_dpm.c:4046-4047           for (i = 0; i < voltage_table->count; i++)
                                 table->lowSMIO[i] |= cpu_to_be32(entries[i].smio_low);

lowSMIO is uint32_t[32] (sislands_smc.h:207/215) inside the packed 3012-byte SISLANDS_SMC_STATETABLE, embedded in the kzalloc'd si_power_info (si_dpm.h:196). With count 33..255 the OR-write walks up to 1020 bytes past lowSMIO[0], corrupting voltageMaskTable, phaseMaskTable, dpm2Params, the four SWSTATEs and dpmLevels[] β€” everything that si_init_smc_table uploads to the GPU power-management microcontroller at si_dpm.c:4779-4781 (si_copy_bytes_to_smc(... sizeof (SISLANDS_SMC_STATETABLE) ...)). Consequence: wrong VDDC/VDDCI/MVDD masks, wrong TDP limits, wrong state clocks programmed on the GPU β€” miscalibrated voltages (undervoltage instability / overvoltage stress) from a crafted VBIOS. The overflow is CONTAINED within the single si_power_info allocation (max touched offset 1087 vs struct size 3012+), so no cross-object kernel heap corruption β€” hence Medium, not High.

Harness confirmation

run.log (verbatim packed struct replicas from sislands_smc.h):

sizeof(SISLANDS_SMC_STATETABLE) = 3012; lowSMIO spans 4..132
last byte touched by buggy loop: lowSMIO[254] at offset 1020
[A] lowSMIO[32]=0xa5a55a5a (OOB start)
    voltageMaskTable.lowMask[0] (VDDC mask) = 0xa5a55a5a  CORRUPTED
    phaseMaskTable.lowMask[0]                = 0xa5a55a5a  CORRUPTED
    dpm2Params.TDPLimit                      = 0xa5a55a5a  CORRUPTED
    initialState.levels[0].sclk.sclk_value   = 0xa5a55a5a  CORRUPTED
    first corrupted byte at allocation offset 68; last touched offset 1087
[B] lowSMIO[32]=0x00000000 (untouched) ... dpm2Params.TDPLimit intact

GCC independently flags the replica loop: warning: iteration 32 invokes undefined behavior [-Warray-bounds] / array subscript 32 is above array bounds of 'uint32[32]' β€” the compiler's own confirmation that i>=32 is out of bounds.

Trigger conditions (not met on this guest)

SI GPU + radeon DPM (virtio-only guest) + mixed-VRM VBIOS (VDDC GPIO_LUT, VDDCI SVID2) with vddci_dependency_on_mclk.ucNumEntries > 32.

Exploit chain

Contained corruption β€” no cross-object primitive, no kernel-memory info leak; the attacker-visible effect is tampered GPU power config (hardware risk), so impact is DoS-grade for the kernel itself.

Fix

fix.diff β€” bound the sink loop:

for (i = 0; i < voltage_table->count && i < SISLANDS_MAX_NO_VREG_STEPS; i++)

(defense-in-depth at the sink; independently recommended alongside the DF-1136 root-cause clamp in si_get_svi2_voltage_table). Validation: apply-check OK; make si_dpm.o -Werror clean (stacked); harness [B] shows the statetable tail intact after the fix.

fix_status: fixed (compile + harness-level).

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

fixed
baseline reproduced→ patch + rebuild →patched clean

git apply --check OK (standalone and stacked with the other four si_dpm.c fixes); targeted module TU compile make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC si_dpm.o => rc=0, -Werror clean (87080 B). Harness fixed-path shows the primitive gone (fix.diff bounds the sink loop: for (i = 0; i < voltage_table->count && i < SISLANDS_MAX_NO_VREG_STEPS; i++). git apply --check OK; stacked module TU compile -Werror clean; harness [B] shows the statetable tail intact.)

fix.diff bounds the sink loop: `for (i = 0; i < voltage_table->count && i < SISLANDS_MAX_NO_VREG_STEPS; i++)`. git apply --check OK; stacked module TU compile -Werror clean; harness [B] shows the statetable tail intact.
↓ fix.diffper-fix-DF-3614

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS: VDDC GPIO_LUT + VDDCI SVID2 + vddci_dependency_on_mclk.ucNumEntries>32 -> si_get_svi2_voltage_table sets count untrimmed -> si_populate_smc_voltage_tables else-branch -> si_populate_smc_voltage_table loop -> lowSMIO[32..254] |= value -> SMC statetable fields corrupted -> uploaded to GPU power-management microcontroller (wrong voltage masks/TDP/clocks)

Evidence (decisive lines)

run.log [A]: 'lowSMIO[32]=0xa5a55a5a (OOB start)'; 'voltageMaskTable.lowMask[0] (VDDC mask) = 0xa5a55a5a CORRUPTED'; 'dpm2Params.TDPLimit = 0xa5a55a5a CORRUPTED'; 'initialState.levels[0].sclk.sclk_value = 0xa5a55a5a CORRUPTED'; 'last touched offset 1087' inside 3012-byte STATETABLE; [B] fixed: 'lowSMIO[32]=0x00000000 (untouched)'; build warnings: 'iteration 32 invokes undefined behavior' [-Warray-bounds]

PoC changes

n/a (pass-2 finding authored with its harness; no seeded PoC to fix)

Verified recommended fix

see fix.diff

Verdict

radeon twin of the lowSMIO half of amdgpu DF-3585 (the entries[] half is pass-1 DF-1136; both stem from the untrimmed SVI2 dependency count, but the lowSMIO sink at :4046-4047 survives an entries-only clamp and is a distinct write primitive). Mixed-VRM VBIOS: VDDC via GPIO_LUT (voltage_control true) + VDDCI via SVID2 (vddci_control false, vddci_control_svi2 true). si_construct_voltage_tables:3998-4004 fills vddci_voltage_table.count = vddci_dependency_on_mclk.count (u8, up to 255) with NO trim (the GPIO paths trim at :3973/:3993/:4020). si_populate_smc_voltage_tables then takes the non-SVI2-VDDC else branch (:4058 false since VDDC is GPIO) and at :4079-4080 calls si_populate_smc_voltage_table, whose loop at :4046 OR-writes table->lowSMIO[i] for i<count into the packed STATETABLE embedded in the kzalloc'd si_power_info (si_dpm.h:196). count 33..255 writes up to 1020 bytes past lowSMIO[0] (struct is 3012 B; overflow is contained within the si_power_info allocation) corrupting voltageMaskTable, phaseMaskTable, dpm2Params, initialState/ACPIState/ULVState/driverState and dpmLevels β€” data that si_init_smc_table uploads verbatim to the GPU SMC at :4779-4781, programming wrong voltage masks / TDP limits / state clocks (hardware misconfiguration, undervoltage/overvoltage risk). Harness with verbatim packed struct replicas from sislands_smc.h confirms the exact corrupted fields; gcc itself flags the replica loop (-Warray-bounds: 'iteration 32 invokes undefined behavior').