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)
PoC verification
Evidence pack
findings/poc/DF-3704 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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, ¶meter[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).
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)
- amdgpu_pm.c:552
long parameter[64];:584kstrtol(sub_str, 0, ¶meter[parameter_size])β signed 64-bit parse; "s -1 1600 1150" yields parameter[0] = -1. - vega10_hwmgr.c:4884
input[i] >= podn_vdd_dep_table->countβ long vs (int-promoted) uint8_t count: signed comparison; -1 < count β guard passes. - :4888
input_level = input[i];β uint32_t truncation of the negative long β 0xFFFFFFFF. - :4893-4895 index
dpm_table->dpm_levels[input_level](dpm_levels[8]) andpodn_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_volfully 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_testablefix.diff authored against read-only sys/ tree; no AMD hw on guest so no patched-kernel validation possible
fix.diff; run.log hardware gate
Confirmed kernel references
Detail
Exploit chain
root echo 's -1
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.
No comments yet.