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)
PoC verification
Evidence pack
findings/poc/DF-2970 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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
fixedA/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.
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.
No comments yet.