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

lm_detach on a zero-sensor attach (W83791SD) TAILQ_REMOVEs a never-installed ksensordev: NULL-pointer write plus global sensordev_list corruption, kernel panic on driver detach

Summary

wb_match claims the W83791SD without setting a sensor table, so attach returns before sensordev_install - but detach unconditionally deinstalls, and TAILQ_REMOVE of the never-inserted zeroed node writes through NULL and corrupts the global list. Reproduced macro-faithfully on the guest (verbatim queue.h macro: SIGSEGV NULL-write at 0x0 inside deinstall); 19-sensor control and the guarded variant detach cleanly. Root-triggered panic (kldunload/devctl detach) on W83791SD hardware. Zero-sensor guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4546 Β· 14 files
FileTypeDescriptionSize
lm_detach_nullwrite.c β€” 6.3 KB view raw
build.sh β€” 128 B view raw
run.sh β€” 102 B view raw
build.log β€” 17 B view raw
run.log β€” 527 B view raw
run.2.log β€” 445 B view raw
fix_run.log β€” 527 B view raw
fix.diff β€” 497 B view raw
fix_build.log β€” 241 B view raw
env.txt β€” 335 B view raw
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 3.5 KB view raw

DF-4546 β€” lm78 lm_detach on a zero-sensor attach (W83791SD) β†’ NULL-pointer write + global sensordev_list corruption

Severity: Low (privileged detach trigger, obscure chip) | Confidence: certain File: sys/dev/powermng/lm/lm78.c:590-592,427-429,445-458

What

wb_match() claims the Winbond W83791SD without setting up any sensor table (lm78.c:590-592 β€” the only match case with no lm_setup_sensors). sc->numsensors stays 0, so lm_attach() bails at its guard (lm78.c:427-429) and sensordev_install() never runs. lm_detach() (lm78.c:445-458) then unconditionally calls sensordev_deinstall(&sc->sensordev).

sensordev_deinstall (kern_sensors.c:153-176) executes TAILQ_REMOVE(&sensordev_list, sensdev, list) on a node that was never inserted. The newbus softc is zeroed, so per the DragonFly TAILQ_REMOVE (sys/sys/queue.h:646-662):

if ((TAILQ_NEXT(elm, field)) != NULL) ...       /* tqe_next == NULL -> skip */
else (head)->tqh_last = (elm)->field.tqe_prev;  /* global list tqh_last = NULL */
*(elm)->field.tqe_prev = TAILQ_NEXT(elm, field);/* *(NULL) = NULL  <-- WRITE to 0x0 */

i.e. an immediate write through a NULL pointer (kernel panic) that first also clobbers the global sensordev_list.tqh_last (any later TAILQ_INSERT_TAIL would itself write through NULL).

Trigger: driver detach of a bound lm(4) on W83791SD hardware β€” kldunload/devctl detach/unbind (root). Reachable at runtime, not exploitable for anything but DoS; filed because the teardown path is fundamentally unguarded and the same shape fires for any future zero-sensor match entry.

Reproduce (lifted-copy harness β€” macro-level)

The in-kernel path needs the physical chip (guest has no LM78-class device), so the harness replicates struct ksensordev in a zeroed softc, the verbatim TAILQ_REMOVE from sys/sys/queue.h:646, and the install/deinstall list logic from kern_sensors.c.

./build.sh && ./run.sh

Expected (run.log): - CONTROL normal chip (numsensors=19, e.g. W83792D) β†’ install β†’ attach β†’ detach clean, list intact - BUG zero-sensor attach (W83791SD path) β†’ detach β†’ SIGSEGV: kernel-equivalent NULL-pointer WRITE at 0x0 - PATCHED (fix.diff: numsensors == 0 guard) β†’ same zero-sensor attach detaches cleanly

Fix validation

  • fix.diff β€” git apply --check OK; one guard
  • kernel-toolchain -fsyntax-only compile of the patched lm78.c in the X86_64_GENERIC kernel objdir: PATCHED_RC=0 with a deliberate-error negative control failing (NEGCTL_RC=1) β€” the check is real (fix_build.log); guest /usr/src restored

Files

  • lm_detach_nullwrite.c (sha256 2efe60dd3d3e7e77e4d78e9061d741869c72f6fd87686a3d9562507cada2ddef)
  • build.sh / run.sh, build.log / run.log / run.2.log / fix_run.log
  • fix.diff, fix_build.log, env.txt, VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

VERDICT β€” DF-4546

Reproduced: YES (lifted-copy harness, macro-faithful; in-kernel path hardware-gated)

  • Root cause proven (lm78.c): wb_match() case WB_CHIPID_W83791SD (:590-592) sets a description but never calls lm_setup_sensors(), so sc->numsensors == 0; lm_attach() returns early at :427-429 before sensordev_install() (nothing is ever inserted into the global sensordev_list); lm_detach() (:450-451) unconditionally calls sensordev_deinstall(&sc->sensordev).
  • Fault mechanics proven (kern_sensors.c:154-160 β†’ sys/sys/queue.h:646-662): TAILQ_REMOVE on the zeroed, never-inserted node: tqe_next == NULL β†’ sensordev_list.tqh_last = NULL (global list corrupted), then *(tqe_prev == NULL) = tqe_next β†’ immediate write to virtual address 0. In the kernel that is an unrecoverable NULL-page write β†’ panic. The harness replicates the exact macro expansion (verbatim from queue.h) and struct layout (sensors.h:156-166) in a calloc'd softc.
  • Harness result (run.log): CONTROL (numsensors=19) installs and detaches cleanly; BUG (numsensors=0, the W83791SD path) β†’ SIGSEGV: kernel-equivalent NULL-pointer WRITE at 0x0 inside sensordev_deinstall; PATCHED (fix.diff guard) β†’ clean detach.
  • Trigger realism: requires an lm(4) bound to a W83791SD and a privileged detach (kldunload / devctl). Root-triggered local DoS on rare hardware β†’ severity Low. The defect is structural: any future match entry without a sensor table arms the same crash.

Fix validation

  • git apply --check OK
  • Kernel-toolchain -fsyntax-only of patched lm78.c in the X86_64_GENERIC objdir: PATCHED_RC=0 (0 bytes diagnostics) with a deliberate-syntax-error negative control failing (NEGCTL_RC=1) β€” fix_build.log; guest /usr/src restored + verified
  • Harness PATCHED control: zero-sensor attach detaches without a fault

Kernel refs

sys/dev/powermng/lm/lm78.c:590, sys/dev/powermng/lm/lm78.c:427, sys/dev/powermng/lm/lm78.c:450, sys/kern/kern_sensors.c:153, sys/sys/queue.h:646, sys/sys/sensors.h:156, sys/dev/powermng/lm/lm78var.h:126

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 reproduced→ patch + rebuild →patched clean

Patched kernel not runnable against real W83791SD hardware on the guest (no LM78-class chip). fix.diff is git-apply-able, compiles clean under the kernel toolchain in the X86_64_GENERIC objdir (-fsyntax-only, RC=0, with a deliberate-error negative control failing RC=1), and the harness PATCHED control detaches the zero-sensor device without a fault.

fix.diff; fix_build.log (PATCHED_RC=0, NEGCTL_RC=1); run.log PATCHED case (clean detach)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 x86_64 (kernel objdir syntax validation)

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (BUG case: '>>> SIGSEGV: kernel-equivalent NULL-pointer WRITE at 0x0' inside sensordev_deinstall; CONTROL + PATCHED clean); run.2.log (repeat); fix_build.log (kernel-toolchain -fsyntax-only of patched lm78.c: PATCHED_RC=0, deliberate-error negative control NEGCTL_RC=1); fix.diff (git apply --check OK)

PoC changes

authored fresh this run (pass 2); no seed PoC existed

Verified recommended fix

guard lm_detach with 'if (sc->numsensors == 0) return 0;' (fix.diff) -- nothing was installed, so nothing may be deinstalled

Verdict

lm_detach() (lm78.c:445-458) unconditionally calls sensordev_deinstall() on a ksensordev that was never installed: wb_match() claims the W83791SD without a sensor table (lm78.c:590-592, the only chip case with no lm_setup_sensors), so numsensors==0 makes lm_attach() return early (lm78.c:427-429) before sensordev_install(). sensordev_deinstall (kern_sensors.c:153-176) runs TAILQ_REMOVE (sys/sys/queue.h:646-662) on the zeroed never-inserted node: head->tqh_last = NULL (global sensordev_list corrupted) followed by *(tqe_prev==NULL) = tqe_next -- an immediate NULL-pointer WRITE, i.e. a kernel panic on any later driver detach (kldunload/devctl) of the bound lm(4) device. Reproduced with a macro-faithful lifted-copy harness (verbatim TAILQ_REMOVE from queue.h, struct ksensordev per sensors.h:156-166, zeroed softc): BUG case faults writing to 0x0 inside the deinstall; CONTROL (numsensors=19) and PATCHED (fix.diff guard) detach cleanly. In-kernel path hardware-gated (no LM78-class chip on the guest); trigger is privileged detach on rare hardware -> Low severity, root-triggered DoS.