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

Undefined shift counts throughout the amdgpu ATOM interpreter: VBIOS-controlled shift operands (up to full 32-bit) used unmasked in shl/shr/shift ops, WS OR/AND masks and IIO width masks (DF-4340's amdgpu twin)

Summary

Shift counts are untrusted VBIOS values used unmasked: the shl/shr ops shift by a full 32-bit operand; the WS masks use gctx->shift (u8 truncated from a u32 write); the IIO masks compute >>(32-width) with width 0. Counts >= width are UB - the guest's gcc constant-folds to a DIFFERENT result than runtime x86 masking, making interpreter-computed register values compiler-version dependent. Guest demo shows concrete divergence. atom_shl/shr helper fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4348 Β· 14 files
FileTypeDescriptionSize
README.md β€” 1.0 KB ↓ raw
VERDICT.md β€” 2.8 KB ↓ raw
atom_harness.c β€” 14.3 KB view raw
atom_harness_fixed.c β€” 11.4 KB view raw
build.sh β€” 95 B view raw
run.sh β€” 101 B view raw
run_fixed.sh β€” 233 B view raw
build.log β€” 26 B view raw
run.log β€” 815 B view raw
fix_run.log β€” 1.1 KB view raw
env.txt β€” 382 B view raw
fix.diff β€” 2.2 KB view raw
manifest.json β€” 873 B view raw
verdict.json β€” 2.8 KB view raw

DF-4348 β€” undefined shift counts throughout the amdgpu ATOM interpreter (VBIOS-controlled, up to full 32-bit)

sys/dev/drm/amd/amdgpu/atom.c uses VBIOS-controlled shift counts unmasked: atom_op_shl/shr (atom.c:958/977, full 32-bit operand), atom_op_shift_left/right (atom.c:925/941, u8), the WS OR/AND masks (atom.c:241/244, gctx->shift truncated from a u32 write at :508), and the IIO width masks (atom.c:121-157, >> (32 - width) with width 0 or

32). Counts β‰₯ 32 are UB; on the audit guest's gcc 8.3 -O1 the same expression constant-folds to a different result than runtime x86 masking produces (0xffff << 40 β†’ 0 vs 0x00ffff00). Amdgpu twin of DF-4340 (radeon). Impact: compiler-dependent GPU register values β€” hardening/correctness, not memory safety.

Reproduce (logic level)

cc -O1 -g -o atom_harness atom_harness.c
./atom_harness shift   # UB divergence report (exit 0)

Fix

fix.diff β€” atom_shl/atom_shr (shift β‰₯ 32 β‡’ 0) at the four op sites + WS masks; IIO width masks noted as residual.

VERDICT.md
↓ download raw

DF-4348 VERDICT β€” undefined shift counts throughout the amdgpu ATOM interpreter (DF-4340's amdgpu twin)

Status: inconclusive (kernel runtime hardware-gated; UB divergence demonstrated on the audit guest compiler). Defect: certain (UB), impact low/hardening.

Root cause

Shift counts are untrusted VBIOS values used unmasked (all in sys/dev/drm/amd/amdgpu/atom.c):

  • atom_op_shl atom.c:958 β€” dst <<= shift where shift = atom_get_src(...) is a full 32-bit VBIOS-controlled value.
  • atom_op_shr atom.c:977 β€” dst >>= shift, same.
  • atom_op_shift_left atom.c:925 β€” dst <<= shift, shift is u8 (≀255).
  • atom_op_shift_right atom.c:941 β€” dst >>= shift, u8.
  • WS masks atom.c:241/244 β€” 1 << gctx->shift / ~(1 << gctx->shift) where gctx->shift is assigned from a full u32 write (ATOM_WS_SHIFT, atom.c:508) into a uint8_t member (atom.h:136) β€” so 0..255, still β‰₯ 32 possible.
  • IIO masks atom.c:121/127/133/143/147/157 β€” 0xFFFFFFFF >> (32 - CU8(base+1)): width 0 β†’ >> 32, width > 32 β†’ negative shift count.

In C, shift counts β‰₯ width (or negative) are undefined; compilers may constant-fold differently from runtime hardware masking, and the language gives no guarantee the x86 shl %cl count&31 behavior is what executes.

Demonstration (guest gcc 8.3, -O1)

./atom_harness shift (exit 0, run.log):

(0xffff << 40)=0x00000000  vs (0xffff << 8)=0x00ffff00  -> divergent
IIO mask width=0: >>32 gives 0x12345678 (UB relied upon)
WS OR_MASK shift=200: 1<<200 = 0x00000000 (UB relied upon)
  • 0xffff << 40 compiled to 0 (constant-folded), while the runtime x86 semantics mask 40β†’8 and would give 0x00ffff00 β€” the same source expression yields two different results depending on folding: the interpreter's computed register/MMIO values are compiler-version dependent.
  • >> 32 folded away entirely (full value passed through as the mask).
  • 1 << 200 folded to 0.

Impact: wrong values written to GPU registers (MMIO writes through atom_put_dst), wrong PLL/mask math β€” a correctness/hardening hazard, not a memory-safety primitive by itself (no OOB results from the shift value directly).

Fix

fix.diff β€” port of DF-4340's fix: atom_shl/atom_shr helpers (shift β‰₯ 32 β‡’ 0) applied at shl/shr/shift_left/shift_right and the WS OR/AND masks. IIO width-mask sites are additionally noted as residual hardening (the central DF-4345 port's in_bios gates bound the IIO pointers, not these width values; mask them the same way when touching the file).

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

inconclusive
baseline no→ patch + rebuild →patched clean

fix.diff (atom_shl/atom_shr port) passes git apply --check; the fixed-logic demo returns defined values for the same inputs. No kernel runtime possible on this guest.

fix.diff; fix_run.log case shift
↓ fix.diffper-fix-DF-4348

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log case shift (divergence report: <<40 folds to 0, >>32 mask dropped, 1<<200 = 0); fix.diff (atom_shl/atom_shr port, git apply --check OK); fix_run.log case shift (defined semantics)

PoC changes

shift case adds the amdgpu shl full-u32 operand and the u8-truncated gctx->shift (200) demonstrations on top of DF-4340's

Verified recommended fix

Define the hardware semantics (shift >= 32 yields 0) via atom_shl/atom_shr helpers used at the four shift op sites and the WS OR/AND masks; mask IIO width bytes the same way

Verdict

UB certain, impact low (hardening/correctness): amdgpu ATOM shift counts are untrusted VBIOS values used unmasked -- atom_op_shl/shr (atom.c:958/977) shift by a full 32-bit operand, shift_left/right (atom.c:925/941) by a u8, WS OR/AND masks (atom.c:241/244) use gctx->shift (u8 truncated from a u32 write at atom.c:508), and the IIO masks (atom.c:121-157) compute 0xFFFFFFFF >> (32 - width) with width 0 or >32. Counts >= width are undefined; on the audit guest's gcc 8.3 -O1 the same expression constant-folds to a DIFFERENT result than runtime x86 masking (0xffff << 40 folds to 0 while masking semantics give 0xffff << 8 = 0x00ffff00; >> 32 folds away entirely) -- the interpreter's computed register/MMIO values are compiler-version dependent. amdgpu twin of DF-4340 (radeon); previously unfiled for amdgpu. No memory-safety primitive by itself.