Signed left-shift undefined behavior in BAR resource sizing when ln2size == 31
Summary
pci_add_map() computes resource sizes as 1<<ln2size where literal 1 is signed int and ln2size can be up to 31 (from pci_mapsize on uint32_t BAR decoding 2 GiB). Shifting signed int into sign bit (1<<31) is UB in C. Typically produces INT_MIN (-2147483648) which when assigned to uint64_t count produces 0xFFFFFFFF80000000 causing wildly incorrect resource sizing. end=base+(1<<31)-1 wraps to value less than base producing inverted start/end range. Malicious PCI hardware reporting 2 GiB memory BAR. Functional DoS device BAR not programmed. [VERIFIED pass2: reproduced - ln2size 31 yields count 0xffffffff80000000 end 0xffffffffbfffffff accepted by real resource_list_add, deterministic under shipped -fno-strict-overflow; controls at 30 16 correct; 1ULL fix-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2325 Β· 20 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.7 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| build.log | β | 531 B | view raw | |
| env.txt | β | 2.9 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| rig/bar_capture.log | β | 1.4 KB | view raw | |
| rig/build.sh | β | 2.1 KB | view raw | |
| rig/cflags_evidence.txt | β | 236 B | view raw | |
| rig/check_verbatim.sh | β | 801 B | view raw | |
| rig/check_verbatim_fixed.sh | β | 801 B | view raw | |
| rig/dfpci_post.c | β | 25.2 KB | view raw | |
| rig/dfpci_pre.c | β | 8.8 KB | view raw | |
| rig/fix.diff | β | 4.4 KB | view raw | |
| rig/fixed_runs.log | β | 6.0 KB | view raw | |
| rig/gen.sh | β | 2.6 KB | view raw | |
| rig/gen_fixed.sh | β | 2.6 KB | view raw | |
| rig/mk_extract.py | β | 4.7 KB | view raw | |
| rig/run_2325.sh | β | 705 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-2325 β signed left-shift UB in pci_add_map BAR sizing at ln2size 31 β evidence pack
Finding: sys/bus/pci/pci.c:2644,2650 β count = 1 << ln2size; and
end = base + (1 << ln2size) - 1; shift a signed int literal into
the sign bit at ln2size 31 (2 GiB BAR) β C11 6.5.7p4 UB β and the
sign-extended result poisons the u64 resource range.
Verdict (short)
REPRODUCED on the stock INVARIANTS kernel through the verbatim
pci_add_map/pci_mapsize: a fabricated BAR decoding 2 GiB yields
count=0xffffffff80000000, end=0xffffffffbfffffff (expected
0x80000000 / 0xbfffffff); controls at 1 GiB and 64 KiB are
correct; the REAL resource_list_add accepts the garbage range into
the device list. Kernel CFLAGS (-fno-strict-overflow, no
-fwrapv) make the wrap deterministic on this build (cc 8.3).
Impact is a permanently broken BAR reservation for 2 GiB-BAR devices
(correctness/availability; no memory-safety effect). Fix A/B validated:
1ULL shifts produce correct values at 31 with lower sizes unchanged.
Rig
Shared with DF-2323 (same verbatim-interposition kernel-compiled harness; verbatim sha256 gates; see DF-2323/rig). BAR sizing probe emulated at the interposed PCIB_WRITE/READ_CONFIG boundary; computed (start,end,count) captured at the interposed resource_list_add.
Reproduce
sh rig/run_2325.sh β see rig/bar_capture.log (baseline) and
rig/fixed_runs.log (fixed kernel, DFPCIPF).
Files
- VERDICT.md β narrative + value table + CFLAGS determinism
- rig/bar_capture.log β baseline captures (stock kernel)
- rig/fixed_runs.log β fixed-kernel captures
- rig/cflags_evidence.txt β -fno-strict-overflow, no -fwrapv
- rig/ β shared harness + fix.diff
- env.txt, manifest.json, verdict.json
DF-2325 β signed 1 << ln2size UB in BAR sizing at ln2size 31 β VERDICT
status: reproduced / impact: none at the kernel-memory-safety level (wrong computed resource range: count/end wildly wrong for a 2 GiB BAR; the bogus range is accepted into the resource list; no corruption demonstrated or claimed).
Line-accurate analysis (verified mechanically)
pci_add_map computes, with ln2size from the verbatim
pci_mapsize (pci.c:451-466) driven by the BAR sizing probe:
count = 1 << ln2size; /* pci.c:2644 */
...
end = base + (1 << ln2size) - 1; /* pci.c:2650 */
count/end are pci_addr_t (uint64_t, pcivar.h:48). At
ln2size == 31 (2 GiB BAR: sizing readback 0x80000000) the int
literal 1 << 31 overflows into the sign bit β C11 6.5.7p4 UB. On
this kernel build (cc 8.3, -O2 -fno-strict-overflow, no -fwrapv β
rig/cflags_evidence.txt) the shift compiles to a plain shl and wraps
deterministically: (int)0x80000000 -> conversion to uint64_t
SIGN-EXTENDS.
Observed through the verbatim pci_add_map + interposed
resource_list_add (the REAL resource_list_add was also called in the
realadd variant β it accepted the range into the list):
| ln2size | count (computed) | end (computed) | verdict |
|---|---|---|---|
| 16 (64 KiB) | 0x10000 | 0x4000ffff | correct |
| 30 (1 GiB) | 0x40000000 | 0x7fffffff | correct |
| 31 (2 GiB) | 0xffffffff80000000 | 0xffffffffbfffffff | wrong (expected count 0x80000000, end 0xbfffffff) |
The wrong count is ~2^64-2^31 and end lands 4 GiB above the 32-bit
space after wrap β the rman reservation for the BAR can never succeed
with such a count (and the range entry is nonsense); downstream the
BAR is simply never correctly reserved/mapped (device malfunction), it
is not a memory-corruption primitive. Determinism note: the shipped
CFLAGS include -fno-strict-overflow (signed wrap is two's-complement
in this build), and gcc emits shl regardless β the mis-sizing is
deterministic on this kernel, while remaining UB per the standard
(gcc's shift-into-sign-bit is exactly the case -fwrapv-style flags
make reproducible in practice).
Threat model: malicious PCI device decoding a 2 GiB BAR (AV:P); ln2size 31 is also reachable with real 2 GiB-BAR hardware (correct sizing of a perfectly legitimate device is also broken β this is a correctness bug first).
Fix (validated A/B)
rig/fix.diff DF-2325 hunk:
- count = 1 << ln2size;
+ count = 1ULL << ln2size;
...
- end = base + (1 << ln2size) - 1;
+ end = base + (1ULL << ln2size) - 1;
A/B on DFPCIPF: ln2size 31 now computes count=0x80000000, end=0xbfffffff (correct); ln2size 16/30 unchanged (0x10000/0x40000000) β rig/fixed_runs.log.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
rig/bar_capture.log (stock kernel: bar 16/30 correct, bar 31 count=0xffffffff80000000 end=0xffffffffbfffffff, realadd accepts it), rig/fixed_runs.log (fixed: bar 31 count=0x80000000 end=0xbfffffff, controls unchanged), rig/cflags_evidence.txt (-fno-strict-overflow, no -fwrapv)
PoC changes
verbatim pci_add_map + helpers via the shared DFPCI harness (see DF-2323 pack); BAR sizing probe answered by the interposed PCIB_WRITE/READ_CONFIG (write 0xffffffff -> next read returns the crafted mask), computed (start,end,count) captured at the interposed resource_list_add
Verified recommended fix
use 1ULL << ln2size in both the count and end computations of pci_add_map
Verdict
Reproduced mechanically on the stock INVARIANTS kernel: the verbatim pci_add_map/pci_mapsize driven with a fabricated 2 GiB BAR (sizing readback 0x80000000 -> ln2size 31) computed count=0xffffffff80000000 and end=0xffffffffbfffffff (sign-extended INT_MIN from the UB int shift at pci.c:2644/2650), versus the correct 0x80000000/0xbfffffff - exactly the inverted/garbage range the finding claims. Controls at ln2size 30 (1 GiB) and 16 (64 KiB) computed correctly, isolating the boundary. The shipped kernel CFLAGS (-O2 -fno-strict-overflow, no -fwrapv; cc 8.3) make the wrap deterministic in this build (cflags_evidence.txt) while remaining UB per C11 6.5.7p4. The REAL resource_list_add accepted the bogus range into the device resource list (realadd run), so the entry exists with a ~2^64 count; no memory corruption is claimed or needed - the practical impact is a permanently broken BAR reservation/mapping for 2 GiB-BAR devices (device malfunction, availability), hence impact recorded as none at the kernel memory-safety level with this note. Fix A/B validated: 1ULL shifts give the correct values at 31 with 16/30 bit-identical.
No comments yet.