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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2325 Β· 20 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download raw

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_testable
per-fix-DF-2325

Confirmed 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.