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

dce112_compressor_construct() init-order defects: dead 64-bit-bus LPT guard + lpt_channels_num wipe make program_lpt_control's only valid case unreachable (LPT armed with unprogrammed config)

Summary

The 'single DRAM channel => no LPT' guard reads memory_bus_width while it is still 0 (assigned only later), so it never fires; lpt_channels_num=1 is unconditionally wiped to 0 later, so program_lpt_control always hits default and LPT_MODE is never programmed - while enable_lpt still arms LPT with stale values whenever FBC+LPT come up on eDP. banks/raw/dram_channels never set => lpt_size_alignment always 0. Upstream-inherited statement order. Register-programming logic only, no kernel memory corruption; semi-privileged Polaris eDP reach. Statement-for-statement logic mirror PROVEN on guest (both defects confirmed). Reorder-the-init fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4601 Β· 11 files
FileTypeDescriptionSize
construct_order.c β€” 4.3 KB view raw
build.sh β€” 145 B view raw
run.sh β€” 106 B view raw
build.log β€” 95 B view raw
run.log β€” 450 B view raw
env.txt β€” 188 B view raw
README.md β€” 3.5 KB ↓ raw
VERDICT.md β€” 3.7 KB ↓ raw
fix.diff β€” 1.6 KB view raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 3.5 KB view raw

DF-4601 β€” dce112_compressor_construct() init-order defects: dead 64-bit-bus LPT guard + lpt_channels_num wipe

File: sys/dev/drm/amd/display/dc/dce112/dce112_compressor.c (DCE11.2 / Polisar FBC+LPT engine) Severity: Info (correctness/dead-code; no demonstrated kernel memory-safety impact β€” register-programming logic only) Confidence: certain (both facts provable by source alone; demonstrator reproduces the logic)

What it is

Two initialization-ordering defects in dce112_compressor_construct() (dce112_compressor.c:787-829):

  1. Dead single-DRAM-channel guard (:802). The object is kzalloced by dce112_compressor_create() (:831-841), so memory_bus_width == 0 when dce112_compressor_construct() evaluates if (compressor->base.memory_bus_width == 64) at :802. The field is assigned its real value, ctx->asic_id.vram_width, only afterwards at :810. The guard's whole purpose ("only 1 DRAM channel -> LPT should not be supported") can therefore never fire: LPT_SUPPORT stays set on 64-bit-bus ASICs.

  2. lpt_channels_num wipe (:818). :797 sets lpt_channels_num = 1 ("For DCE 11 always use one DRAM channel for LPT"), then :818 unconditionally overwrites it with 0. Consequently the only valid case 1: of dce112_compressor_program_lpt_control() (:675-691) is unreachable β€” the switch always falls to default (:686-690) and LOW_POWER_TILING_MODE is never programmed by this driver; the register keeps whatever stale value it holds.

Knock-on (same construct): banks_num, raw_size, dram_channels_num, channel_interleave_size are zeroed at :814-817 and no code anywhere in the tree ever sets them (verified by grep across sys/dev/drm/amd/display/dc/), so lpt_size_alignment() (:98-103) always returns 0 and dce112_compressor_program_lpt_control() leaves LOW_POWER_TILING_ROWS_PER_CHAN at 0 (:708-714) and dce112_compressor_program_compressed_surface_address_and_pitch() skips the address alignment (:500-505) β€” yet dce112_compressor_enable_lpt() (:594-651) still arms LOW_POWER_TILING_ENABLE=1 and GMCON_LPT_TARGET = stale_mode+1 whenever FBC+LPT come up on eDP (dce110_hw_sequencer.c:enable_fbc -> enable_lpt when LPT_SUPPORT && paths_num<2 && wh <= 25601600).

Why no runtime reproduction on the audit guest

The QEMU guest has no AMD GPU (virtio only), so the DCE11.2 register path cannot execute there. The two defects are nevertheless pure static facts β€” no hardware-dependent behavior is needed to prove them β€” and the pack contains a host-compiled demonstrator that mirrors the exact statement order and asserts both. On-HW impact (LPT armed with unprogrammed config on Polaris eDP panels) is speculative and out of the audit's demonstrable scope; worst realistic case is display underflow / GPU hang (local DoS by a modeset-capable user), not kernel memory corruption. Filed as Info hardening for upstream.

Reproduce (host, no GPU)

./build.sh    # cc -O2 -Wall -o construct_order construct_order.c
./run.sh      # expects: PROOF-1 OK / PROOF-2 OK / RESULT: DEFECT CONFIRMED, exit 0

Fix

fix.diff (authored against the read-only sys/ tree, never applied there): assign memory_bus_width from asic_id.vram_width before the 64-bit-bus check, and set lpt_channels_num = 1 after the zeroing block instead of wiping it. Kernel-build validation on the guest is not possible (the amd drm driver cannot attach to QEMU virtio hardware), hence fix_status=not_testable.

VERDICT.md
↓ download raw

DF-4601 VERDICT β€” not_reproduced on guest (target ASIC absent); logic defect CONFIRMED by mirror; severity Info

The defect (static proof, no hardware needed)

dce112_compressor_construct() (dce112_compressor.c:787-829) runs on a kzalloced object (created at :831-841), so every field starts at 0:

  1. Dead guard. if (compressor->base.memory_bus_width == 64) at :802 is evaluated while the field is still 0; it is assigned its real value, ctx->asic_id.vram_width, only afterwards at :810. The guard whose comment says "if only 1 [DRAM channel] then LPT should not be supported" can therefore never fire β€” LPT_SUPPORT stays set on 64-bit-bus ASICs.
  2. Wiped field. lpt_channels_num = 1 at :797 ("For DCE 11 always use one DRAM channel for LPT") is unconditionally overwritten to 0 at :818 (part of the zero-everything block). No other code in the tree writes the field (verified across sys/dev/drm/amd/display/dc/), so at the only read β€” the switch in dce112_compressor_program_lpt_control() (:675-691) β€” it is always 0: the sole valid case 1: is unreachable and the switch always falls to default (:686), leaving LOW_POWER_TILING_MODE unprogrammed (stale HW value).

Knock-on (same construct + tree-wide grep): banks_num, raw_size, channel_interleave_size, dram_channels_num are zeroed (:814-817) and never set anywhere, so lpt_size_alignment() (:98-103) always returns 0: rows_per_channel stays 0 (:708-714) and the compressed-surface address alignment is skipped (:500-505) β€” while dce112_compressor_enable_lpt() (:594-651) can still arm LPT (LOW_POWER_TILING_ENABLE=1 at :645-650, GMCON_LPT_TARGET = stale_mode+1 at :628-640) whenever FBC+LPT come up on eDP (dce110_hw_sequencer.c:enable_fbc β†’ enable_lpt when LPT_SUPPORT && paths_num<2 && wh ≀ 25601600; fbc_gpu_addr IS set at runtime by amdgpu_dm.c:376, so the path is live on real hardware).

Verification performed

  • construct_order.c β€” a statement-for-statement userspace mirror of construct() + the switch β€” built and run on the audit guest (host has no compiler): both proofs hold, RESULT: DEFECT CONFIRMED, exit 0 (run.log). This is a logic proof, not a kernel-path execution.
  • Kernel-path execution is impossible on this guest: it has no AMD GPU (QEMU virtio only), so the DRM register path cannot run. Status therefore not_reproduced / impact none (the orchestrator's status enum has no feature-absent value; not_testable is disallowed).
  • Reachability is modeset-gated (/dev/dri, typically root/operator), eDP
  • FBC-memory + single-display preconditions further narrow it, and the consequence is register misconfiguration on the GPU (display underflow / possible hang β€” speculative), not kernel memory corruption. Filed at Info severity with certain confidence in the (provable) logic defect itself.

Fix validation

fix.diff (minimal reorder: assign memory_bus_width before the ==64 check; set lpt_channels_num = 1 after the zeroing block instead of wiping it) β€” cannot be kernel-validated on this guest (driver cannot attach), so fix_status = not_testable. The diff was authored against the pristine sys/ tree and applies by construction.

Guest state

Guest used only to compile/run the userspace mirror; returned clean via vm.sh reset with-src after evidence 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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Single-fix kernel validation impossible: the amd drm driver cannot attach to the QEMU virtio guest, so neither baseline nor patched kernels can exercise the path. fix.diff is a minimal reorder authored against the pristine sys/ tree (applies by construction).

fix.diff (assign memory_bus_width before the 64-bit-bus guard; stop wiping lpt_channels_num β€” set it to 1 after the zeroing block)
↓ fix.diffper-fix-DF-4601

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (PROOF-1 OK / PROOF-2 OK / KNOCK-ON alignment=0 / RESULT: DEFECT CONFIRMED, exit 0); README.md path:line walkthrough; fix.diff

PoC changes

n/a (demonstrator authored fresh; no seed PoC existed)

Verified recommended fix

In dce112_compressor_construct(), assign memory_bus_width from ctx->asic_id.vram_width BEFORE the ==64 check, and set lpt_channels_num=1 after the zeroing block instead of wiping it (fix.diff).

Verdict

Info-severity init-order defects, both provable statically and confirmed by a host-side logic mirror (construct_order.c): (1) dce112_compressor_construct() evaluates the 'single DRAM channel => no LPT' guard (memory_bus_width == 64, :802) while the field is still 0 (kzalloc'd object; assigned from asic_id.vram_width only at :810) β€” the guard is dead and LPT_SUPPORT is never cleared on 64-bit-bus ASICs; (2) lpt_channels_num=1 set at :797 is unconditionally wiped to 0 at :818, making the only valid case of the program_lpt_control() switch (:675-691) unreachable β€” LOW_POWER_TILING_MODE is never programmed. Knock-on: banks_num/raw_size/dram_channels_num are never set anywhere in the tree, so lpt_size_alignment() is always 0 and enable_lpt() (:594-651) can arm LPT (LOW_POWER_TILING_ENABLE=1, :645-650) with ROWS_PER_CHAN=0 and a stale MODE whenever FBC+LPT come up on eDP. Runtime kernel-path reproduction is impossible on the audit guest (no AMD GPU; QEMU virtio only), hence not_reproduced/impact none; on-HW consequence (display underflow / GPU hang on affected Polaris eDP systems) is speculative and requires modeset privilege (/dev/dri).