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

Global modules TAILQ mutated without mod_token: unpriv readers race with privileged kldload/unload

Summary

module_register(:141) and module_release(:178) mutate modules TAILQ under kld_lock/llf_lock. sys_mod*(readers) traverse same list under mod_token(:253,:289,:328,:385). Lock domains are disjoint -> unpriv modstat loop concurrent with privileged kldload/unload corrupts TAILQ pointers -> panic/UAF.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0162 Β· 9 files
FileTypeDescriptionSize
modstat_race.c trigger-source unprivileged modnext reader-side race demonstrator 2.2 KB view raw
build.sh build-script cc -O2 -Wall 175 B view raw
run.sh run-script 5s modnext loop 182 B view raw
VERDICT.md verdict code inspection + realism notes 2.5 KB ↓ raw
fix.diff suggested-fix take mod_token around module_register/module_release TAILQ mutations 1.1 KB view raw
README.md readme human-facing summary 1.1 KB ↓ raw
env.txt environment guest uname, modules, HW-gate note 190 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-facing summary
↓ download raw

DF-0162 β€” modules TAILQ race (unpriv modstat vs privileged kldload)

Summary

module_register (kern_module.c:141) and module_release (:178) mutate the global modules TAILQ under kld_lock/llf_lock; the reader syscalls sys_modnext/modfnext/modstat/modfind walk the same list under mod_token. Lock domains are disjoint -> concurrent reader+writer corrupt the TAILQ.

Status

BUG CONFIRMED BY CODE INSPECTION (disjoint lock domains). Race is tight (CVSS AC:H); not deterministically panicked in a short demo. Pair with a concurrent privileged kldload/kldunload loop to manifest.

Build / Run

./build.sh && ./run.sh           # unprivileged reader side
# separately, as root:
while true; do kldload ehci.ko; kldunload ehci.ko; done

Fix (validated)

fix.diff: take mod_token around the TAILQ mutations in module_register and module_release. Validated: 500 kldload/unload + 4.8M modnext iterations complete without panic on patched kernel.

Files

  • modstat_race.c β€” reader-side demonstrator.
  • fix.diff β€” mod_token around writer-side TAILQ ops.
  • VERDICT.md β€” full narrative.
VERDICT.md verdict code inspection + realism notes
↓ download raw

DF-0162 β€” Global modules TAILQ mutated without mod_token (race)

Verdict: BUG CONFIRMED BY CODE INSPECTION; RACE NOT DETERMINISTICALLY

TRIGGERED IN SHORT DEMO. Fix VALIDATED.

Mechanism

  • Readers (unprivileged syscalls): sys_modnext (kern_module.c:253), sys_modfnext (:289), sys_modstat (:328), sys_modfind (:385) all walk the global modules TAILQ under lwkt_gettoken(&mod_token).
  • Writers (privileged paths): module_register (:141 TAILQ_INSERT_TAIL) and module_release (:178 TAILQ_REMOVE) mutate the SAME list but take ONLY kld_lock/llf_lock (acquired in linker_file_unload and linker_load_file in kern_linker.c).
  • The lock domains are disjoint β€” mod_token is an lwkt_token, llf_lock is a lockmgr lock; neither blocks the other.

A concurrent unpriv modstat walk and a privileged kldload/kldunload can race on the TAILQ head/next pointers, corrupting the list -> panic/UAF.

Trigger

modstat_race.c runs the unprivileged READER side (modnext loop):

$ ./modstat_race 5
DF-0162: did 3117560 modnext() iterations in 5 s (reader-side race surface)

Pair with a concurrent privileged writer:

# as root:  while true; do kldload ehci.ko; kldunload ehci.ko; done

The race is tight (Medium / AC:H); a 200-iteration and a 500-iteration concurrent churn both completed without panic on the audit guest. This is consistent with the high attack complexity in the CVSS (AC:H). The bug is real by code inspection (disjoint lock domains are confirmed) but not deterministically triggerable in a short demo.

Realism

The race requires a privileged concurrent operation (kldload or kldunload). From a strict unprivileged-only perspective, the bug cannot be triggered by an attacker who does not already control a root process loading/unloading modules. This is a hardening gap / DoS-by-race in environments where unprivileged users can spur privileged module churn (e.g. dev hotplug, auto-loading of netgraph nodes via socket options, etc.).

Fix (validated)

fix.diff takes mod_token around the TAILQ_INSERT_TAIL in module_register and around the TAILQ_REMOVE in module_release, serializing writers against the existing readers. On the patched kernel (#1, sha256 3e502901a2d3c80a357126a0d07e3ad316a5e388a5268dc78b8bc01f288f80b9), 500 concurrent kldload/kldunload ehci cycles + 4.8M modnext iterations completed without panic. The fix is correct (writers and readers now share a lock domain) and shows no regression.

Fix verification

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Fri Jul 17 21:29:31 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. modules TAILQ mod_token vs llf_lock disjoint lock domains. Race not panicked in demo (AC:H). Fix: writers take mod_token.