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

Memory leak in clist_alloc_cblocks: old c_data never freed on resize

Summary

clist_alloc_cblocks(:61) kmalloc new data, bcopy from old(:71-74), cl->c_data=data(:80) WITHOUT kfree of old. Every ccmax change leaks old_ccmax*sizeof(short). Reachable: unpriv pty open+tcsetattr varying baud -> ttywatermarks -> clist_alloc_cblocks. Sustained -> kmem exhaustion DoS.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0207 Β· 14 files
FileTypeDescriptionSize
df0207_leak.c trigger-source userspace pty baud-toggle trigger (note: pty path does not change ccmax - see VERDICT) 2.6 KB view raw
df0207_harness.c exploit-harness kld that calls clist_alloc_cblocks directly with changing ccmax - definitive leak proof 1.6 KB view raw
Makefile build kld Makefile 113 B ↓ download
build.sh build-script build userspace + kld 864 B view raw
run.sh run-script load harness, measure M_TTYS 767 B view raw
leak_sample.txt leak-sample vmstat -m before/after showing 143K->29.4M leak 979 B view raw
fix_build.log build-log single-fix kernel build output (rc=0) 5.6 MB ↓ download
fix_run.log run-log FIXED kernel: M_TTYS stays 143K (no leak) 883 B view raw
env.txt environment uname, cc version 229 B view raw
fix.diff suggested-fix kfree old c_data before overwrite 611 B view raw
VERDICT.md verdict full root-cause + reachability analysis 4.1 KB ↓ raw
README.md readme human reproduce doc 821 B ↓ 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 reproduce doc
↓ download raw

DF-0207 β€” Memory leak in clist_alloc_cblocks (missing kfree of old c_data)

See VERDICT.md for the full analysis (root cause, proof, reachability nuance).

Reproduce (the leak)

./build.sh                 # builds the userspace trigger + the kld harness
sudo ./run.sh              # kldload harness; vmstat -m | grep ttys before/after

UNPATCHED kernel: M_TTYS MemUse jumps 143K -> ~29M (leak). FIXED kernel: M_TTYS MemUse stays 143K (flat, no leak).

The harness (df0207_harness.c) calls clist_alloc_cblocks() directly with a changing ccmax, which is the definitive proof the bug leaks on every resize.

Fix

fix.diff adds kfree(odata, M_TTYS) of the old buffer before overwriting cl->c_data. Validated: single-fix kernel #1 boots; harness no longer leaks (M_TTYS flat). See fix_run.log.

VERDICT.md verdict full root-cause + reachability analysis
↓ download raw

DF-0207 β€” Memory leak in clist_alloc_cblocks() (missing kfree of old c_data)

Verdict: REPRODUCED (leak primitive definitively demonstrated via direct invocation). Impact: leak / DoS β€” unbounded M_TTYS kernel-memory exhaustion. Not memory corruption, so there is no privilege-escalation chain; the ceiling is kmem-exhaustion DoS.

The bug (root cause)

sys/kern/tty_subr.c, clist_alloc_cblocks():

48: void
49: clist_alloc_cblocks(struct clist *cl, int ccmax)
50: {
51:     short *data;
...
61:     data = kmalloc(ccmax * sizeof(*data), M_TTYS, M_INTWAIT|M_ZERO);
...
71:         bcopy(cl->c_data + cl->c_cchead, data, n * sizeof(*data));   /* copy OLD -> new */
...
80:     cl->c_data = data;     /* <-- overwrites the OLD pointer WITHOUT kfree() */
81: }

Compare with clist_free_cblocks() (lines 86-100) which does kfree(data, M_TTYS). Every time ccmax != cl->c_ccmax (a resize), the previous c_data buffer is orphaned β€” a permanent leak of old_ccmax * sizeof(short) bytes of M_TTYS.

Proof (direct harness)

Because the finding's claimed unprivileged vector (pty + tcsetattr baud change) does not actually change ccmax on this guest (see Reachability note below), a kernel-module harness (df0207_harness.c) calls clist_alloc_cblocks() directly with an alternating ccmax (1024 ↔ 4096), 3000 iterations. Measured with vmstat -m | grep ttys:

Count MemUse Requests
BEFORE load 37 143K 70
AFTER load 5.90K 29.4M 5.93K

~5860 c_data buffers orphaned, ~29 MB of M_TTYS permanently leaked. Each subsequent clist_alloc_cblocks(cl, X) where X != cl->c_ccmax leaks again β€” unbounded.

Reachability (important nuance)

  • The bug fires whenever a clist's ccmax changes. Callers that resize: tty.c:1664/2435/2457/2489 (ttsetwater / ttypend), netgraph ng_tty/ng_h4, if_sl (SLIP).
  • The finding's stated trigger is partly incorrect: for a pty, t_ispeedwat/t_ospeedwat are 0 (never set by the pty driver), so ttsetwater()'s case 0: makes the computed outq/rawq buffer sizes constant regardless of baud (outq ccmax is always imax(ohiwat,2048)+OBUFSIZ+100; ohiwat clamps to ≀2048 for any speed). The early return if (ccmax == cl->c_ccmax) return; then prevents any realloc, so tcsetattr baud changes on a pty do not leak.
  • The only in-tree driver that sets t_*speedwat = (speed_t)-1 (the speed-dependent case that does vary ccmax with baud) is sio.c:2480-2481 (the COM serial driver). On this guest /dev/ttyd0 (sio0) is root:wheel 0600 β€” not openable by an unprivileged user β€” and it is the live console, so its baud-change path could not be driven from userspace here.
  • Conclusion: the leak is real and unbounded on any code path that resizes a clist. The realistic trigger on a stock system is a serial/tty driver or netgraph/SLIP path that varies ccmax; the unprivileged-pty+baud vector claimed by the finding does not hold. Reported as a confirmed leak (DoS-class), reachability = needs a ccmax-change path (sio baud / driver / netgraph), not a blanket unprivileged trigger.

The fix

fix.diff saves the old c_data pointer (odata) before overwriting cl->c_data, and kfree(odata, M_TTYS)s it afterward (guarding NULL). Minimal, targeted, mirrors clist_free_cblocks.

Fix validation

Built a single-fix kernel (DF-0207 fix only) on the with-src baseline: - Unpatched #0 baseline: harness load β†’ M_TTYS 143K β†’ 29.4M (leak). - Patched kernel: harness load β†’ M_TTYS stays flat (~143K, only the legitimately-live buffers) β€” leak eliminated.

(See run.log / fix_run.log for the before/after vmstat -m lines.)

Kernel refs

Fix verification

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Fri Jul 17 22:32:00 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (kld+harness). clist_alloc_cblocks old c_data never freed -> 29MB M_TTYS leak. kld harness proven.