# DF-5028 — verdict narrative

## Mode: pass-2 audit of `sys/dev/drm/radeon/dce6_afmt.c` (GLM 5.3)

## Result: not reproduced at runtime (hardware-gated); source-level proof

The bug is a missing NULL check on `radeon_encoder->enc_priv` before
`dig->pin` in the encoder-list walk inside `dce6_audio_get_pin()`
(sys/dev/drm/radeon/dce6_afmt.c:104-105).

### Proof chain (all path:line)

1. `dce6_afmt.c:101-107` — the walk dereferences `dig->pin` for every encoder
   for which `radeon_encoder_is_digital()` is true. No NULL guard.
2. `radeon_encoders.c:423-445` — `radeon_encoder_is_digital()` tests only
   `radeon_encoder->encoder_id`; nothing guarantees `enc_priv != NULL`.
3. `atombios_encoders.c:2838, 2872` — for digital encoder ids with
   `ATOM_DEVICE_LCD_SUPPORT`, `enc_priv = radeon_atombios_get_lvds_info()`.
4. `radeon_atombios.c:1626-1640` — `get_lvds_info()` returns NULL when
   `atom_parse_data_header(..., LVDS_Info, ...)` fails (no/invalid VBIOS
   table) and when `kzalloc` fails; caller does not check.
5. `atombios_encoders.c:2757-2766` — `set_dig_info()` (the other digital
   `enc_priv` source, lines 2842/2876/2880) returns NULL on `kzalloc` failure.
6. Reachable by default: `radeon_audio = -1` (radeon_drv.c:180), audio gate is
   `radeon_audio != 0` (radeon_connectors.c:1766); detect paths call
   `radeon_audio_detect()` (radeon_connectors.c:1439, :1767) ->
   `radeon_audio_get_pin()` (radeon_audio.c:425) -> `dce6_audio_get_pin()`
   (dce6_afmt.c:86) on any audio-capable DCE6+ system.
7. Note the sibling unguarded derefs at `radeon_audio.c:483` (detect) and
   `radeon_audio.c:266-267` (`radeon_audio_enable` walk) — the same event
   would usually crash there first for the *calling* encoder; the dce6_afmt.c
   walk additionally crashes on *any other* digital encoder with NULL
   `enc_priv` on the system.

### Why the crash is a panic, not an exploit primitive

`struct radeon_encoder_atom_dig` is ~230 bytes; `pin` sits at a small fixed
offset, so the access is a read from the zero page (unmapped on amd64 with
`vm.pmap.pg_ps_enabled`-style protections; NULL page is never mappable by
userland on DragonFly). No attacker-controlled data is read or written. Impact
ceiling: kernel panic (DoS) on machines with anomalous VBIOS or under extreme
memory pressure. Not a memcorrupt primitive.

### Verification attempt on the audit guest

- `uname -a`: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (see env.txt).
- `pciconf -l`: only display device is `vgapci0 ... chip=0x11111234` (Bochs
  QEMU std VGA). No ATI/AMD GPU -> `radeon` driver never attaches ->
  `dce6_afmt.c` code paths cannot execute on this guest.
- Status therefore `not_reproduced` (impact `none` demonstrated at runtime);
  the finding stands on the source-level chain above, which is deterministic.

### Fix

`fix.diff` (this directory) adds the NULL guard in `dce6_afmt.c:105` and the
primary sibling in `radeon_audio.c:459` (`radeon_audio_detect`). A third
identical walk in `radeon_audio.c:266` (`radeon_audio_enable`) should get the
same guard. Fix cannot be boot-validated without AMD hardware ->
`fix_status: not_testable`.

### Other classes hunted in dce6_afmt.c and killed (pass-2 negative result)

- ELD/SAD walk bounds: `sads[]`/`sad_count` come from
  `drm_edid_to_sad()` (drm_edid.c:4034) which allocates exactly
  `count = dbl/3` entries and returns that same `count`; caller
  `radeon_audio_write_sad_regs` (radeon_audio.c:375-381) rejects `<= 0`.
  `dce6_afmt_write_sad_regs`'s `for (j = 0; j < sad_count; j++)`
  (dce6_afmt.c:246) is therefore in-bounds by construction.
- `sadb[0]` (dce6_afmt.c:181, :208) is guarded by `if (sad_count)`;
  `drm_edid_to_speaker_allocation` returns count==0 with sadb==NULL or a
  3-byte kmemdup with count==3 — never a short non-NULL buffer.
- pin-array bounds: `num_pins <= 7 == RADEON_MAX_AFMT_BLOCKS`
  (radeon_audio.c:316-328, radeon_mode.h:52, radeon.h:1769) — walks at
  dce6_afmt.c:75 and :96 are in-bounds.
- div-by-zero: `radeon_audio_decode_dfs_div()` returns 0 for any
  out-of-range WDIVIDER field and `dce6_afmt.c:326` guards `if (div)`;
  `clock*100 <= 60,000,000` cannot overflow u32.
- register write indexes: all endpoint writes go through
  `dce6_endpoint_rreg/wreg` under `rdev->end_idx_lock` (dce6_afmt.c:49-52,
  :60-67) with offsets from the fixed `pin_offsets[]` table — no
  attacker-controlled index.
- latency fields (dce6_afmt.c:144-156): `connector->latency_present[]` /
  `video_latency[]` / `audio_latency[]` are fixed-size fields written by the
  EDID CEA parser, always in-bounds here.

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