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)
PoC verification
Evidence pack
findings/poc/DF-4348 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 vs0x00ffff00). 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.
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_shlatom.c:958 βdst <<= shiftwhereshift = atom_get_src(...)is a full 32-bit VBIOS-controlled value.atom_op_shratom.c:977 βdst >>= shift, same.atom_op_shift_leftatom.c:925 βdst <<= shift, shift is u8 (β€255).atom_op_shift_rightatom.c:941 βdst >>= shift, u8.- WS masks atom.c:241/244 β
1 << gctx->shift/~(1 << gctx->shift)wheregctx->shiftis assigned from a full u32 write (ATOM_WS_SHIFT, atom.c:508) into auint8_tmember (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 << 40compiled 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.>> 32folded away entirely (full value passed through as the mask).1 << 200folded 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
inconclusivefix.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
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.
No comments yet.