NULL-pointer dereference in pccard_child_pnpinfo_str via pccard_safe_quote on cards lacking a valid CISTPL_VERS_1
Summary
pccard_child_pnpinfo_str() passes sc->card.cis1_info[0] and [1] directly to pccard_safe_quote() with no NULL check. These pointers remain NULL when inserted card CIS omits CISTPL_VERS_1 or fails to emit null-terminated string before 0xff terminator. CIS parser sets NO error flag in that case so pccard_attach_card proceeds and creates child devices. pccard_safe_quote() dereferences *src unconditionally at :1001. Panics automatically when any driver attaches to such a child (via devadded -> bus_child_pnpinfo_str) and re-triggerable on demand by any unprivileged local user reading child pnpinfo sysctl. Every other consumer (pccard_set_default_descr:159 pccard_do_product_lookup:374 pccard_probe_nomatch:974 pccard_print_cis) guards against NULL; this one does not.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2318 Β· 25 files| File | Type | Description | Size | |
|---|---|---|---|---|
| rig2_main.c | β | 18.2 KB | view raw | |
| rig2_shims.h | β | 13.8 KB | view raw | |
| extract2.sh | β | 8.1 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 106 B | view raw | |
| run_matrix.sh | β | 1.9 KB | view raw | |
| run.log | β | 20.7 KB | view raw | |
| run.2.log | β | 20.7 KB | view raw | |
| run.3.log | β | 20.7 KB | view raw | |
| disasm_proof.txt | β | 6.4 KB | view raw | |
| build.log | β | 2.5 KB | view raw | |
| extract2_manifest.txt | β | 2.4 KB | view raw | |
| matrix.log | β | 1.6 KB | view raw | |
| env.txt | β | 323 B | view raw | |
| fix.diff | β | 743 B | view raw | |
| fixes/df2318.diff | β | 743 B | view raw | |
| fixes/df5355.diff | β | 1.1 KB | view raw | |
| fixes/df5356.diff | β | 601 B | view raw | |
| kernel_apply.log | β | 231 B | view raw | |
| kernel_build.tail.log | β | 4.6 KB | view raw | |
| postboot_extract_manifest.txt | β | 2.4 KB | view raw | |
| postboot_run.log | β | 16.8 KB | view raw | |
| VERDICT.md | β | 7.3 KB | β raw | |
| README.md | β | 1.7 KB | β raw | |
| verdict.json | β | 5.8 KB | view raw |
DF-2318 -- pccard_child_pnpinfo_str NULL deref (no CISTPL_VERS_1)
Impact: kernel panic at evil-card insertion; re-triggerable on demand by
any unprivileged local user (devinfo -v / sysctl hw.bus.devices) while
the card is seated. Physical-access DoS (16-bit PC Card socket).
Reproduce
The rig lives in this directory; it machine-extracts the production
pccard.c / subr_bus.c / pccard_cis.c code byte-identical from the
guest's /usr/src (sha256-gated) and drives the real entry
pccard_attach_card() with crafted CIS images.
# on the DragonFly guest (pristine /usr/src): sh build.sh # extract + cc; prints BYTE_IDENTICAL=yes ./run.sh # all scenarios
Success criterion (baseline):
SCENARIO no_vers1_devadded ... RIG SIGSEGV si_addr=0x0 (signal 11) <- DF-2318 SCENARIO no_vers1_nomatch ... RIG SIGSEGV si_addr=0x0 <- DF-2318 SCENARIO no_vers1_sysctl ... RIG SIGSEGV si_addr=0x0 <- DF-2318 SCENARIO vers1_pnpinfo_ok ... exit 0 (negative control) SCENARIO busypanic ... RIG KERNEL PANIC: resource entry is busy + exit 42 (DF-5355) SCENARIO reentry_leak ... exit 0, leak=19176 bytes after 51 bounce cycles (DF-5356) SCENARIO benign_cycle ... exit 0, leak=0 (negative control)
The same binary covers sibling findings DF-5355 (busypanic) and DF-5356 (reentry_leak); see their packs for their own verdicts.
Fix A/B
sh run_matrix.sh matrix # baseline + f2318 + f5355 + f5356 variants
f2318 flips exactly the three no_vers1 scenarios to clean exit 0
(cisvendor="" cisproduct="" strings built); all other scenarios unchanged.
fix.diff == fixes/df2318.diff (validated, never applied to sys/).
DF-2318 -- VERDICT
Verdict in one line
REPRODUCED (verbatim-rig level; deterministic across 3/3 runs) -- impact: panic.
pccard_safe_quote() dereferences sc->card.cis1_info[0]/[1] == NULL at
sys/bus/pccard/pccard.c:1001 inside pccard_child_pnpinfo_str() (:1020-1021);
the kernel entry path is hardware-gated (no PCMCIA bridge in QEMU -- env.txt),
so the byte-identical production pccard.c code was machine-extracted from the
guest's own /usr/src and driven through the real entry pccard_attach_card()
with the real devadded/devnomatch/sysctl-trigger surfaces.
What was executed (the rig, DF-2319 protocol)
extract2.shsed/awk-extracts, sha256-gated against the pristine audit tree (pccard.c=5b610669...), byte-identical (cross-checked two ways,PCCARD_BYTE_IDENTICAL=yesin extract2_manifest.txt):pccard.c: DPRINTF set +pccard_ccr_read/write,pccard_set_default_descr,pccard_attach_card,pccard_detach_card,pccard_function_init/_free/ _enable/_disable,pccard_mfc_adjust_iobase,pccard_safe_quote,pccard_child_pnpinfo_str,pccard_read_ivar(verbatim).kern/subr_bus.c:resource_list_init/free/add/find/delete+bus_child_pnpinfo_str(verbatim;SUBR_BUS_BYTE_IDENTICAL=yes).pccard_cis.cparser (verbatim; the same ranges DF-2319 validated).pccardvar.hstructs/macros + constant headers (verbatim ranges).rig2_shims.hemulates ONLY the kernel environment (device tree, bus_alloc_resource bridge-window model, rman, power/CARD methods, kmalloc ledger).device_probe_and_attach()mirrors production subr_bus.c semantics: attach success ->devadded(dev)->bus_child_pnpinfo_str()(subr_bus.c:2107-2114, :633-645); probe failure ->devnomatch(dev)does the same (subr_bus.c:1984/2039 -> :673); the sysctl walk (hw.bus.devices, read bydevinfo(8)at subr_bus.c:3863-3899, world readable -- verified live as usermaxxon this guest) asks every device for its pnpinfo string.
The reproduced defect chain
CIS image: MANFID + CONFIG + CFTABLE_ENTRY(io) -- a perfectly legal card
that simply omits CISTPL_VERS_1.
- The CIS gate does not help (run.log
PRE:line): the verbatim parser returnscard.error=0,pf_headnon-empty, and leavescis1_info[0]=cis1_info[1]=NULL(they are only ever set by CISTPL_VERS_1, pccard_cis.c:735-760, initialized NULL at :86-89).pccard_attach_cardproceeds (pccard.c:205-212 gates only on error/empty-pf_head) and creates child devices (:253-259). - Any of the three pnpinfo surfaces then faults at NULL:
*
no_vers1_devadded(driver attaches): SIGSEGVsi_addr=0x0*no_vers1_nomatch(no driver): SIGSEGVsi_addr=0x0*no_vers1_sysctl(unprivilegeddevinfo -v/sysctl walk after a silent probe failure): SIGSEGVsi_addr=0x0 -
Instruction-level pinning (disasm_proof.txt, cc 8.3 -O2, inlined
pccard_safe_quoteinsidepccard_child_pnpinfo_str.isra):4019a6: mov 0x110(%r11),%rcx ; rcx = sc->card.cis1_info[0] (NULL) 4019c0: movzbl (%rcx),%r8d ; :1001 if (*src == '"') <-- FAULT
r11 = device_get_softc() (PCCARD_SOFTC); the rig prints
offsetof(pccard_softc, card.cis1_info[0]) = 0x110 -- exact match with
the displacement. si_addr=0x0 == src, deterministic read at NULL.
In kernel context this is a data-access page fault on the socket event
thread -> panic at card insertion, and re-triggerable on demand by any
unprivileged local user (devinfo -v, sysctl -a) while the card is in.
Negative control: vers1_pnpinfo_ok (identical card WITH VERS_1) builds
manufacturer=0x1234 product=0x5678 cisvendor="ACME" cisproduct="Widget"
function_type=-1 and exits clean -- the crash is exactly the NULL
cis1_info, not the pnpinfo machinery.
Determinism: run.log / run.2.log / run.3.log -- identical results 3/3.
Why this is production behavior
The crashing text is the exact sys/bus/pccard/pccard.c that ships in the
guest's kernel (sha256-gated extraction), compiled by the guest toolchain;
the trigger sequence (devadded/devnomatch/sysctl-walk -> bus_child_pnpinfo_str
-> pccard_child_pnpinfo_str -> pccard_safe_quote) is the production dispatch
(subr_bus.c:645/:673/:3899 -> DEVMETHOD bus_child_pnpinfo_str, pccard.c:1340).
The only non-production element is the card -- a crafted CIS image in a
guard buffer instead of physical attribute memory (no PCMCIA bridge exists in
QEMU; env.txt: no /dev/pccard*, 0 pccard devices in devinfo, while
device pccard+device cbb are compiled into X86_64_GENERIC).
Fix validation (rig A/B + booted combined-fix kernel)
- Single-fix A/B (matrix.log): guard both
pccard_safe_quotecalls on cis1_info[0]/[1] != NULL (empty string otherwise) -> all three no_vers1 scenarios exit 0 with sanecisvendor="" cisproduct=""strings; every other scenario unchanged (busypanic still panics = DF-5355's bug; reentry still leaks = DF-5356's bug; controls stay green). - Combined three-fix kernel: pccard.c with DF-2318+DF-5355+DF-5356 fixes
built
-Werror(nativekernel rc=0), installed, booted (kernel #1); the rig re-extracted from the RUNNING kernel's source (--allow-patched) and re-driven: every scenario clean (postboot_run.log). Guest reset to clean-source snapshot afterwards.
Kernel references
- sys/bus/pccard/pccard.c:993 (pccard_safe_quote signature)
- sys/bus/pccard/pccard.c:1001 (
if (*src == '"')-- the NULL deref) - sys/bus/pccard/pccard.c:1020-1021 (unguarded cis1_info passes)
- sys/bus/pccard/pccard.c:205-212 (error/empty gates that do not check VERS_1)
- sys/bus/pccard/pccard.c:256 (device_add_child creates the child)
- sys/bus/pccard/pccard.c:1340 (bus_child_pnpinfo_str method)
- sys/bus/pccard/pccard_cis.c:86-89 (cis1_info initialized NULL)
- sys/bus/pccard/pccard_cis.c:735-760 (only VERS_1 ever sets them)
- sys/kern/subr_bus.c:645 (devadded -> pnpinfo)
- sys/kern/subr_bus.c:673 (devnomatch -> pnpinfo)
- sys/kern/subr_bus.c:174 (DEVICE_SYSCTL_PNPINFO -> pnpinfo)
- sys/kern/subr_bus.c:3899 (sysctl_devices walk -> pnpinfo)
- sys/kern/subr_bus.c:2114 (device_doattach -> devadded on attach success)
Exploit chain
evil PC Card without CISTPL_VERS_1 -> socket insert event ->
pccard_attach_card (pccard.c:178) -> verbatim CIS parse (no error) -> child
created + probed -> devadded OR devnomatch OR later unprivileged
devinfo -v/sysctl hw.bus.devices... -> bus_child_pnpinfo_str ->
pccard_child_pnpinfo_str -> pccard_safe_quote(NULL) -> kernel-mode read at
NULL+0 -> panic (local/physical DoS; re-triggerable at will by any local
user while the card is seated). No privilege boundary is crossed: the fault
is a fixed read at NULL, not a controllable mapping.
Environment
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64, cc 8.3 [DragonFly] (env.txt). Artifacts: run.log (decisive baseline, all 7 scenarios), run.2.log/run.3.log (determinism), disasm_proof.txt, build.log, extract2_manifest.txt (verbatim proof), matrix.log (A/B), postboot_run.log (running-kernel source), fixes/df2318.diff (validated).
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
fixedVALIDATED. (1) Single-fix rig A/B (matrix.log): only the three no_vers1 scenarios change -- clean exit 0, devadded prints sane manufacturer=0x1234 product=0x5678 cisvendor="" cisproduct="" strings; busypanic (DF-5355) and reentry_leak (DF-5356) unchanged under the f2318 variant; negative controls green. (2) Combined three-fix kernel: -Werror nativekernel rc=0, installkernel rc=0, booted kernel #1; rig re-extracted from the RUNNING kernel's patched source (--allow-patched) and re-driven: every scenario clean (postboot_run.log). In-kernel behavioral test impossible (no PCMCIA bridge) -- DF-1040/1041/2319 precedent.
fix.diff; matrix.log; postboot_extract_manifest.txt; postboot_run.log; kernel_apply.log; kernel_build.tail.log
Confirmed kernel references
- sys/bus/pccard/pccard.c:993
- sys/bus/pccard/pccard.c:1001
- sys/bus/pccard/pccard.c:1020
- sys/bus/pccard/pccard.c:1021
- sys/bus/pccard/pccard.c:205
- sys/bus/pccard/pccard.c:256
- sys/bus/pccard/pccard.c:1340
- sys/bus/pccard/pccard_cis.c:86
- sys/bus/pccard/pccard_cis.c:735
- sys/kern/subr_bus.c:174
- sys/kern/subr_bus.c:645
- sys/kern/subr_bus.c:673
- sys/kern/subr_bus.c:2114
- sys/kern/subr_bus.c:3899
Detail
Exploit chain
evil PC Card without CISTPL_VERS_1 -> socket insert event -> pccard_attach_card (pccard.c:178) -> CIS parses with no error, cis1_info stays NULL -> child created+probed -> devadded/devnomatch or later unprivileged devinfo -v / sysctl hw.bus.devices walk -> bus_child_pnpinfo_str -> pccard_child_pnpinfo_str -> pccard_safe_quote(cis0, NULL, 128) -> kernel read at NULL (pccard.c:1001) -> panic. No privilege boundary crossed: fixed read at NULL, no attacker-controlled mapping.
Evidence (decisive lines)
run.log (decisive: PRE line error=0/cis1_info[0]=0x0 + 3x 'RIG SIGSEGV si_addr=0x0'); run.2.log/run.3.log (determinism); disasm_proof.txt (faulting movzbl (%rcx) with rcx from sc+0x110, offsetof printed =0x110); extract2_manifest.txt (byte-identity); build.log; matrix.log (f2318 variant flips exactly the three no_vers1 scenarios to clean exit 0 with cisvendor="" strings); postboot_run.log (combined-fix kernel, running-source re-extraction); env.txt
PoC changes
No seed PoC existed for DF-2318 (pack was missing entirely). Wrote the verbatim lifecycle rig (rig2): machine-extracted pccard.c/subr_bus.c/pccard_cis.c cores, kernel-env shims (device tree + devadded/devnomatch/sysctl-walk surfaces mirroring subr_bus.c, bridge-window bus_alloc_resource model, ledgered kmalloc), CIS builders, and 7 scenarios covering DF-2318/5355/5356 plus two negative controls. Extraction needed the production forward-decl block (pccard.c:83-90), ccr_read/write, the PCCARD_IVAR_* enum and PFF_ENABLED ranges, and the subr_bus prototype block to compile verbatim; byte-identity cross-checked sed-vs-awk.
Verified recommended fix
Guard both pccard_safe_quote calls in pccard_child_pnpinfo_str on cis1_info[i] != NULL (emit empty string otherwise); validated by rig A/B and combined-fix kernel.
Verdict
REPRODUCED at the verbatim-rig level on the DragonFly guest (3/3 deterministic runs); the kernel entry path itself is hardware-gated (no PCMCIA bridge in QEMU -- env.txt). The production pccard.c was machine-extracted BYTE-IDENTICAL from the guest's own /usr/src (extract2.sh; sha256 gate 5b610669...; PCCARD_BYTE_IDENTICAL=yes + SUBR_BUS_BYTE_IDENTICAL=yes in extract2_manifest.txt) and driven through the real entry pccard_attach_card(). A perfectly-legal CIS that omits CISTPL_VERS_1 parses with card.error=0 and a non-empty pf_head (run.log PRE line), so pccard_attach_card proceeds and creates children (pccard.c:205-259); cis1_info[0]/[1] remain NULL (only CISTPL_VERS_1 ever sets them, pccard_cis.c:735-760). Every production pnpinfo surface then faults: devadded on driver-attach success (subr_bus.c:645 via :2114), devnomatch on probe failure (subr_bus.c:673), and the unprivileged hw.bus.devices walk used by devinfo(8)/sysctl -a (subr_bus.c:3899, DEVICE_SYSCTL_PNPINFO at :174 -- the walk was verified live as user maxx on this guest). All three scenarios die with SIGSEGV si_addr=0x0 pinned by disassembly to 'movzbl (%rcx),%r8d' == pccard.c:1001 ('if (*src == '"')') with rcx loaded from sc+0x110 == &sc->card.cis1_info[0] (rig prints offsetof=0x110, exact match; disasm_proof.txt). In kernel context: deterministic panic at card insertion, re-triggerable on demand by any unprivileged local user while the card is seated. Negative control vers1_pnpinfo_ok (same card WITH VERS_1) builds the pnpinfo string cleanly.
No comments yet.