β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3943

resource_build_scaling_params INTERLACE error path leaves shared dc_stream_state.dst.height permanently doubled (compounding signed-int overflow)

Summary

For interlaced streams the function doubles the SHARED, refcounted dc_stream_state dst.height at entry, but the early return-false for viewport < 16px skips the compensating /= 2. A failed validation therefore commits persistent corruption to the stream object owned by amdgpu_dm's crtc state; the same stream object is reused across commits, so each failed interlaced attempt doubles again: dst.height = h*2^N, overflowing signed int after ~21 attempts. VERIFIED: userland verbatim transcription executed on the guest - 1080->2160->...->-2030043136 at attempt 21. Persistent display corruption/local DoS reachable by the DRM-master session user on amdgpu DC hardware. Upstream dropped the shared-state mutation entirely by v5.4. Balance-on-error-return fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3943 Β· 9 files
FileTypeDescriptionSize
interlace_sim.c β€” 2.7 KB view raw
build.sh β€” 99 B view raw
run.sh β€” 99 B view raw
build.log β€” 162 B view raw
run.log β€” 1.4 KB view raw
env.txt β€” 68 B view raw
VERDICT.md β€” 3.3 KB ↓ raw
fix.diff β€” 859 B view raw
verdict.json β€” 2.4 KB view raw
VERDICT.md
↓ download raw

VERDICT β€” DF-3943

Bottom line

not_reproduced on the audit guest β€” source-level certain. The audit QEMU guest has no AMD GPU (virtio-gpu 0x1111:1234, no /dev/dri, drm not built into the running kernel β€” env.txt), so the kernel path cannot be exercised. The bug is however provable by direct reading of the cited lines, and the control-flow was executed 1:1 in a userland harness on the guest (run.log): every failed interlaced validation leaves pipe_ctx->stream->dst.height doubled.

Root cause (path:line)

resource_build_scaling_params() mutates the shared dc_stream_state:

pipe_ctx->stream is the refcounted struct dc_stream_state owned by amdgpu_dm (dm_new_crtc_state->stream), not a per-validation copy; a failed dc_validate_global_state() β†’ DC_FAIL_SCALING (dc_resource.c:1201-1206, 1195-1209) aborts the atomic commit but leaves the stream object corrupted. amdgpu_dm reuses the same stream object across commits that keep the same mode, so the doubling compounds: N failed attempts β†’ dst.height Γ— 2^N. int (32-bit) overflows after ~21 attempts from 1080 (see run.log attempt 21: -2030043136 = 1080Β·2^21 wrapped). Downstream sinks of the corrupted field: calculate_recout() :637-657 (recout.height numerator), calculate_scaling_ratios() :713-737 (out_h), scl_data.h_active/v_active β€” all part of the same commit path.

Why upstream confirms this is wrong

The whole "double dst.height in the shared stream during validation" pattern was removed from mainline by Linux v5.4 (resource_build_scaling_params in v5.4 has no INTERLACE mutation at all; verified against the v5.4 blob fetched from api.github.com β€” /tmp/opencode/dc_v5.4.c:962ff). No dedicated fix commit exists; it died in the DC refactoring between 4.17 and 5.4.

Impact

Local persistent display-state corruption / DoS reachable by the graphical session user (DRM master) on DCE8+/DCN amdgpu hardware in interlaced modes. Not memory corruption: the corrupted value is a bounded int used in display math; worst case is garbage scaling parameters, display corruption, and errant division results (denominators are separately validated by amdgpu_dm; DF-1301 tracks the raw zero-rect class). Severity Medium.

PoC changes vs seed

None β€” no seed existed (pass-2 new finding). Harness written from scratch; logic transcribed verbatim from dc_resource.c:1118-1189.

Fix validation

Not possible on this guest (no AMD GPU): fix_status=not_testable, fix_baseline_reproduced=0, fix_patched_reproduced=0. fix.diff is git apply --check-clean against sys/ (verified in-place).

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

cannot build/run amdgpu DC on the audit guest (no AMD GPU); fix.diff applies clean to sys/ and restores the *=2//=/=2 balance

fix.diff; git apply --check clean
↓ fix.diffper-fix-DF-3943

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (interlace_sim: compounding doubling + signed wrap at attempt 21), interlace_sim.c (verbatim control flow of :1118-1189), VERDICT.md (root cause + upstream v5.4 comparison), fix.diff (git-apply-clean)

PoC changes

no seed; harness transcribed verbatim from the kernel function

Verified recommended fix

Restore dst.height /= 2 on the early-return path, or stop mutating the shared stream in validation (upstream v5.4 removed the pattern)

Verdict

Source-certain error-path imbalance: resource_build_scaling_params() doubles the shared dc_stream_state.dst.height for INTERLACE at entry (dc_resource.c:1118-1119) but the early return false for viewport<16 (:1125-1126) skips the compensating /=2 (:1188-1189), so every failed interlaced validation permanently doubles the persistent stream's dst.height; compounding across attempts overflows the signed int (~21 doublings from 1080). Guest has no AMD GPU (no /dev/dri, virtio-gpu), so not reproduced at kernel level; a verbatim userland transcription of the control flow run on the guest (run.log) shows 1080->2160->...->-2030043136. Upstream dropped the shared-state mutation entirely by Linux v5.4.