# DF-3653 — NULL pointer dereference in `amdgpu_dm_atomic_commit_tail()` via `update_stream_scaling_settings()`

**File:** `sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c`
**Lines:** 4882-4885 (call site, guard placed after use), 2479-2480 (faulting deref)
**Severity:** Medium (local DoS — deterministic kernel panic) · **CWE-476** · confidence: certain

## What

The connector-scaling loop of `amdgpu_dm_atomic_commit_tail()` calls
`update_stream_scaling_settings(..., dm_new_crtc_state->stream)` **before**
checking `dm_new_crtc_state->stream` for NULL (the check sits at 4885, the
call at 4882). `update_stream_scaling_settings()` only guards `!mode` — and
`mode` is `&crtc->mode`, an *embedded struct* that is never NULL — so it
unconditionally executes `stream->timing.h_addressable` (2479). With
`stream == NULL` this reads address **0x10** (offset of `timing.h_addressable`
in `dc_stream_state`: 8-byte `sink` pointer + `h_total` + `h_border_left`) →
page fault → kernel panic.

## How to reproduce (kernel)

Three atomic commits from a DRM-master client (any local user who holds DRM
master — e.g. any user when no compositor is running, or the compositor
itself):

1. Disable CRTC (active=0, MODE_ID=0, connector CRTC_ID=0).
2. Attach connector to the CRTC with a mode but **active=0**
   (`CONNECTOR:CRTC_ID=crtc`, `CRTC:MODE_ID=<mode>, ACTIVE=0`). The check
   passes: `needs_modeset` is true this round, but `dm_update_crtcs_state()`
   skips stream creation because `active == 0` (amdgpu_dm.c:5293-5294
   `modereset_required` → `goto next_crtc`). After commit:
   `connector->state->crtc == crtc`, `crtc->state->{enable=1,active=0}`,
   `dm_crtc_state->stream == NULL`.
3. Change only an underscan property on the connector
   (`underscan=1`, `underscan hborder=1`, `underscan vborder=1`).
   `drm_atomic_get_connector_state()` pulls the CRTC into the state
   (drm_atomic.c:819-826); nothing changes on the CRTC so
   `drm_atomic_crtc_needs_modeset()` is false; `is_scaling_state_different()`
   is true → commit tail enters the scaling block with `stream == NULL` →
   panic at 0x10.

Requires a connected display (or forced connector) so `mode_valid()` passes in
step 2; the panic itself needs no hardware event after that.

## Harness (this pack)

The audit guest is virtio-only (no AMD GPU — see env.txt), so the kernel path
cannot execute there. `harness.c` models the exact commit-tail decision and
replicates the verbatim field order of `dc_stream_state`/`dc_crtc_timing` so
the fault address equals the kernel's.

```
./build.sh   # cc -O2 -Wall -o harness harness.c
./run.sh     # prints the decision trace, then SIGSEGV reading 0x10
```

Expected decisive output (run.log):

```
offsetof(dc_stream_state, timing.h_addressable) = 0x10
commit-tail scaling loop: acrtc!=NULL, needs_modeset=0, scaling_changed=1, stream=NULL
-> update_stream_scaling_settings(mode, dm_state, stream=NULL)
Segmentation fault  (EXIT=139)
```

## Contents

`harness.c` · `build.sh` · `run.sh` · `build.log` · `run.log` · `env.txt` ·
`VERDICT.md` · `fix.diff` · `manifest.json` · `verdict.json`
