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

atkbdattach() ignores IRQ resource allocation failure: NULL resource reaches nexus_setup_intr() which panics by design (boot-time kernel panic; BUS_SETUP_INTR result also unchecked, resource leaked)

Summary

No hint.atkbd.N.irq -> ivar -1 -> the rman range [(u_long)-1,(u_long)-1] fails -> NULL -> nexus panic 'NULL irq resource!'. GENERIC pins irq 1 so boot-config-gated; the psm sibling on the same bus does both checks. NULL-check + unwind fix (apply-verified).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5179 Β· 8 files
FileTypeDescriptionSize
README.md β€” 3.6 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
trigger.txt β€” 1.3 KB view raw
fix.diff β€” 825 B view raw
build.sh β€” 148 B view raw
run.sh β€” 160 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 2.8 KB view raw

DF-5179 β€” atkbd(4) ISA attach: unchecked IRQ resource allocation β†’ boot-time kernel panic

  • File: sys/dev/misc/atkbd/atkbd_isa.c
  • Severity: Low (boot-time DoS; trigger requires privileged config / resource exhaustion)
  • Confidence: certain (source-proven panic path)
  • Verification: source-confirmed only β€” trigger is boot-time and gated on kernel-config/loader hints (root-only) or ISA IRQ rman exhaustion; there is no unprivileged runtime entry point to exercise in the QEMU guest, so no guest run was attempted (verdict.json status untested, attempts 0).

Root cause

atkbdattach() (sys/dev/misc/atkbd/atkbd_isa.c:118-124) allocates the legacy IRQ resource and hands it straight to BUS_SETUP_INTR() without any NULL check:

rid = 0;
sc->intr = bus_alloc_legacy_irq_resource(dev, &rid, irq, RF_ACTIVE);
BUS_SETUP_INTR(device_get_parent(dev), dev, sc->intr, INTR_MPSAFE,
               atkbd_isa_intr, kbd, &sc->ih, NULL, NULL);

Three things go wrong when the allocation fails:

  1. sc->intr == NULL is forwarded down the bus tree (atkbdc β†’ bus_generic β†’ isa β†’ nexus) and nexus_setup_intr() panics on NULL by design β€” sys/platform/pc64/x86_64/nexus.c:505-506:

c /* somebody tried to setup an irq that failed to allocate! */ if (irq == NULL) panic("%s: NULL irq resource!", __func__);

A driver-caused NULL irq is supposed to be a driver bug caught before this point; atkbd_isa is exactly such an unchecked driver.

  1. Even for the non-NULL late-failure path (register_int() returns NULL β†’ EINVAL), the BUS_SETUP_INTR() return value is ignored, the irq resource is leaked, and atkbdattach() still return 0 β€” the driver reports a healthy attach with no interrupt plumbed.

  2. atkbdresume() (atkbd_isa.c:141) later calls rman_get_start(sc->intr) which dereferences the resource pointer ((r)->r_start) β€” a second NULL-deref site if the attach bug is ever fixed by ignoring the error instead of failing the attach.

Trigger path

irq comes from the parent atkbdc ivar. When no hint.atkbd.N.irq hint exists, atkbdc_add_device() initializes it to -1 (sys/dev/misc/atkbdc_layer/atkbdc_isa.c:221-224), which is passed as (u_long)-1 to bus_alloc_legacy_irq_resource() β†’ BUS_ALLOC_RESOURCE(..., start = ULONG_MAX, end = ULONG_MAX, ...) β†’ the ISA IRQ rman cannot satisfy the range β†’ NULL β†’ panic above. IRQ rman exhaustion/conflict at boot is an alternative route to the same NULL.

Default configs pin the irq (sys/config/X86_64_GENERIC:152: device atkbd0 at atkbdc? irq 1), so the panic needs a custom kernel config / /boot/loader.conf hints (root-only) or IRQ pressure β€” hence Low.

In-tree reference for the correct pattern

The sibling driver on the same parent does it right (sys/dev/misc/psm/psm.c:2014-2022):

sc->intr = bus_alloc_legacy_irq_resource(dev, &rid, irq, RF_ACTIVE);
if (sc->intr == NULL) {
        return (ENXIO);
}

error = BUS_SETUP_INTR(device_get_parent(dev), dev, sc->intr,
                INTR_NOPOLL, psmintr, sc, &sc->ih, NULL, NULL);
if (error)
        goto out;

Fix

fix.diff in this directory β€” NULL-check the allocation (return ENXIO), check the BUS_SETUP_INTR() result, unwind the resource on failure. Not applied to sys/ (audit tree is read-only); not guest-validated (fix_status: not_testable β€” trigger is boot-config-gated).

Artifacts

  • VERDICT.md β€” this narrative
  • trigger.txt β€” the privileged config that triggers the panic
  • fix.diff β€” the verified-by-inspection fix
  • manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-5179 β€” Verification Verdict

Status: untested (source-confirmed; boot-config-gated β€” no unprivileged or guest-runnable trigger exists) Impact: panic (boot-time kernel panic when triggered) Confidence: certain Attempts: 0 (no runtime attempt β€” see below)

Why the code path is proven without a guest run

Every link in the chain is visible in the tree:

  1. atkbdattach() passes the parent ivar irq to bus_alloc_legacy_irq_resource() and forwards the unchecked result to BUS_SETUP_INTR() β€” sys/dev/misc/atkbd/atkbd_isa.c:119-122.
  2. With no hint.atkbd.N.irq, the parent initializes the ivar to -1 (sys/dev/misc/atkbdc_layer/atkbdc_isa.c:221-224), which becomes (u_long)-1 in BUS_ALLOC_RESOURCE(..., start=ULONG_MAX, end=ULONG_MAX, ...) (sys/kern/subr_bus.c:3303-3310) β€” an ISA IRQ rman range that can never be satisfied β†’ returns NULL.
  3. BUS_SETUP_INTR walks atkbdc (bus_generic) β†’ isa (sys/bus/isa/x86_64/isa.c:143-149) β†’ nexus, and nexus_setup_intr() executes panic("%s: NULL irq resource!", __func__) on the NULL resource β€” sys/platform/pc64/x86_64/nexus.c:505-506.
  4. Secondary NULL-deref site: atkbdresume() dereferences rman_get_start(sc->intr) (sys/dev/misc/atkbd/atkbd_isa.c:141).
  5. Sibling driver on the same bus checks both conditions (sys/dev/misc/psm/psm.c:2014-2022) β€” atkbd_isa.c is the outlier.

Why no guest run

The trigger is boot-time and requires either a kernel config/loader hint without irq 1 (root-only file edits + reboot, risking an unbootable console-less guest) or ISA IRQ rman exhaustion. There is no runtime syscall, ioctl, or device node that reaches this path after boot completes, so a QEMU PoC would be testing the guest's own boot configuration rather than an attacker-reachable primitive. Marked untested rather than not_reproduced because the proof is static, per the family precedent (DF-2177/DF-2178/DF-2385 were likewise source-confirmed).

Fix validation

fix.diff (in this directory) adds the NULL check (ENXIO) and the BUS_SETUP_INTR error check with resource unwind. Verified to apply cleanly (patch -p1 --dry-run OK against a pristine copy of the file). Kernel build/boot validation not performed (Low severity, boot-gated trigger): fix_status: not_testable.

Source File

sys/dev/misc/atkbd/atkbd_isa.c

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored after source verification; verified to apply cleanly to a pristine copy (patch -p1 --dry-run OK). Kernel build/boot validation skipped: Low-severity boot-gated trigger would require editing the guest's loader config and rebooting into a possibly console-less system.

findings/poc/DF-5179/fix.diff (sha256 15fb54e0d7c0150cb4c53f7f843f8a8b3d7c4add384f5bf8e3d2bcebefafb019); dry-run apply check in VERDICT.md
↓ fix.diffper-fix-DF-5179

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5179/VERDICT.md (static chain, 5 links); trigger.txt (privileged config); fix.diff (applies cleanly, patch -p1 --dry-run OK)

PoC changes

n/a (no seed PoC; trigger documented as privileged boot configuration in trigger.txt)

Verified recommended fix

Check bus_alloc_legacy_irq_resource() for NULL (return ENXIO) and the BUS_SETUP_INTR() result (release the resource and return the error), mirroring psm.c:2014-2022.

Verdict

Source-confirmed boot-time kernel panic: atkbdattach() (atkbd_isa.c:119-122) forwards the unchecked result of bus_alloc_legacy_irq_resource() to BUS_SETUP_INTR(); when the allocation fails (missing hint.atkbd.N.irq defaults the ivar to -1 per atkbdc_isa.c:221-224, making the rman range [ULONG_MAX,ULONG_MAX] unsatisfiable, or under ISA IRQ exhaustion) the NULL resource reaches nexus_setup_intr() which panics by design (nexus.c:505-506). The BUS_SETUP_INTR return value is also ignored and the resource leaks on late failure, and atkbdresume() would dereference rman_get_start(NULL) (atkbd_isa.c:141). The sibling driver psm.c:2014-2022 performs both checks. No guest run: the trigger is privileged boot configuration (kernel config/loader hints) with no post-boot entry point, so no attacker-reachable runtime PoC exists; static proof chain is complete.