isa_alloc_resource/isa_release_resource cast grandchild ivars via DEVTOISA unconditionally β type-confused (often NULL) resource-list pointer passed through, safe today only because resource_list_alloc/release early-forward on passthrough
Summary
passthrough is computed but the DEVTOISA cast runs regardless: for grandchild callers (psm0 on ISA-attached atkbdc0, ata channels, pccbb cards) rl is type-confused or NULL, handed to resource_list_alloc/release whose early-forward-on-passthrough is the ONLY guard. Live-verified: hinted atkbdc0 onto ISA -> psmattach executed the exact passthrough call with atkbdc_device_t ivars; IRQ 12 allocated, guest healthy - the confused pointer is computed on every such boot and never dereferenced. One refactor hoisting resource_list_find out of the !passthrough guard (as pre-fix FreeBSD once had) turns every ISA PS/2-mouse boot into a NULL/wild deref. guard-the-cast fix mirrors FreeBSD's restructuring.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5641 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.8 KB | β raw | |
| VERDICT.md | β | 7.3 KB | β raw | |
| introspect.sh | β | 1.4 KB | view raw | |
| build.sh | β | 131 B | view raw | |
| run.sh | β | 215 B | view raw | |
| trigger_hints.txt | β | 115 B | view raw | |
| devinfo.hinted.txt | β | 403 B | view raw | |
| dmesg.hinted.txt | β | 12.0 KB | view raw | |
| env.txt | β | 288 B | view raw | |
| run.log | β | 1.8 KB | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 5.0 KB | view raw |
DF-5641 β isa_alloc_resource/isa_release_resource cast grandchild ivars via DEVTOISA (latent type confusion, dead-cast today)
Target file: sys/bus/isa/x86_64/isa.c (pass 2)
Severity: Info (defense-in-depth) Β· no runtime impact demonstrated β and this pack
proves the confused pointer is computed on a live boot and never dereferenced.
Claim
isa_alloc_resource() (sys/bus/isa/x86_64/isa.c:76-124) and
isa_release_resource() (isa.c:126-134) unconditionally do
struct isa_device* idev = DEVTOISA(child); /* isa.c:87, :130 */
struct resource_list *rl = &idev->id_resources;
even when passthrough is true (device_get_parent(child) != bus, isa.c:85).
For a passthrough (grandchild) caller the child's ivars are not a
struct isa_device β they are the sub-bus's own ivar type (or NULL), so rl
is a type-confused / NULL pointer. It is handed to
resource_list_alloc()/resource_list_release(), which early-forward on
passthrough without touching rl (sys/kern/subr_bus.c:2683-2687 and
:2729-2732). That subr_bus.c implementation detail is the only reason every
DragonFly x86_64 boot with a PS/2 mouse does not panic in
resource_list_find() (subr_bus.c:2652 SLIST_FOREACH dereference).
Live demonstration (guest)
Standard QEMU guest, kernel X86_64_GENERIC (see env.txt). One
configuration change β loader hints that attach atkbdc0 to the ISA bus
(the in-tree DRIVER_MODULE(atkbdc, isa, β¦) attachment,
sys/dev/misc/atkbdc_layer/atkbdc_isa.c:401):
hint.atkbdc.0.at="isa" # isahint.c:121-129 creates atkbdc0 on the isa bus hint.atkbdc.0.port="0x60" hint.atkbdc.0.irq="1" hint.psm.0.at="atkbdc0" # atkbdc_isa.c:290-295 adds psm0 with atkbdc_device_t ivars hint.psm.0.irq="12"
psm0 is then a grandchild of the isa bus. psmattach()
(sys/dev/misc/psm/psm.c:2014) calls
bus_alloc_legacy_irq_resource(dev=psm0, β¦, irq=12, RF_ACTIVE)
(sys/kern/subr_bus.c:3302-3308), which dispatches to atkbdc's
bus_generic_alloc_resource (atkbdc_isa.c:83) and then to
isa_alloc_resource(bus=isa0, child=psm0) with:
passthrough == 1(psm0's parent is atkbdc0),- psm0's ivars =
atkbdc_device_t *(atkbdc_isa.c:231-232) β cast tostruct isa_device *at isa.c:87.rl= theatkbdc_device_tobject reinterpreted as an SLIST head (id_resources is the first member, isa_common.h:53-54).
Observed (dmesg.hinted.txt, devinfo.hinted.txt, env.txt):
atkbdc0: <Keyboard controller (i8042)> at port 0x60,0x64 irq 1 on isa0 psm0: <PS/2 Mouse> irq 12 on atkbdc0 interrupt uses mplock: psm0 psm0: model IntelliMouse Explorer, device ID 4
vmstat -i lists psm0 as a registered interrupt source β
sc->intr != NULL at psm.c:2015 β the full chain
psm.c:2014 β subr_bus.c:3302 β atkbdc_isa.c:83 β isa.c:76-124 (type-confused
rl computed) β subr_bus.c:2683 (early forward, rl untouched) β nexus
completed, the guest booted and stayed up. The confused pointer is used on
every such boot and survives only by the subr_bus.c passthrough convention.
Reproduce
./introspect.sh(from the host) β or manually: append the five hints above to the guest's/boot/loader.conf.local, reboot, thendevinfo | grep -A3 atkbdc0andvmstat -i | grep psm0.- Expected: tree shows
isa0 β atkbdc0 β psm0,psm0registered as an interrupt, guest healthy (no panic) β the type-confused passthrough call executed and was harmless. - Remove the hints and reboot to restore (guest was left in the restored
state;
guest_dirty: 0).
Why Info and not higher
No dereference of the confused rl exists on any reachable path today:
the only deref site (resource_list_find, isa.c:92) is inside
if (!passthrough && !isdefault), and the two subr_bus.c helpers early-return
on passthrough before touching rl. All inputs to this file come from
in-tree drivers (kernel-internal bus methods at probe/attach/kldload); no
user, hint, or loader value reaches the non-default arithmetic here (hints
enter through isa_set_resource, isa_common.c:893-902, bounds-checked, and
are read back via resource_list_alloc, never via this file's checks).
The finding documents a one-edit-away landmine: hoisting the
resource_list_find call (as pre-fix FreeBSD once did) or adding any rl
use outside the passthrough guard turns every PS/2-mouse boot into a
NULL/wild dereference. FreeBSD restructured exactly this code to go through
BUS_GET_RESOURCE_LIST() (and gave isa a bus_get_resource_list method).
Fix
fix.diff β compute rl only for direct children (!passthrough), where
isa_add_child() (isa_common.c:527-533) guarantees struct isa_device
ivars. Fix not applied to sys/ (read-only audit); not kernel-rebuilt for
this Info-severity hardening (no misbehavior exists to diff β baseline
observation itself is the proof).
DF-5641 VERDICT β reproduced (code-path demonstration), impact none
File: sys/bus/isa/x86_64/isa.c (pass 2, GLM 5.3) Finding class: latent type confusion / fragile-coupling hardening (Info) Status: reproduced (the observable claim β a passthrough call with foreign ivars executes at boot and survives only via the subr_bus.c passthrough early-forward) Β· impact: none (no dereference on any reachable path) Β· confidence: certain
Root cause (path:line)
isa_alloc_resource()computespassthrough = (device_get_parent(child) != bus)at sys/bus/isa/x86_64/isa.c:85 β then ignores it when casting:idev = DEVTOISA(child); rl = &idev->id_resources;at isa.c:87-88.isa_release_resource()repeats the pattern at isa.c:130-131.DEVTOISA(dev)is((struct isa_device *)device_get_ivars(dev))(sys/bus/isa/isa_common.h:64). For a grandchild caller this is the sub-bus's ivar type or NULL β never astruct isa_device.- The confused
rlis passed toresource_list_alloc()(isa.c:122) /resource_list_release()(isa.c:133). Both compute the samepassthroughflag and return early without touchingrlwhen it is set: sys/kern/subr_bus.c:2680-2687 and :2726-2732. This is the only guard. - Direct isa children always have proper ivars:
isa_add_child()allocates and installsstruct isa_device(sys/bus/isa/isa_common.c:527-533), and the isa bus has no other child-creation path (isa_bus_deviceis used only forisa_probe_children, sys/platform/pc64/x86_64/autoconf.c:161).
Why the confused path is real and exercised
Chain proven by source and executed live on the audit guest:
DRIVER_MODULE(atkbdc, isa, β¦)β sys/dev/misc/atkbdc_layer/atkbdc_isa.c:401.- atkbdc's bus method table registers
bus_alloc_resource = bus_generic_alloc_resource(atkbdc_isa.c:83) β pure upward forwarder (sys/kern/subr_bus.c:3295-3301). - atkbdc children get
atkbdc_device_tivars (atkbdc_isa.c:218-232) β notstruct isa_device. psmattach()unconditionally allocates its IRQ viabus_alloc_legacy_irq_resource(dev, β¦)β sys/dev/misc/psm/psm.c:2014, which callsBUS_ALLOC_RESOURCE(dev->parent, dev, β¦)(sys/kern/subr_bus.c:3302-3308).- β
isa_alloc_resource(bus=isa0, child=psm0)withpassthrough == 1andDEVTOISA(psm0)castingatkbdc_device_t*tostruct isa_device*.id_resourcesis the first struct member (isa_common.h:53-54), sorlaliases the atkbdc ivar object itself, reinterpreted as an SLIST head.
Same shape exists for other isa sub-buses (ata channels under ata-isa: sys/dev/disk/nata/ata-isa.c has no bus_alloc_resource method; CardBus cards under pccbb_isa: sys/dev/pccard/pccbb/pccbb_isa.c:185-186 forwards up).
Live verification (audit guest, QEMU/KVM)
- Guest: DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026, X86_64_GENERIC (env.txt).
- Baseline boot: ACPI claims atkbdc0 (acpi0), so the default config does not exercise the isa variant β noted honestly.
- Test boot: loader hints
hint.atkbdc.0.at="isa", port 0x60, irq 1,hint.psm.0.at="atkbdc0", irq 12 (see trigger_hints.txt / introspect.sh). isahint (sys/bus/isa/isahint.c:121-129) creates atkbdc0 on the isa bus;atkbdc_attach(atkbdc_isa.c:290-295) creates psm0 withatkbdc_device_tivars; psmattach runs the chain above. - Observed (dmesg.hinted.txt):
atkbdc0: <Keyboard controller (i8042)> at port 0x60,0x64 irq 1 on isa0psm0: <PS/2 Mouse> irq 12 on atkbdc0interrupt uses mplock: psm0/psm0: model IntelliMouse Explorer, device ID 4 vmstat -ilists psm0/atkbd0 as registered interrupts (env.txt) β the allocation succeeded βresource_list_alloctook the passthrough branch (subr_bus.c:2683-2687) and never dereferenced the confusedrl. Guest stayed up; no panic.- Tree captured in devinfo.hinted.txt:
nexus0 β legacy0 β pcib0 β pci0 β isab0 β isa0 β atkbdc0 β {atkbd0, psm0}. - Restored afterwards (hints removed, acpi0 back, atkbdc0 on acpi0) β guest_dirty = 0.
Why impact is "none" (candidates killed, with citations)
- Deref of confused
rl: impossible today β sole deref siteresource_list_find(rl,β¦)sits insideif (!passthrough && !isdefault)(isa.c:91-92); both subr_bus.c consumers early-forward on passthrough (subr_bus.c:2683-2687, 2729-2732). Proven live above. machintr_legacy_intr_cpuid(start)(isa.c:100): ICU implementation ignores its argument (sys/platform/pc64/icu/icu_abi.c:399-402); IO-APIC implementation bounds-checks withKKASSERT(irq >= 0 && irq < legacy_irq_max)before indexing (sys/platform/pc64/apic/ioapic_abi.c:1122-1125); the argument is truncatedu_longβintand is always an in-tree driver constant on this path (hints reach IRQ cpuid via isahint.c:73-75 / isa_set_resource instead, never through isa.c:100).- rid bounds: non-default path fully bounds-checked per type
(isa.c:94-115, ISA_NIRQ/ISA_NDRQ/ISA_NMEM/NPORT from isavar.h:63-66);
the default (0,~0) path never indexes anything β it only reads rles that
were created through the checked
isa_set_resource(isa_common.c:893-902) or this file's checkedresource_list_add. isa_setup_intrtrailing NULL (isa.c:148): that parameter isdesc(nexus.c:498-501), not cpuid; nexus derives the CPU fromrman_get_cpuid(irq)and usesdevice_get_nameunit(child)when desc is NULL (sys/platform/pc64/x86_64/nexus.c:522-536). Intentional.- User/hint/loader reachability: none β all four functions are bus
methods invoked only by in-tree drivers at probe/attach (boot) or
isa_driver_added(kldload, root-only, serialized); no copyin/ioctl path exists, and no file-hint value flows into this file's arithmetic. - Refcount/lifetime/locks: the file holds no refs and takes no locks; its callers run in serialized boot/kld contexts.
Exploit chain
None β no memory misbehavior exists to exploit; the finding is a one-edit-away hazard, not a bug with a primitive.
Fix
fix.diff (authored post-verification, never applied to the read-only sys/
tree): compute rl from child ivars only when !passthrough; pass NULL
into the resource_list helpers for passthrough (they ignore it there).
Not kernel-rebuilt: Info severity, no runtime misbehavior to diff β the
baseline run itself demonstrates the code path, and the change only narrows
when the cast happens (no behavioral delta for any in-tree caller).
Verdict bottom line
The unconditional DEVTOISA(child) in isa_alloc_resource/isa_release_resource
produces a type-confused (sometimes NULL) resource-list pointer for every
grandchild allocation, including every ISA-attached PS/2 mouse boot; it is
harmless today solely because resource_list_alloc/resource_list_release
forward on passthrough before touching rl. Guard the cast (fix.diff) or
restructure the way FreeBSD did (BUS_GET_RESOURCE_LIST + an isa
bus_get_resource_list method). File as hardening (Info); the rest of
sys/bus/isa/x86_64/isa.c is clean under the pass-2 checklist.
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
not_testablefix.diff authored post-verification and git-apply --check-verified against pristine sys/, but not kernel-rebuilt: Info-severity hardening with no runtime misbehavior to diff (the baseline run itself demonstrates the path executes harmlessly); the change only narrows when the ivars cast happens and alters no behavior for any in-tree caller.
findings/poc/DF-5641/fix.diff (git apply --check clean); VERDICT.md 'Fix' section
Confirmed kernel references
- sys/bus/isa/x86_64/isa.c:85-88
- sys/bus/isa/x86_64/isa.c:122-123
- sys/bus/isa/x86_64/isa.c:130-133
- sys/kern/subr_bus.c:2680-2687
- sys/kern/subr_bus.c:2726-2732
- sys/kern/subr_bus.c:3295-3308
- sys/bus/isa/isa_common.h:53-64
- sys/bus/isa/isa_common.c:527-533
- sys/dev/misc/atkbdc_layer/atkbdc_isa.c:83
- sys/dev/misc/atkbdc_layer/atkbdc_isa.c:218-232
- sys/dev/misc/atkbdc_layer/atkbdc_isa.c:290-295
- sys/dev/misc/atkbdc_layer/atkbdc_isa.c:401
- sys/dev/misc/psm/psm.c:2014
- sys/bus/isa/isahint.c:121-129
- sys/dev/acpica/acpi.c:419-421
Detail
Evidence (decisive lines)
run.log [2]-[5] and VERDICT.md: devinfo.hinted.txt shows nexus0->legacy0->pcib0->pci0->isab0->isa0->atkbdc0->psm0 (psm0 = isa grandchild with atkbdc_isa.c:231-232 atkbdc_device_t ivars); dmesg.hinted.txt 'psm0: <PS/2 Mouse> irq 12 on atkbdc0'; env.txt vmstat -i lists psm0 as a registered interrupt (chain through isa.c:76-124 completed); guest stayed up β proving the type-confused rl at isa.c:87 was computed and never dereferenced (subr_bus.c:2683 early-forward). Guest restored afterwards (acpi0 back).
PoC changes
not a seeded-code PoC: the trigger is a loader-hint configuration (trigger_hints.txt) driving an in-tree code path; no source had to be corrected. Two restore attempts stumbled on grep -v exit semantics (all file lines were hint lines -> empty output -> exit 1 -> '&& mv' skipped); final restore used truncate.
Verified recommended fix
Compute the resource-list pointer only for direct ISA children (rl = &DEVTOISA(child)->id_resources when !passthrough, NULL otherwise) in isa_alloc_resource/isa_release_resource, so grandchild ivars are never cast to struct isa_device.
Verdict
isa_alloc_resource()/isa_release_resource() (sys/bus/isa/x86_64/isa.c:87-88,130-131) cast the child's ivars to struct isa_device via DEVTOISA() unconditionally β including passthrough (grandchild) requests whose ivars belong to a sub-bus (e.g. atkbdc_device_t for psm0 on an ISA-attached atkbdc0) or are NULL β producing a type-confused/NULL resource-list pointer that is passed into resource_list_alloc()/resource_list_release(). Demonstrated live on the audit guest: with atkbdc0 hinted onto the ISA bus, psm0's psmattach() (psm.c:2014) allocation walked psm->atkbdc(bus_generic_alloc_resource)->isa_alloc_resource with passthrough=1 and a type-confused rl, and succeeded only because both helpers early-forward on passthrough without touching rl (subr_bus.c:2683-2687, 2726-2732). No dereference of the confused pointer exists on any reachable path today, so impact is none; the hazard is one refactor away (hoisting resource_list_find out of the !passthrough guard, as pre-fix FreeBSD once had, turns every ISA PS/2-mouse boot into a NULL/wild deref). Fix: compute rl only for direct children (fix.diff), or restructure FreeBSD-style via BUS_GET_RESOURCE_LIST plus an isa bus_get_resource_list method (DF's generic default returns NULL β subr_bus.c:3000-3004 β so the isa method is required).
No comments yet.