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)
PoC verification
Evidence pack
findings/poc/DF-4546 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 --checkOK; one guard- kernel-toolchain
-fsyntax-onlycompile 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.logfix.diff,fix_build.log,env.txt,VERDICT.md,manifest.json,verdict.json
VERDICT β DF-4546
Reproduced: YES (lifted-copy harness, macro-faithful; in-kernel path hardware-gated)
- Root cause proven (lm78.c):
wb_match()caseWB_CHIPID_W83791SD(:590-592) sets a description but never callslm_setup_sensors(), sosc->numsensors == 0;lm_attach()returns early at :427-429 beforesensordev_install()(nothing is ever inserted into the global sensordev_list);lm_detach()(:450-451) unconditionally callssensordev_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 --checkOK- Kernel-toolchain
-fsyntax-onlyof 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_testablePatched 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)
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.
No comments yet.