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

pci_devq traversal in PCIOCGETCONF is unprotected: pci_token is single-taker races pci_freecfg (UAF)

Summary

PCIOCGETCONF acquires lwkt_token pci_token (line 332) and STAILQ-traverses global pci_devq dereferencing each pci_devinfo. But pci_token is declared static in this file (line 59) taken NOWHERE else in sys/ tree. Meanwhile pci_read_device() inserts into pci_devq and pci_freecfg() does STAILQ_REMOVE+kfree neither taking the token. pci_freecfg reachable from CardBus hot-plug. Token serializes nothing and concurrent device removal frees pci_devinfo that ioctl loop still dereferences - UAF. Loop dereferences dinfo->cfg.dev device_get_name() dinfo->conf fields copyout. CardBus eject/hot-unplug while root runs PCIOCGETCONF. Impact: kernel panic local DoS by already-privileged user. [VERIFIED pass2: statically confirmed - single-taker pci_token traversal vs unserialized pci_freecfg with unpriv 0644 read side, BUT zero CardBus bridges on the guest so the free side cannot run; hardware-gated, fix sketched in VERDICT]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2326 Β· 3 files
FileTypeDescriptionSize
VERDICT.md β€” 1.7 KB ↓ raw
verdict.json β€” 2.5 KB view raw
README.md β€” 718 B ↓ raw

DF-2326 β€” PCIOCGETCONF traversal of pci_devq is unserialized vs pci_freecfg (UAF)

Verdict: source-confirmed, hardware-gated on this guest (no CardBus). See VERDICT.md: static pci_token is a single taker (pci_user.c:59,332, 753; nm shows it as local 'd'), pci_freecfg (pci.c:2139-2162) mutates the list with no lock and is called only from CardBus hot-unplug (cardbus.c:191,246), while the ioctl loop (:463-553) dereferences every entry β€” and /dev/pci is 0644 (unprivileged traversal side!). No CardBus bridge exists on the QEMU guest (pciconf in env.txt: 0 matches), so the free side of the race cannot be produced here. fix.diff sketch in VERDICT.md (token around the devq mutations); no A/B run possible.

VERDICT.md
↓ download raw

DF-2326 -- verification verdict

Status: not_reproduced (impact: none, confidence: likely)

NOT REPRODUCED LIVE -- hardware-gated: the free side of the race does not exist on this guest. Source defect fully confirmed: pci_token is file-static (pci_user.c:59) and its only takers in the whole tree are pci_ioctl (pci_user.c:332 gettoken / :753 reltoken) -- nm on the running kernel shows it as local d -- while pci_freecfg (pci.c:2139-2162) STAILQ_REMOVEs + kfrees pci_devinfo with no lock at all, and the PCIOCGETCONF walk (:463-553) dereferences dinfo->cfg.dev (:474), dinfo->conf fields and copyouts them. /dev/pci is 0644 (world-readable) and GETCONF needs no FWRITE, so the traversal side is unprivileged. But pci_freecfg is reachable only from CardBus hot-unplug (sys/dev/pccard/cardbus/cardbus.c:191,246) and this QEMU guest has zero CardBus bridges (pciconf: no class 0607), so the UAF cannot be triggered here by any actor. On CardBus-capable hardware the window is wide (full-list walk with per-entry copyout vs constant-time free).

Fix validation

not_testable on this guest (no CardBus controller: the free side of the race cannot run).

Machine verdict

See verdict.json (schema-matched to audit/persist_poc.py). Key evidence files are listed in manifest.json.

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-2326

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md + env.txt: static token single-taker proof (source grep + nm local symbol), pci_freecfg caller census, /dev/pci 0644 perms, pciconf CardBus census (0)

PoC changes

No PoC possible on this guest; static verification pack only (env.txt captures the running-kernel facts).

Verified recommended fix

Serialize pci_devq mutations with the same token the ioctl uses (fix sketch in VERDICT.md: lwkt_gettoken around STAILQ_REMOVE/INSERT in pci_freecfg and pci_add_device).

Verdict

NOT REPRODUCED LIVE -- hardware-gated: the free side of the race does not exist on this guest. Source defect fully confirmed: pci_token is file-static (pci_user.c:59) and its only takers in the whole tree are pci_ioctl (pci_user.c:332 gettoken / :753 reltoken) -- nm on the running kernel shows it as local d -- while pci_freecfg (pci.c:2139-2162) STAILQ_REMOVEs + kfrees pci_devinfo with no lock at all, and the PCIOCGETCONF walk (:463-553) dereferences dinfo->cfg.dev (:474), dinfo->conf fields and copyouts them. /dev/pci is 0644 (world-readable) and GETCONF needs no FWRITE, so the traversal side is unprivileged. But pci_freecfg is reachable only from CardBus hot-unplug (sys/dev/pccard/cardbus/cardbus.c:191,246) and this QEMU guest has zero CardBus bridges (pciconf: no class 0607), so the UAF cannot be triggered here by any actor. On CardBus-capable hardware the window is wide (full-list walk with per-entry copyout vs constant-time free).