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)
PoC verification
Evidence pack
findings/poc/DF-2326 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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_testableConfirmed 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).
No comments yet.