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

Config bridges forward unvalidated reg/width into the PCIe MMIO backend whose reg-only bound allows OOB read/write past the MMCFG kernel_map mapping

Summary

legacy_pcib_read/write_config pass caller reg/bytes with zero validation; the MMIO backend bound-checks only reg>4095 - never reg+bytes<=4096 nor alignment (which the port-I/O backend enforces) - so reg=0xfff/2 or 0xffd/4 crosses the pmap_mapdev MMCFG window: kernel page fault or a 1-3 byte OOB write into the adjacent kernel_map mapping. The one caller with chosen reg (/dev/pci PCIOCREAD/WRITE) sets no upper bound on pi_reg. Root-gated (SYSCAP_RESTRICTEDROOT); dormant on QEMU i440FX. KLD-proven with the exact legal pcie_cfgregopen call ACPI makes: control read at 0xffc/4 succeeds, 0xfff/2 -> Fatal trap 12 at exactly pcie_base+0x10000000; width/alignment/end-bound fix validated on rebuilt kernel (OOO rejected, no panic).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5454 Β· 13 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
df5454.c β€” 3.2 KB view raw
build.sh β€” 169 B view raw
run.sh β€” 120 B view raw
build.log β€” 5.7 KB view raw
run.log β€” 1.6 KB view raw
run_patched.log β€” 791 B view raw
panic.txt β€” 1008 B view raw
env.txt β€” 611 B view raw
fix.diff β€” 1.1 KB view raw
fix_build.log β€” 255 B view raw
verdict.json β€” 4.8 KB view raw

DF-5454 β€” PCIe MMIO config backend: missing reg+width bound β†’ OOB access past pcie_base devmap

File under audit (pass 2): sys/bus/pci/x86_64/pci_bus.c Sink: sys/bus/pci/x86_64/pci_cfgreg.c (pciereg_cfgread/pciereg_cfgwrite)

What

The legacy host-PCI bridge config bridges legacy_pcib_read_config / legacy_pcib_write_config (pci_bus.c:61-75) forward caller-supplied (bus, slot, func, reg, bytes) to pci_cfgregread/pci_cfgregwrite with no validation. When MMIO config space is active (CFGMECH_PCIE β€” Intel 7520/7320/915/925/915GM chipsets, pci_cfgreg.c:104-124, or any ACPI MCFG table, sys/dev/acpica/acpi.c:1669), the request lands in pciereg_cfgread/pciereg_cfgwrite (pci_cfgreg.c:315-365), which bound-check only reg > PCIE_REGMAX (4095). They never check reg + bytes <= 4096 and never check alignment β€” both of which the port-I/O backend pci_cfgenable (pci_cfgreg.c:187-193) enforces.

The MMCFG window is pmap_mapdev_uncacheable(base, (maxbus+1) << 20) (pci_cfgreg.c:278-279) β€” with maxbus=255 a 256MB kernel_map allocation. The highest mapped byte is (bus=255, slot=31, func=7, reg=0xfff). A 2-byte access at reg=0xfff or 4-byte access at reg=0xffd/0xffe reads/writes 1-3 bytes past the end of that kernel virtual allocation β†’ kernel page fault (panic) or silent corruption of the adjacent kernel mapping.

Reachability / threat model

  • /dev/pci PCIOCREAD/PCIOCWRITE (pci_user.c:602-673) checks pi_width in {1,2,4}, pi_reg >= 0, alignment β€” but sets no upper bound on pi_reg β€” then calls PCIB_READ_CONFIG/PCIB_WRITE_CONFIG β†’ these bridges. Opening /dev/pci requires SYSCAP_RESTRICTEDROOT (pci_user.c:82) β†’ root-only.
  • Kernel-internal callers keep reg <= 0xff (capability walk clamps/aligns via pci_fixup_nextptr, pci.c:602-630). No in-tree caller passes reg>0xff except the ioctl.
  • Requires CFGMECH_PCIE β€” dormant on QEMU i440FX (no MCFG; host bridge 8086:1237 not in the chipset list), active on most modern MCFG-bearing x86_64 hardware and on the listed Intel chipsets.

Net: privileged-user-triggerable kernel OOB read/write (1-3 bytes) past a kernel_map devmap allocation β†’ reliable panic (DoS), potential corruption of the adjacent kernel mapping. Severity: Low (already-privileged attacker).

Reproduce (this pack)

  1. build.sh β€” compile the KLD inside the guest (needs /usr/src, cc, make).
  2. run.sh β€” kldload /root/df5454/df5454.ko as root.

The KLD performs the exact legal pcie_cfgregopen() call the platform/ACPI makes at boot (activating CFGMECH_PCIE β€” dormant on the i440FX guest), then:

  • CONTROL: in-bounds read (255,31,7,0xffc,4) β€” succeeds, no fault.
  • step2: OOB read (255,31,7,0xfff,2) β€” Fatal trap 12, supervisor read, page not present, fault VA exactly pcie_base + 0x10000000.
  • step3/4: OOB write / 3-bytes-past read (only reached if step2 survives).

Expected (vulnerable kernel): panic at step2 β€” see panic.txt. Expected (patched kernel, fix.diff): no panic; step2 returns 0xffffffff (rejected), module load completes, guest stays up β€” see run_patched.log.

Fix

fix.diff β€” carry the mech-1 backend's width/alignment/end bound into pciereg_cfgread/pciereg_cfgwrite. Validated: baseline panics, patched kernel rejects the access and boots/runs normally.

VERDICT.md
↓ download raw

DF-5454 β€” VERDICT

Finding: legacy_pcib_read_config/legacy_pcib_write_config (sys/bus/pci/x86_64/pci_bus.c:61-75) forward caller-supplied (reg, bytes) into pci_cfgregread/pci_cfgregwrite with no validation; when MMIO config space is active (CFGMECH_PCIE), the backend pciereg_cfgread/ pciereg_cfgwrite (sys/bus/pci/x86_64/pci_cfgreg.c:315-365) bound-checks only reg > PCIE_REGMAX (4095) β€” it never checks reg + bytes <= 4096 nor alignment, both of which the port-I/O backend pci_cfgenable (pci_cfgreg.c:187-193) enforces. The MMCFG window is a pmap_mapdev_uncacheable(base, (maxbus+1)<<20) kernel_map allocation (pci_cfgreg.c:278-279; pmap.c:6174-6217 kmem_alloc_nofault); its highest mapped byte is (bus=255, slot=31, func=7, reg=0xfff). A width-2 access at reg=0xfff (or width-4 at reg=0xffd/0xffe) reads/writes 1-3 bytes past the end of that allocation.

Reproduced β€” how

  1. Baseline (vulnerable kernel, snapshot with-src, kernel #0 Jul 2 2026): - KLD df5454.ko (this pack) performs the exact legal call a platform makes at boot to activate MMIO config β€” pcie_cfgregopen(0x40000000, 0, 255), the same call ACPI MCFG handling makes at sys/dev/acpica/acpi.c:1669 (the QEMU i440FX guest has no MCFG and an unlisted host bridge 8086:1237, so the backend is dormant on stock boot; the KLD removes that environmental precondition only). - CONTROL: in-bounds read (255,31,7,0xffc,4) β€” succeeds (0x00000000), proving the mapping is healthy up to its final byte. - OOB: read (255,31,7,0xfff,2) β†’ Fatal trap 12: page fault while in kernel mode, supervisor read data, page not present, fault VA 0xfffff80128660000 = pcie_base + 0x10000000 exactly (mapping base 0xfffff80118660000), stopped at pci_docfgregread+0xb4: movzwl (%rax),%eax β€” the inlined *(volatile uint16_t *) load of pciereg_cfgread (see panic.txt, run.log). - Guest wedged in DDB; ssh never returned (rc 124). That is reproduced, impact panic, from a root-triggered KLD.

  2. Patched (fix.diff applied, nativekernel build, kernel #1 Oct 5 18:56): - Same KLD, same commands: CONTROL read still OK (0x0000000b), OOB read at 0xfff/2 rejected β†’ returns 0xffffffff, OOB write rejected (no-op), 0xffd/4 rejected, KLDLOAD_RC=0, guest stayed up (see run_patched.log). Bad behavior GONE β†’ fix validated.

Why root-gated (severity Low, not higher)

  • The only caller that supplies attacker-chosen reg is /dev/pci PCIOCREAD/PCIOCWRITE (sys/bus/pci/pci_user.c:602-673): it validates width∈{1,2,4}, pi_reg >= 0 and alignment, but sets no upper bound on pi_reg β€” however opening /dev/pci requires caps_priv_check_self(SYSCAP_RESTRICTEDROOT) (pci_user.c:82), i.e. root.
  • All kernel-internal callers keep reg <= 0xff aligned (capability walk: pci_fixup_nextptr, sys/bus/pci/pci.c:602-630).
  • Activation requires CFGMECH_PCIE (Intel 7520/7320/915/925/915GM chipsets at pci_cfgreg.c:108-123, or ACPI MCFG at sys/dev/acpica/acpi.c:1669) β€” i.e. most modern MCFG-bearing hardware, dormant on QEMU i440FX.

An already-privileged user gets: reliable kernel panic (DoS) and a 1-3 byte OOB write primitive into whatever kernel mapping follows the MMCFG window in kernel_map β€” corruption, not controllable enough for privilege escalation beyond what the trigger already requires. No unprivileged chain exists (open of /dev/pci is capability-gated). uid=0 escalation: not applicable (trigger requires root).

Conclusion

Real, proven missing-bounds defect in the x86_64 PCI config path β€” filed against sys/bus/pci/x86_64/pci_bus.c (the unvalidated pass-through bridges are this file's code; the sink and the fix live in the sibling backend pci_cfgreg.c, which pass 1 marked clean). Severity Low (privileged trigger), confidence certain, reproduced as panic, fix validated end-to-end (build + boot + re-run).

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied to guest /usr/src (patch -p1, hunks at 320/348), kernel rebuilt with make nativekernel KERNCONF=X86_64_GENERIC and installed (BUILD_RC=0, INSTALL_RC=0), rebooted, exact same PoC re-run: control read unaffected, all OOB accesses rejected (0xffffffff / no-op), KLDLOAD_RC=0, no panic, guest stayed up. Baseline (unpatched) panicked at the same step. Bad behavior gone.

fix.diff; fix_build.log (BUILD_RC=0 / INSTALL_RC=0, kernel #1: Mon Oct 5 18:56:47 2026); run_patched.log (rejected OOB accesses, guest alive); panic.txt + run.log (baseline panic)
↓ fix.diffper-fix-DF-5454

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (Fatal trap 12, fault VA 0xfffff80128660000 == pcie_base+256MB, stopped at pci_docfgregread+0xb4 movzwl); run.log (baseline: control read 0xffc/4 ok then step2 panic); run_patched.log (same PoC on fix.diff kernel: step2 returns 0xffffffff rejected, KLDLOAD_RC=0, guest up); df5454.c (PoC source); fix.diff (validated fix); env.txt (guest 6.5-DEVELOPMENT X86_64_GENERIC, i440FX, no MCFG line in dmesg)

PoC changes

authored fresh (no seed pack existed): KLD performs the legal pcie_cfgregopen() call ACPI MCFG makes at boot to activate the dormant CFGMECH_PCIE backend on the i440FX guest, then issues control (0xffc/4) and OOB (0xfff/2, 0xfff/2 write, 0xffd/4) config accesses via the exported pci_cfgregread/write

Verified recommended fix

In pciereg_cfgread/pciereg_cfgwrite (pci_cfgreg.c) require bytes in {1,2,4}, reg aligned to bytes, and reg+bytes <= PCIE_REGMAX+1 β€” same policy the mech-1 backend pci_cfgenable already enforces.

Verdict

The legacy host-PCI config bridges in sys/bus/pci/x86_64/pci_bus.c:61-75 forward caller-supplied (reg,bytes) with no validation into the PCIe MMIO backend pciereg_cfgread/cfgwrite (pci_cfgreg.c:315-365), which checks only reg>4095 and never reg+bytes<=4096 nor alignment (the port-I/O backend pci_cfgenable at pci_cfgreg.c:187-193 checks both). A 2-byte access at reg=0xfff on (bus=pcie_maxbus,slot=31,func=7) reads/writes 1-3 bytes past the 256MB pmap_mapdev_uncacheable MMCFG kernel_map allocation. Proven live on the QEMU guest with a KLD that makes the same legal pcie_cfgregopen() call ACPI MCFG handling makes at boot (acpi.c:1669; the i440FX guest boots with the MMIO backend dormant): control read at reg=0xffc/4 succeeds, read at reg=0xfff/2 panics instantly (Fatal trap 12, supervisor read, fault VA exactly pcie_base+0x10000000, stopped at the inlined volatile uint16_t load in pci_docfgregread+0xb4). Root-gated: the only attacker-regulated path, /dev/pci PCIOCREAD/PCIOCWRITE, validates width/alignment but no upper bound on pi_reg, yet /dev/pci open requires SYSCAP_RESTRICTEDROOT (pci_user.c:82). Impact ceiling for the (already-privileged) trigger: reliable kernel panic plus a 1-3 byte OOB write into the kernel_map neighbor of the MMCFG window. No unprivileged chain exists. fix.diff (carry width/alignment/end-bound into the MMIO backend) validated end-to-end: patched kernel rejects all OOB accesses (0xffffffff / silent no-op), control read unaffected, guest stays up.