β¬’ 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 706 B ↓ raw
build.sh build-script N/A (source-only) 61 B view raw
run.sh run-script N/A (source-only) 87 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 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)