# DF-4366 — amdgpu_set_pp_power_profile_mode: strsep NULL deref panic + unterminated stack walk

**File:** `sys/dev/drm/amd/amdgpu/amdgpu_pm.c:916-975` (the sysfs store for
`pp_power_profile_mode`, i.e. the entry that dispatches into
`pp_set_power_profile_mode` / `amd_powerplay.c:888-910`) · **Severity:** Low
(root-only sysfs, deterministic kernel panic) · **Class:** CWE-476 NULL deref
(+ CWE-125 uninitialized-stack overread)

## Bug (source)

The CUSTOM-profile parse loop:

```c
944:  memcpy(buf_cpy, buf, count-i);          /* buf_cpy[128], NO NUL written */
946:  tmp_str = buf_cpy;
947:  while (tmp_str[0]) {
948:      sub_str = strsep(&tmp_str, delimiter);   /* {' ','\n','\0'} */
949:      ret = kstrtol(sub_str, 0, &parameter[parameter_size]);
...
955:      while (isspace(*tmp_str))           /* tmp_str == NULL after the
956:          tmp_str++;                          last un-delimited token */
```

DragonFly `strsep` (`sys/libkern/strsep.c:63-65`) sets `*stringp = NULL`
when it consumes the remainder without finding a delimiter. A write that
does not end in `' '`/`'\n'` — e.g. `printf '5 1 2 3' >
/sys/class/drm/card0/device/pp_power_profile_mode` (no echo-added newline)
— copies `count-i` bytes ending exactly on the sysfs NUL sentinel, the final
token's `strsep` matches the `'\0'` delimiter, returns `tmp_str = NULL`, and
the very next `isspace(*tmp_str)` (`sys/sys/ctype.h:43` expands to a
comparison on `*tmp_str`) dereferences NULL → **Fatal trap 12, fault
address 0x0**.

Secondary: `buf_cpy[128]` is never NUL-terminated by the code (termination
falls out accidentally from the off-by-one copy that includes the sysfs
sentinel); any input shape where `i == 0` (no space after the leading
profile digit) copies `count` bytes with no sentinel and the walk continues
into uninitialized kernel stack until it happens to hit a zero byte.

Sibling handler `amdgpu_set_pp_od_clk_voltage` (`:543-606`) copies
`count+1` bytes (includes the sentinel) and cannot exceed 63 tokens, so it
does not have this bug — the delta proves the profile-mode loop is the
defect.

## Reproduce (guest, mechanism replica — guest has no AMD GPU)

```
./build.sh    # ships + builds amdgpu_parser_replica.ko
./run.sh      # kldload; sysctl kern.amdgparun=1  -> guest PANICS
```

The module runs the exact `amdgpu_pm.c:946-957` walk (same 3-delimiter
strsep set, same isspace skip) on the buffer `printf '5 1 2 3' > …` would
deliver (count=7, i=1, copy = `"1 2 3\0"`). Expected serial console:

```
amdgparun: walking 6-byte content, last byte 0x00
Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x0
Stopped at      run_parse.part.1+0xe0:  movzbl  (%rax),%edx
db>
```

Guest sits in DDB; reset with `vm.sh reset with-src` afterwards.

## Fix

Bound `parameter_size` (< 64), NUL-terminate `buf_cpy` explicitly
(`buf_cpy[count-i] = '\0'`), and guard the skip loop
(`while (tmp_str != NULL && isspace(*tmp_str))`) — see `fix.diff`.
