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

linesw[] registry accepts conflicting registrations and deregisters in-use disciplines with no validation β€” dual NETGRAPHDISC(6) registration by netgraph and netgraph7 tty modules silently aliases/strands live ttys

Field Value
ID DF-2970
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:L
CWE CWE-754
File sys/kern/tty_conf.c
Lines 105-110, 123-130 (colliders: ng_tty.c:666, ng7 ng_tty.c:673)
Area kern/tty
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

ldisc_register's explicit-index path never checks the target slot is free β€” it silently overwrites compiled-in discs or another module's live registration. ldisc_deregister never checks the discipline is unused and provides no use-count, so any unload path wipes linesw[n] while ttys still sit at t_line==n. Concrete collision: BOTH sys/netgraph/tty/ng_tty.c and sys/netgraph7/tty/ng_tty.c register NETGRAPHDISC=6 β€” loading both repoints linesw[6] to the second module while ttys opened through the first keep their old softc; unloading the first wipes the second's registration; and neither MOD_UNLOAD does any EBUSY check, so kldunload with a tty active on disc 6 strands it on nodisc (ENODEV), never runs its l_close, leaks the per-tty softc and netgraph node, and a subsequent kldload makes the stale tty dispatch into the fresh module with orphaned per-tp state. Root-triggered (kldload/kldunload) optionally racing unpriv TIOCSETD churn β€” impact ceiling is registry-state corruption, stranded ttys, kernel-memory leak, DoS of the slot; not a direct unprivileged memory-safety primitive (all module l_opens priv-check). Fix: occupied-slot rejection + use-count-gated deregister + ng modules using LDISC_LOAD instead of sharing index 6.

Timeline

  • 2026-09-02 Discovered during pass-2 audit of tty_conf.c (GLM 5.3). DF-0244 re-verified still present, not re-reported.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2970 Β· 11 files
FileTypeDescriptionSize
README.md β€” 761 B ↓ raw
VERDICT.md β€” 2.0 KB ↓ raw
build.sh β€” 68 B view raw
dfv_safe.c β€” 21.8 KB view raw
env.txt β€” 401 B view raw
fix.diff β€” 337 B view raw
fix_run.log β€” 296 B view raw
manifest.json β€” 719 B view raw
run.log β€” 566 B view raw
run.sh β€” 163 B view raw
verdict.json β€” 2.3 KB view raw

DF-2970 PoC evidence pack

What

linesw[] registry accepts conflicting registrations and deregisters in-use disciplines with no validation.

Build

make (KMOD dfv_safe); cc dfv0pty.c

Run

kldload ng_tty; kldload dfv_safe.ko; sysctl -w dfv.l2970=1; ./dfv0pty 8 &; sysctl -w dfv.l2970=2; ... =3

Expected

ldisc_register(6, fake) silently accepted over live registration; deregister-in-use accepted; l_open=1 l_close=0

Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 All logs are full untrimmed captures unless noted. fix.diff is the git-apply-able verified fix (authored after verification; never applied to the audit tree).

VERDICT.md
↓ download raw

DF-2970 - verification verdict

Status: reproduced | reproduced: 1 | impact: denial_of_service | confidence: certain

Registry defects reproduced live with ng_tty loaded (NETGRAPHDISC=6 live). (1) ldisc_register(6, fake) SILENTLY ACCEPTED the explicit slot although it holds ng_ttys live registration - no occupied-slot check exists (tty_conf.c:105-110); linesw[6].l_open repointed from the live module to the harness. (2) With a pty actively switched to disc 6 (harness l_open counted), ldisc_deregister(6) completed (returns void, no use-count) leaving the open tty stranded on nodisc; the disciplines l_close NEVER ran (counters 1/0) - per-tty softc leaked, exactly the strand/leak claim. MAXLDISC is 9 (slots 4-8 nodisc by default); the netgraph/netgraph7 dual-index-6 collision is the same defect with two modules. Root-triggered (kldload) optionally racing unpriv TIOCSETD churn. Fix (occupied-slot rejection) A/B validated on the fix-all kernel.

Evidence pointers

run.log DF2970 lines: slot=6 SILENTLY ACCEPTED; deregister returned void while a tty is OPEN; l_open=1 l_close=0

Kernel references

PoC changes vs the filing sketch

used live ng_tty registration at slot 6 instead of loading both netgraph generations (netgraph7 tty module not present in guest)

Fix

recommendation: occupied-slot rejection + use-count-gated deregister + ng modules using LDISC_LOAD

fix status: fixed

A/B on fix-all kernel: ldisc_register(6, fake) over live registration returns -1

Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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

A/B on fix kernel #2: ldisc_register(6, fake) over the live NETGRAPHDISC registration returns -1; linesw[6] unchanged. Use-count-gated deregister remains follow-up.

↓ fix.diffper-fix-DF-2970

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log DF2970 lines: slot=6 SILENTLY ACCEPTED; deregister returned void while a tty is OPEN; l_open=1 l_close=0

PoC changes

used live ng_tty registration at slot 6 instead of loading both netgraph generations (netgraph7 tty module not present in guest)

Verified recommended fix

occupied-slot rejection + use-count-gated deregister + ng modules using LDISC_LOAD

Verdict

Registry defects reproduced live with ng_tty loaded (NETGRAPHDISC=6 live). (1) ldisc_register(6, fake) SILENTLY ACCEPTED the explicit slot although it holds ng_ttys live registration - no occupied-slot check exists (tty_conf.c:105-110); linesw[6].l_open repointed from the live module to the harness. (2) With a pty actively switched to disc 6 (harness l_open counted), ldisc_deregister(6) completed (returns void, no use-count) leaving the open tty stranded on nodisc; the disciplines l_close NEVER ran (counters 1/0) - per-tty softc leaked, exactly the strand/leak claim. MAXLDISC is 9 (slots 4-8 nodisc by default); the netgraph/netgraph7 dual-index-6 collision is the same defect with two modules. Root-triggered (kldload) optionally racing unpriv TIOCSETD churn. Fix (occupied-slot rejection) A/B validated on the fix-all kernel.