# DF-4145 — VERDICT

**REPRODUCED, with the finding's mechanism corrected by the rig.** Verbatim
fidelity as for DF-4143/4144 (68 blocks identical to the guest's
`/usr/src/sys`).

## Confirmed part of the claim

- A pplib state with `ucNumDPMLevels == 0` is parsed with `ps->num_levels`
  left at its kzalloc zero: `trinity_parse_pplib_clock_info` sets
  `num_levels = index + 1` only when invoked (trinity_dpm.c:1726), and the
  rig's verbatim parse of the crafted two-state image (state0 BOOT with one
  level, state1 SDSTATE with `ucNumDPMLevels=0`) leaves
  `state1 num_levels=0` (run.log, mode `4145-fullflow`).
- When that state is selected, the verbatim
  `trinity_dpm_pre_set_power_state` → `trinity_apply_state_adjust_rules`
  path executes the `:1603-:1618` block (proved: `Dpm0PgNbPsLo` 1→2,
  `DpmXNbPsLo` 2→2 — the `:1604/:1606` defaults overwritten at
  `:1615/:1617`, which immediately precede the store) and then executes
  `ps->levels[ps->num_levels - 1].allow_gnb_slow = 0;` (:1619) — an
  out-of-bounds store. Reachability requires `pi->sys_info.nb_dpm_enable`
  (verbatim-parsed from the crafted IntegratedSystemInfo v1_7,
  `ucNBDPMEnable=1`) and an SDSTATE/HDSTATE/UI_BATTERY classification —
  all VBIOS-controlled, all set in the crafted image.

## Corrected mechanism (the finding's `ps-1` claim is wrong)

`ps->num_levels` is `u32`. `ps->num_levels - 1` therefore evaluates to
`0xFFFFFFFF` (not −1), and C array subscripting scales by
`sizeof(struct trinity_pl)`:

    store address = ps + offsetof(trinity_ps, levels)     (= 4)
                      + 0xFFFFFFFF * 12                    (= 0xBFFFFFFF4)
                      + offsetof(trinity_pl, allow_gnb_slow)(= 7)
                  = ps + 0xBFFFFFFFF        (~48 GiB past the object)

Rig proof, two independent ways:

1. `4145-guard`: the verbatim store faults; the reported address is
   `0x14004bdfff` with `ps = 0x8004be000`, i.e. exactly `ps + 0xBFFFFFFFF`
   (checked arithmetically in the mode output). gdb at the faulting store
   computes `&ps->levels[ps->num_levels - 1].allow_gnb_slow ==
   0x140047b1af == ps + 0xBFFFFFFFF` for the fullflow `ps`.
2. `4145-fullflow`: a canary page mapped at exactly `ps + 0xBFFFFFFFF`
   (MAP_FIXED) receives the store: `canary byte at target: 0xaa -> 0x00
   (WRITTEN 0x00 BY THE VERBATIM STORE)`, `canary region bytes changed:
   1 (of 12288)` — a single byte, at the computed wild offset, value 0.

The read side (`trinity_dpm_get_sclk`, :2102,
`levels[requested_state->num_levels - 1].sclk`) addresses
`ps + 0xBFFFFFFF8` — the same wild region; the rig's
`get_sclk` returns the canary bytes placed there (0xaaaaaaaa),
demonstrating the read is satisfied by whatever memory (if any) exists
~48 GiB past the object — in-kernel an OOB read feeding the watermark
calculations, or a fault.

## In-kernel consequence (corrected)

`pi->requested_ps` is embedded in the kzalloc'd `struct trinity_power_info`
(kernel heap). The store at `heap_object + 0xBFFFFFFFF`:
- most likely lands in unmapped KVA → page fault → panic at the first
  power-state transition after the crafted state is selected (DoS,
  reboot-loop);
- if the offset happens to fall inside mapped kernel VA (DMap is huge),
  silently zeroes one byte of unrelated kernel memory ~48 GiB away.

There is **no pointer-surgery capability**: the offset is a compile-time
constant derived from the type layout, and the written value is always 0x00.
The finding's claimed "zero the MSB of ps_priv → non-canonical pointer →
next deref faults" chain does not occur (the store never touches `ps-1`).

## Fix validation (A/B, identical image)

`fix.diff`: (1) parse-side rejection — after the idx loop, `k == 0` states
are refused with a full unwind (`power state %d has no dpm levels`);
(2) defense-in-depth — `if (ps->num_levels > 0)` guards the `:1619` store.
Fixed run: `4145-fullflow` → `init rc=-22` (`RIG: power state 1 has no dpm
levels`), apply never runs; `4145-guard` — the guarded store cannot fault;
`good` unchanged. The checker proves the `APPLY_DF4145_FIX` lines are
exactly the fix.diff additions (build.log, 11 lines).

## Honest limitations

- No AMD GPU on the audit guest: in-kernel execution impossible here; the
  48 GiB offset makes silent corruption vs. panic depend on the target's
  KVA layout, which the rig cannot decide — the panic-class floor is
  certain, the corruption window speculative.
- The `layout` mode prints the verbatim struct offsets (48/40/12/7/4) that
  the corrected arithmetic rests on.

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