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

ichwd_attach fail path dereferences ZERO_LENGTH_PTR softc, panicking on attach failure

Field Value
ID DF-1831
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-822 Untrusted Pointer Dereference
File sys/dev/misc/ichwd/ichwd.c
Lines 603-604
Area dev/misc (watchdog driver attach)
Confidence certain
Discovered 2026-07-20
Reported pending
Known CVE none
CVE match dfly_specific

Summary

ichwd_attach() uses a file-scope static softc (sc = &ichwd_sc; at ichwd.c:524) because the driver table declares softc size 0 (ichwd.c:661-665). The error path at ichwd.c:603-604 reassigns sc = device_get_softc(dev), which for a size-0 driver returns the sentinel ZERO_LENGTH_PTR = (void*)-8 from kmalloc(0) (kern_slaballoc.c:888-891). The very next read (sc->tco_res at ichwd.c:605) wraps to address 0x30 in the unmapped NULL guard page and the kernel takes an unconditional page fault β†’ panic. The resources actually allocated into &ichwd_sc are leaked. Any of several realistic attach-failure conditions (missing PMBASE, I/O range busy, NO_REBOOT bit hardwired by BIOS) triggers this.

Root cause

ichwd.c:234 declares static struct ichwd_softc ichwd_sc; as a file-scope global because the DragonFlyBSD port dropped the dynamic softc but kept the FreeBSD-style device_get_softc(dev) call in the error path. ichwd_attach() correctly sets the working pointer at line 524:

sc = &ichwd_sc;

The driver table at ichwd.c:661-665 sets softc size to 0:

static driver_t ichwd_driver = {
    "ichwd",
    ichwd_methods,
    0
};

Therefore subr_bus.c:1952-1954 executes dev->softc = kmalloc(0, M_BUS, M_INTWAIT | M_ZERO), and kern_slaballoc.c:888-891 returns the sentinel ZERO_LENGTH_PTR = (void*)-8 for any size==0 allocation. device_get_softc() (subr_bus.c:1782-1785) returns dev->softc unchanged, so it hands back (void*)-8.

In the fail path (ichwd.c:603-616):

 fail:
    sc = device_get_softc(dev);          /* sc = (struct ichwd_softc*)-8 */
    if (sc->tco_res != NULL)             /* reads addr 0x30 -> page fault */
        bus_release_resource(dev, SYS_RES_IOPORT,
            sc->tco_rid, sc->tco_res);
    if (sc->smi_res != NULL)             /* reads addr 0x20 */
        bus_release_resource(dev, SYS_RES_IOPORT,
            sc->smi_rid, sc->smi_res);
    if (sc->gcs_res != NULL)             /* reads addr 0x40 */
        bus_release_resource(ich, SYS_RES_MEMORY,
            sc->gcs_rid, sc->gcs_res);

With amd64 struct layout (device_t=intptr_t): offsets are device@0, ich@8, ich_version@16, active@20, timeout@24, smi_enabled@28, smi_rid@32, smi_res@40, tco_rid@48, tco_res@56, gcs_rid@64, gcs_res@72. Adding these offsets to (u_long)-8 wraps modulo 2^64 into addresses 0x00..0x40, all inside the unmapped NULL page (0..PAGE_SIZE-1). The first execution of sc->tco_res at line 605 reads address 0x30 and the kernel takes a page fault in supervisor mode β†’ panic.

Reachability of goto fail: - ichwd.c:528-531 (no ICH LPC bridge) - ichwd.c:537-540 (PMBASE==0) - ichwd.c:547-549 (smi_res alloc fails) - ichwd.c:556-558 (tco_res alloc fails) - ichwd.c:565-567 (gcs_res alloc fails for ICH6+) - ichwd.c:571-572 (ichwd_clear_noreboot returns EIO because NO_REBOOT is hardwired by BIOS β€” common on server boards; driver itself prints "ICH WDT present but disabled in BIOS or hardware" at ichwd.c:419-420)

Every one of these branches trips the panic; the failure is not limited to the late resource-allocated cases.

Threat model & preconditions

  • Attacker position: requires local administrator capable of loading the module, OR a system builder/BIOS that has device ichwd compiled in or auto-loaded at boot. The bug fires during bus attach, not from a user syscall.
  • Privileges gained or impact: unconditional kernel panic at module load / boot (denial of service). On systems where ichwd is built in or auto-loaded, the machine cannot boot.
  • Required config or capabilities: device ichwd (sys/conf/files:2121 marks it optional), Intel ICH LPC bridge present (any of ~160 PCI IDs in ichwd.c:74-229), plus any of the realistic failure preconditions listed above.
  • Reachability: bus attach sequence for the LPC bridge child device created by ichwd_identify (ichwd.c:246).

Proof of concept

No userspace PoC is meaningful β€” the bug is in bus attach. The trigger is simply running kldload ichwd (or booting) on a system matching the preconditions.

Build & run

# In a DFly kernel build with `device ichwd` enabled:
kldload ichwd
# or boot a kernel that auto-loads ichwd on a system whose BIOS has set
# the NO_REBOOT latch, or whose PMBASE is unset.

Expected output

ichwd0: <watchdog timer on ICH6 or later> port 0x..-0x.. irq 9 on acpi0
ichwd0: ICH WDT present but disabled in BIOS or hardware.
Fatal trap 12: page fault while in kernel mode
fault virtual address    = 0x30
fault code               = supervisor read, data not present
instruction pointer      = 0x..:0x..<ichwd_attach+...>
...
panic: from debugger

Impact

Driver load or kernel boot panics on any system that has the Intel ICH LPC bridge and any of several realistic attach-failure preconditions. No confidentiality or integrity impact; pure availability (DoS). Most practically affects embedded/server boards whose BIOS hardwires NO_REBOOT, and development boxes where the TCO I/O range is already claimed by another driver.

The local variable sc is already correctly initialized to &ichwd_sc at line 524. Remove the incorrect device_get_softc(dev) reassignment in the fail path so the fail block reads the real resource pointers. The cleaner long-term fix additionally sets each *_res field to NULL after its bus_release_resource to make the path idempotent, and gates the GCS release on sc->ich_version >= 6 (matching the allocation guard at ichwd.c:562).

--- a/sys/dev/misc/ichwd/ichwd.c
+++ b/sys/dev/misc/ichwd/ichwd.c
@@ -601,7 +601,6 @@ ichwd_attach(device_t dev)

    return (0);
  fail:
-   sc = device_get_softc(dev);
    if (sc->tco_res != NULL)
        bus_release_resource(dev, SYS_RES_IOPORT,
            sc->tco_rid, sc->tco_res);

References

  • DFly kmalloc(0) returning ZERO_LENGTH_PTR = (void*)-8: kern_slaballoc.c:888-891.
  • subr_bus.c:1952-1954 dev->softc = kmalloc(0, ...) for size-0 drivers.
  • FreeBSD counterpart uses a dynamically allocated softc and never hits this path; the bug was introduced during the DFly port that replaced the dynamic softc with the static global.

Timeline

  • 2026-07-20 Discovered during automated audit.
  • 2026-07-20 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1831 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 315 B view raw
VERDICT.md verdict source-confirmation analysis 1.0 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1831 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: ichwd_attach uses static global softc sc=&ichwd_sc but fail path reassigns sc=device_get_softc(dev) which for size-0 driver returns ZERO_LENGTH_PTR=(void*)-8 from kmalloc(0); first read sc->tco_res wr

Citation: sys/dev/misc/ichwd/ichwd.c:603-604

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: fail path sc=device_get_softc(dev) returns ZERO_LENGTH_PTR (ichwd.c:603-604)

Verified recommended fix

Source-confirmed: fail path sc=device_get_softc(dev) returns ZERO_LENGTH_PTR (ichwd.c:603-604)

Verdict

Source-confirmed: fail path sc=device_get_softc(dev) returns ZERO_LENGTH_PTR (ichwd.c:603-604)