# DF-4366 — VERDICT

**Status: inconclusive (amdgpu sysfs entry hardware-gated on this guest;
parse-loop mechanism reproduced deterministically via kernel-module
replica that panics the guest).** Confidence: certain. Severity: Low.

## Root cause (source-certain)

`amdgpu_set_pp_power_profile_mode()` (`sys/dev/drm/amd/amdgpu/amdgpu_pm.c:916`,
the store handler of the `pp_power_profile_mode` sysfs attribute registered
at `:1025-1027`, `S_IRUGO | S_IWUSR`) parses CUSTOM-profile writes
(`profile_mode == PP_SMC_POWER_PROFILE_CUSTOM == 5`, `kgd_pp_interface.h:135`)
with:

* `:944` `memcpy(buf_cpy, buf, count-i);` — `buf_cpy[128]`, never explicitly
  NUL-terminated; `count ≤ 127` is the only bound (`:940-941`);
* `:948` `strsep(&tmp_str, delimiter)` with `delimiter = {' ', '\n', '\0'}`
  (`:931`);
* `:955-956` `while (isspace(*tmp_str)) tmp_str++;` — executed immediately
  after `strsep`, with no NULL check.

DragonFly `strsep` (`sys/libkern/strsep.c`) returns the trailing token and
sets `*stringp = NULL` when the remainder contains no delimiter. For a
write whose last byte is not `' '` or `'\n'` — trivially produced with
`printf '%s' "5 1 2 3" > ...pp_power_profile_mode` instead of `echo` — the
final `strsep` matches the `'\0'` delimiter (copied in from the sysfs
sentinel by the off-by-one `count-i` copy), sets `tmp_str = NULL`, and
`isspace(*tmp_str)` (`sys/sys/ctype.h:43` — plain comparisons on `*tmp_str`)
dereferences address 0.

The numeric tail (`parameter[]`, `parameter_size`) is bounded in practice:
delimiters terminate inputs yield ≤ 63 tokens within `count ≤ 127`, and the
64-token unterminated shape NULL-panics before the `parameter[parameter_size]`
store — so the stack OOB write is *not* reachable; the reachable defects are
(a) the deterministic NULL deref and (b) the unterminated `buf_cpy` walk into
uninitialized stack for `i == 0` input shapes.

## Guest reproduction (mechanism replica) — panic.txt, run.log

The guest has no AMD GPU (`virtio-net`/emulated VGA only), so the amdgpu
sysfs entry cannot be exercised directly. The replica module
(`amdgpu_parser_replica.c`) runs the verbatim delimiter walk (same strsep
3-delimiter set, same isspace skip, same copy shape `count-i` from the
advanced pointer) on the exact byte sequence `printf '5 1 2 3' >` delivers.
First and only attempt:

```
amdgparun: loaded (pp_power_profile_mode parser replica)
amdgparun: walking 6-byte content, last byte 0x00
Fatal user address access from kernel mode from sysctl at ffffffff82600150
Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x0
fault code              = supervisor read data, page not present
Stopped at      run_parse.part.1+0xe0:  movzbl  (%rax),%edx
db>
```

Decisive: `fault virtual address = 0x0`, `supervisor read data`, and the
faulting instruction is exactly the `isspace(*tmp_str)` byte load
(`movzbl (%rax),%edx` with `%rax == NULL`). The guest died into DDB and
was reset (`vm.sh reset with-src`).

## Why Low severity

The attribute is `S_IWUSR` (root-writable only), so the direct impact is a
root-triggerable kernel panic (DoS). It is filed because it is a
deterministic, one-write kernel crash on a documented interface of the
powerplay dispatch surface audited in this batch, and because the same loop
shape is copy-pasted across the DRM tree.

## Fix

`fix.diff`: explicitly NUL-terminate `buf_cpy`, bound `parameter_size`, and
NULL-guard the skip loop (`while (tmp_str != NULL && isspace(*tmp_str))`).
Driver-level validation impossible on this guest (no AMD GPU); the fix
removes both the NULL deref and the uninitialized-stack walk by
construction.

Guest left clean (`vm.sh reset with-src` after the panic capture).

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