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

Unbounded resource-data ingestion: no-END stream wedges the kernel with unbounded kmalloc growth; endless logical-device tags amplify ~164Γ— into permanent kernel memory

Summary

pnp_read_resources loops until END with no total-length cap (never-ENDING card = kldload/boot thread wedged forever, single allocation 5.57MB at t+50s -> 12.4MB at t+125s still growing); pnp_create_devices creates a device + 8640B configs[] + never-freed csnldn per LD tag with no cap (36KB stream -> +5.9MB permanent M_DEVBUF, 6000 children). Both faces pnprig-reproduced live; 64KiB-data + 256-LDN caps in the validated fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5445 Β· 10 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
verdict.json β€” 4.6 KB view raw
manifest.json β€” 1.2 KB view raw
run_endless.log β€” 1.6 KB view raw
run_ldspray.log β€” 750 B view raw
serial_endless_full.log β€” 2.8 KB view raw
fix_run_endless.log β€” 219 B view raw
fix_run_ldspray.log β€” 643 B view raw
env.txt β€” 382 B view raw

DF-5445 β€” unbounded resource-data ingestion in pnp_read_resources()/pnp_create_devices() (sys/bus/isa/pnp.c)

What

Two unbounded-ingestion faces, both card-controlled:

  1. No-END stream: pnp_read_resources() (pnp.c:536-581) loops until an END tag or a read error. A card that keeps the resource-data-ready bit asserted forever (and never sends tag 0x78) keeps the loop running and pnp_read_bytes() growing the kmalloc buffer in 1 KiB steps β€” no cap on total length β€” until kernel memory is exhausted. The kldload/boot thread spins forever (kernel wedge).

  2. Endless logical devices: each PNP_TAG_LOGICAL_DEVICE processed by pnp_create_devices() (pnp.c:428-460) allocates a device + ivars + csnldn (24 B, also never freed) and pnp_parse_resources() kmallocs 9 Γ— sizeof(struct isa_config) = 8640 B per slice. ~6 bytes of card data β†’ >1 KB of permanent kernel allocation (measured 164Γ— amplification); ldn is an unbounded int even though the LDN register is 8-bit.

Rig

Same rig/ as DF-5443. Module hack_dfc (script 3): card synthesizes an endless tag stream (no END). Module hack_dfd (script 4): card sends 6000 logical-device tags + END (finite amplification demo).

Build / Run

(cd /usr/src/sys/bus/isa/hack_dfc && make obj && make depend && make)
(cd /usr/src/sys/bus/isa/hack_dfd && make obj && make depend && make)
timeout 50 ssh ... 'kldload .../rigdfc.ko'      # never returns (RC=124)
kldload .../rigdfd.ko                           # returns; vmstat -m diff

Expected

Baseline: * rigdfc: kldload never returns (local timeout RC=124); serial log shows kmalloc growth without bound (5.57 MB single allocation at t+50 s, 12.4 MB at t+125 s, still growing); guest degraded. * rigdfd: M_DEVBUF 208K→6.11 MB permanent (+5.9 MB for a 36 KB card stream), 211→11.9K chunks, 6000 children.

Fixed kernel (fix.diff: PNP_MAX_RESOURCE_DATA = 64 KiB cap in pnp_read_resources, 256-LDN cap in pnp_create_devices): * rigdfc: kldload returns quickly with "too much resource data". * rigdfd: capped at 256 logical devices, "too many logical devices".

VERDICT.md
↓ download raw

DF-5445 VERDICT β€” REPRODUCED (kernel wedge / unbounded memory exhaustion; DoS)

Bottom line

Both unbounded-ingestion faces demonstrated live on the baseline kernel; both eliminated by the caps in fix.diff.

Face 1 β€” no-END stream (wedge)

Card keeps resource-data-ready asserted forever, never sends tag 0x78. pnp_read_resources() (pnp.c:546-574) has no total-length cap; pnp_read_bytes() grows the buffer in 1 KiB steps without bound.

Observed (baseline #0):

  • kldload rigdfc.ko β€” never returns (50 s local timeout, RC=124),
  • kmalloc growth: single allocation 5.57 MB at t+50 s β†’ 12.41 MB at t+125 s, still climbing (verbatim samples in run_endless.log),
  • parallel vmstat ssh starved (RC=124); guest never recovers β€” reset required.

Face 2 β€” logical-device spray (permanent memory amplification)

6000 PNP_TAG_LOGICAL_DEVICE tags (36 KB stream, rigdfd.ko):

  • M_DEVBUF 208K β†’ 6.11 MB permanent (+5.9 MB), chunks 211 β†’ 11.9K (run_ldspray.log) β€” ~164Γ— amplification, unbounded (ldn is an int; the LDN register is 8-bit; csnldn at pnp.c:453 is never freed);
  • 6000 children created, found=1, load returns.

Fix validation (kernel #1)

  • Endless stream: completes in 4.26 s, pnp_read_resources: too much resource data Γ—7, RC=0, guest healthy (fix_run_endless.log).
  • LD spray: stops at 256 logical devices β€” isa0: too many logical devices, devbuf delta +258 K vs +5.9 MB baseline (fix_run_ldspray.log).

Fix

fix.diff: PNP_MAX_RESOURCE_DATA 64 KiB cap in pnp_read_resources() + 256-LDN cap in pnp_create_devices(). Build log: DF-5443/fix_build.log.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Single-fix kernel (fix.diff, #1 Mon Oct 5 16:47:13): the exact wedge stream completes in 4.26 s with 'pnp_read_resources: too much resource data' (RC=0, guest healthy, no unbounded growth); the LD spray stops at 256 devices with 'isa0: too many logical devices' and devbuf delta +258K vs +5.9MB baseline. Baseline bad behavior GONE on both faces.

fix_run_endless.log, fix_run_ldspray.log; fix_build.log + fix.diff (in DF-5443 pack)
DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Oct 5 16:47:13 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

crafted ISA-PnP card -> endless ready-or-data byte service, no END tag -> pnp_read_resources infinite loop -> pnp_read_bytes unbounded kmalloc growth (1 KiB steps) -> kernel-memory exhaustion + permanently busy kldload/boot thread (wedge). Alternate face: endless LOGICAL_DEVICE tags -> per-tag device + ivars + csnldn (leaked) + 8640-byte configs[] allocation -> unbounded permanent kernel memory from a ~6-bytes-per-device card stream.

Evidence (decisive lines)

run_endless.log (SSH_RC=124, verbatim kmalloc growth samples 5566464..5570560 at t+50s and 12407808..12409856 at t+125s, starved second command, reset required); run_ldspray.log (baseline: devbuf 208K->6.11MB, 211->11.9K chunks, found=1, 6.57s load); fix_run_endless.log (patched: 4.26 s, RC=0, 7x 'too much resource data'); fix_run_ldspray.log (patched: 'isa0: too many logical devices', devbuf +258K); rig/ (hack_dfc endless wrapper, hack_dfd spray wrapper)

PoC changes

Same rig as DF-5443 (see its poc_changes). Script 3 synthesizes an endless 0x09/0x00 small-tag stream; script 4 emits 6000 finite LD tags + END.

Verified recommended fix

Cap total ingested resource bytes per card (PNP_MAX_RESOURCE_DATA=64 KiB) in pnp_read_resources() and cap logical devices at 256 (LDN register width) in pnp_create_devices() β€” full unified diff in fix.diff.

Verdict

REPRODUCED on DragonFly 6.5-DEVELOPMENT (baseline #0), both faces. Face 1 (no-END stream): a card that keeps resource-data-ready asserted and never sends tag 0x78 keeps pnp_read_resources() (pnp.c:546-574) looping and pnp_read_bytes() growing the kmalloc buffer in 1 KiB steps with NO total-length cap β€” kldload NEVER returned (local timeout RC=124 after 50 s), the buffer reached a single 5.57 MB allocation at t+50 s and 12.41 MB at t+125 s and was still growing, a parallel vmstat ssh starved (RC=124), and the guest never recovered (reset required): kernel wedge + unbounded kernel-memory exhaustion. Face 2 (logical-device spray): 6000 PNP_TAG_LOGICAL_DEVICE tags (36 KB card stream) create 6000 bus children, each with an 8640-byte configs[] kmalloc inside pnp_parse_resources() plus ivars and a never-freed 24-byte csnldn β€” measured M_DEVBUF growth 208K->6.11MB (+5.9MB permanent, 211->11.9K chunks) for the 36 KB stream (~164x amplification); ldn is an unbounded int although the LDN register is 8-bit, so the face is unbounded. FIX VALIDATED on rebuilt kernel #1 (fix.diff: PNP_MAX_RESOURCE_DATA=64 KiB cap in pnp_read_resources + 256-LDN cap in pnp_create_devices): the exact wedge stream now completes in 4.26 s with 'pnp_read_resources: too much resource data' and kldload RC=0 (guest healthy); the LD spray stops at 'isa0: too many logical devices' with devbuf delta +258K (vs +5.9MB baseline).