# DF-3859 VERDICT — vega20_odn_edit_dpm_table negative-index OOB read (DF-3704 twin)

**Status: not_reproduced (kernel reach hardware-gated) — index arithmetic and
OOB read-with-leak reproduced verbatim on the guest heap stand-in.**

## Root cause (path:line, sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c
unless noted)

`vega20_odn_edit_dpm_table()` `:2549`, branch `PP_OD_EDIT_VDDC_CURVE`
(`:2656-2720`):

1. `:2560` declares `int32_t input_index, input_clk, input_vol;`; `:2567+`
   `input` is the caller's `long *` array of kstrtol-parsed user tokens.
2. `:2674` `input_index = input[i];` — a user-controlled signed value.
3. `:2678` `if (input_index > 2)` — **only an upper-bound check**; every
   negative value passes. (Compare the SCLK branch `:2585`
   `input_index != 0 && input_index != 1` and the MCLK branch `:2633`
   `input_index != 1`, which are exact-match and safe.)
4. `:2685` `od8_id = OD8_SETTING_GFXCLK_FREQ1 + 2 * input_index;` — for
   input_index ≤ -2 this is negative; `od8_settings[od8_id]` at `:2686-2687`
   reads `od8_settings_array[od8_id]` **before** the 16-element array
   (`vega20_hwmgr.h:400-429`, element size 20, embedded in the kzalloc'd
   `struct vega20_hwmgr` backend, `vega20_hwmgr.c:383`).
5. On the (almost certain) range-check failure, `:2688-2692` `pr_info`s
   `od8_settings[od8_id].min_value/.max_value` — the OOB-read kernel heap
   bytes land in the log. `:2695-2703` repeats the identical negative index
   for the voltage slots.
6. `:2705-2718` the write `switch (input_index)` matches only 0/1/2, so no
   OOB *write* occurs (this is the read-only sibling of vega10's DF-3704
   wild write).

Offsets: `input_index=-2 → od8_id=-2 → -40 bytes` (reads
`vega20_hwmgr.odn_data`/`disable_dpm_mask` region);
`input_index=-7 → -240 bytes`; the user token is an arbitrary int32, e.g.
`vc -100000000 1000 900` → `od8_id = -199999998` → ≈ **-3.7 GB** offset →
unmapped kernel address → page fault → panic in the sysfs write path.

## Reach

`pp_od_clk_voltage` (`amdgpu_pm.c:1028`, `S_IRUGO | S_IWUSR`) →
`amdgpu_set_pp_od_clk_voltage` → token parser `amdgpu_pm.c:575-592`
(`kstrtol` accepts `-2`; `PP_OD_EDIT_VDDC_CURVE` selected by the `"vc"`
prefix at `:566`) → `amdgpu_dpm_odn_edit_dpm_table` (`:594-596`) →
`vega20_odn_edit_dpm_table`. Requires root (or a confused deputy that
relays user OD strings to the sysfs node) on a Vega20 with OD8 curve
support. `hwmgr->pm_en` gate at `pp_dpm_odn_edit_dpm_table`
(amd_powerplay.c) passes like DF-3858's analysis.

## What ran on the Guest

`odn_negidx.c` replicates the enum, the 20-byte
`vega20_od8_single_setting`, the array embedded after 512 marker
("adjacent heap") bytes inside a heap allocation, and the verbatim
`:2685-2693` index/range/pr_info logic.

Observed (`run.log`, guest cc 8.3 -O2):
- control `vc 1 1000` → `od8_id=4` (+80 bytes, in-bounds), check passes.
- attack `vc -2 1000` → `od8_id=-2` (-40 bytes) → error message prints
  `[-1515870811 - -1515870811]` = `0xA5A5A5A5` — **exactly the marker bytes
  read out of bounds and disclosed in the message**, the same information
  flow as the kernel `pr_info`.
- attack `vc -7 1000` → `od8_id=-12` (-240 bytes), same leak.

The guest kernel path itself cannot run (0 AMD PCI devices, no `/dev/dri`,
`env.txt`); the wild read/leak semantics are nevertheless fully
demonstrated on the identical index arithmetic and access expression.

## Fix validation

`fix.diff` rejects negative indices explicitly in the VDDC_CURVE branch
(`input_index < 0 || input_index > 2`, matching upstream's later fix style
for this handler). git-apply-clean against the read-only `sys/` tree; no
Vega20 ASIC on this guest, so a patched-kernel run is **not_testable**.

## 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.
