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

vega10_odn_edit_dpm_table signed-compare lets negative user 'long' pass the bounds check -> wild kernel write (DF-3687 twin)

Summary

'input[i] >= podn_vdd_dep_table->count' compares a signed long (user token parsed by kstrtol) against a promoted uint8_t count: negative values pass. input_level=(uint32_t)input[i] becomes 0xFFFFFFFF and the writes hit dpm_levels[0xFFFFFFFF].value / entries[0xFFFFFFFF].clk/.vddc - user-chosen 32-bit values at ~34GB offsets from the arrays. Root write to pp_od_clock_voltage (S_IWUSR) on a Vega10 with OD enabled: 'echo "s -1 1600 1150" > ...' -> wild kernel write; a confused-deputy writer of that node escalates to arbitrary-offset write.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3704 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
trigger_notes.c β€” 1.2 KB view raw
build.sh β€” 74 B view raw
run.sh β€” 145 B view raw
run.log β€” 780 B view raw
env.txt β€” 848 B view raw
fix.diff β€” 553 B view raw
manifest.json β€” 810 B view raw
verdict.json β€” 2.2 KB view raw

DF-3704 β€” vega10_odn_edit_dpm_table: signed long user value passes the bounds check and indexes entries[] with a huge uint32 (wild kernel write)

File: sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c (pass 2). smu7 checklist item (e) β€” DF-3687 vega10 twin.

Bug (:4883-4895)

for (i = 0; i < size; i += 3) {
    if (i + 3 > size || input[i] >= podn_vdd_dep_table->count) {   // :4884
        pr_info("invalid clock voltage input\n");
        return 0;
    }
    input_level = input[i];                                          // :4888 uint32_t = (long)negative
    ...
        dpm_table->dpm_levels[input_level].value = input_clk;        // :4893  wild write
        podn_vdd_dep_table->entries[input_level].clk = input_clk;    // :4894  wild write
        podn_vdd_dep_table->entries[input_level].vddc = input_vol;   // :4895  wild write

input[i] is long (64-bit signed, user-parsed). podn_vdd_dep_table->count is uint8_t (promoted to int, then to long): the comparison is SIGNED, so any negative value (e.g. -1) satisfies input[i] >= count == false and passes. input_level (uint32_t) then becomes 0xFFFFFFFF and the three writes land ~34GB past the arrays β€” uncontrolled kernel memory corruption (write value input_clk is user-specified MHz*100).

Input path

root-only sysfs (S_IWUSR): amdgpu_pm.c:543-602 amdgpu_set_pp_od_clk_voltage parses space-separated tokens with kstrtol(sub_str, 0, &parameter[n]) (:584) β€” negative tokens accepted β€” then calls amdgpu_dpm_odn_edit_dpm_table β†’ vega10_odn_edit_dpm_table (vega10_hwmgr.c:4985 wiring). Example on a Vega10 box with OD enabled:

echo "s -1 1600 1150" > /sys/class/drm/card0/device/pp_od_clock_voltage

Trigger requirements

AMD Vega10 with overdrive enabled + root (or any way to write the sysfs node). Guest is virtio-only β†’ inconclusive, source-certain.

Fix

fix.diff: reject input[i] < 0 explicitly (compare as unsigned after range check).

VERDICT.md
↓ download raw

DF-3704 VERDICT β€” odn_edit negative index wild write

status: inconclusive (hardware-gated) β€” source-certain. reproduced=0 on audit guest.

Proof chain (lines verified in guest /usr/src, see run.log)

  1. amdgpu_pm.c:552 long parameter[64]; :584 kstrtol(sub_str, 0, &parameter[parameter_size]) β€” signed 64-bit parse; "s -1 1600 1150" yields parameter[0] = -1.
  2. vega10_hwmgr.c:4884 input[i] >= podn_vdd_dep_table->count β€” long vs (int-promoted) uint8_t count: signed comparison; -1 < count β†’ guard passes.
  3. :4888 input_level = input[i]; β€” uint32_t truncation of the negative long β†’ 0xFFFFFFFF.
  4. :4893-4895 index dpm_table->dpm_levels[input_level] (dpm_levels[8]) and podn_vdd_dep_table->entries[input_level] (entries[8]) with 0xFFFFFFFF β†’ byte offsets ~0xFFFFFFFF*8/12 (~34GB) from the arrays β†’ wild kernel write of user value (input_clk/input_vol fully controlled 32-bit values). Any negative value in [-2^31, -1] truncates to a large uint32; values below -2^31 truncate to smaller/large uint32s equally unchecked.

Impact: privileged (root sysfs S_IWUSR) kernel arbitrary-offset write β†’ memory corruption; on a system where the sysfs node is writable by a lesser-privileged service or via a confused deputy this is an escalation primitive. As shipped: root β†’ kernel corruption (usually a crash, potentially worse on unhardened configs). Severity Medium (privileged trigger).

Guest result

0 AMD PCI devices; amdgpu.ko never binds; pp_od_clock_voltage never exists on this guest. The parser (amdgpu_pm.c) accepts negative tokens β€” verified at source; the sink lines are confirmed in the guest's kernel source (run.log). Verdict: inconclusive, source-certain.

Fix

fix.diff adds an explicit negative check (and documents the correct unsigned compare): if (i + 3 > size || input[i] < 0 || input[i] >= podn_vdd_dep_table->count).

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
baseline no→ patch + rebuild →patched clean

fix.diff authored against read-only sys/ tree; no AMD hw on guest so no patched-kernel validation possible

fix.diff; run.log hardware gate
↓ fix.diffper-fix-DF-3704

Confirmed kernel references

Detail

Exploit chain

root echo 's -1 ' > pp_od_clock_voltage -> entries[(uint32_t)-1] write with controlled value -> kernel memory corruption (privileged; crash-to-RCE on unhardened configs)

Evidence (decisive lines)

VERDICT.md proof chain; run.log (guest-src excerpt :4883-4895); trigger_notes.c (type-confusion demo); fix.diff

PoC changes

no runnable PoC possible (hardware-gated); documented exact sysfs string and the signed/unsigned semantics

Verified recommended fix

Reject negative input before the count compare: input[i] < 0 || input[i] >= count (fix.diff)

Verdict

Source-certain wild kernel write (DF-3687 smu7 twin): vega10_odn_edit_dpm_table:4884 compares signed long user input against uint8_t count, so negative tokens (parsed by kstrtol at amdgpu_pm.c:584) pass the guard; input_level=(uint32_t)negative (:4888) then indexes dpm_levels[8]/entries[8] at :4893-4895 with ~0xFFFFFFFF, writing user-chosen 32-bit values ~34GB off the arrays. Trigger is root-only sysfs pp_od_clock_voltage on a Vega10 with OD enabled β€” node absent and amdgpu unbound on the virtio-only audit guest (0 AMD PCI devices), so no runtime repro; sink lines verified in guest /usr/src.