_create_workqueue_common error path leaks wq->workers array and orphans already-scheduled worker threads
- File:
sys/dev/drm/linux_workqueue.c - Lines: 214, 217β238 (loop), 229β234 (error path)
- Severity: Low
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L - CWE: CWE-401 Memory Leak, CWE-772 Missing Release of Resource after Effective Lifetime
- Confidence: certain
- Status: new
Summary
On lwkt_create failure at iteration i, the error path frees wq (line 233)
but does NOT free the separately-allocated wq->workers array (allocated at
line 214) and does NOT terminate the worker threads for iterations 0..i-1
that were already created and scheduled (line 237).
The code itself acknowledges this with a XXX comment at line 232.
Root cause
linux_workqueue.c:217-238:
for (int i = 0; i < wq->num_workers; i++) {
struct workqueue_worker *worker = &(*wq->workers)[i];
lockinit(&worker->worker_lock, "lwq", 0, 0);
STAILQ_INIT(&worker->ws_list_head);
if (wq->num_workers > 1) {
error = lwkt_create(wq_worker_thread, worker,
&worker->worker_thread, NULL, TDF_NOSTART, i, "%s/%d", name, i);
} else {
error = lwkt_create(wq_worker_thread, worker,
&worker->worker_thread, NULL, TDF_NOSTART, -1, name);
}
if (error) {
kprintf("%s: lwkt_create(%s/%d): error %d",
__func__, name, i, error);
/* XXX: destroy kernel threads and free workers[] if applicable */
kfree(wq); /* frees wq struct ONLY */
return NULL;
// BUG 1: wq->workers (line 214 allocation) is NOT freed -- memory leak.
// BUG 2: worker threads 0..i-1 were lwkt_schedule'd (line 237) -- they're RUNNING.
// They are not terminated. They are orphaned.
}
lwkt_setpri_initial(worker->worker_thread, priority);
lwkt_schedule(worker->worker_thread); /* thread starts running */
}
The kfree(wq) at line 233 frees only the wq struct. The wq->workers
flexible-array pointer (allocated as a separate kmalloc at line 214:
kmalloc(sizeof(struct workqueue_worker) * wq->num_workers, M_DRM, M_WAITOK | M_ZERO))
is leaked.
Additionally, worker threads created and scheduled in prior iterations
(indices 0..i-1) continue running with their worker pointers into the
leaked (but still-allocated) workers array β they are orphaned threads that
can never be stopped.
Threat model
This requires lwkt_create to fail, which happens under kernel thread table
exhaustion or severe memory pressure.
If an attacker can cause repeated workqueue creation attempts that partially
fail (e.g., by exhausting the thread table), each failed attempt leaks
sizeof(workqueue_struct) + ncpus * sizeof(workqueue_worker) bytes and orphans
ncpus kernel threads.
Over time this exhausts kernel memory and thread table slots, causing denial
of service. The orphan threads are benign (they spin in lksleep forever) but
consume scheduling resources.
Privilege required: ability to trigger alloc_workqueue, which is typically
module-load / device-probe (semi-privileged).
Proof of concept
Conceptual trigger (difficult to force reliably β requires thread table exhaustion):
- Exhaust kernel thread creation capacity (e.g., create thousands of LWKT threads via a malicious or buggy kld, or by mounting many filesystems that spawn worker threads).
- Load a DRM driver module β
alloc_workqueuewill partially fail at some iterationi. - Observe via
vmstat/gdbthatwq->workersmemory is leaked and worker threads0..i-1are orphaned. - Repeat to exhaust kernel memory.
This is a resource exhaustion DoS that requires an unusual pre-condition (thread table near-full).
Recommended fix
--- a/sys/dev/drm/linux_workqueue.c
+++ b/sys/dev/drm/linux_workqueue.c
@@ -227,10 +227,18 @@ _create_workqueue_common(const char *name, int flags)
if (error) {
kprintf("%s: lwkt_create(%s/%d): error %d",
__func__, name, i, error);
- /* XXX: destroy kernel threads and free workers[] if applicable */
+ /* Clean up: terminate already-created threads and free everything */
+ for (int j = 0; j < i; j++) {
+ struct workqueue_worker *w = &(*wq->workers)[j];
+ if (w->worker_thread != NULL)
+ lwkt_terminate(w->worker_thread);
+ lockuninit(&w->worker_lock);
+ }
+ kfree(wq->workers, M_DRM);
kfree(wq);
return NULL;
}
References
sys/dev/drm/linux_workqueue.c:214βwq->workersallocationsys/dev/drm/linux_workqueue.c:217-238β worker creation loopsys/dev/drm/linux_workqueue.c:229-234β error path that only freeswq
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1980 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.2 KB | β raw |
| fix.diff | suggested-fix | Fix: Free wq->workers array and wake orphaned threads on error path. | 691 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-1980 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/drm/linux_workqueue.c:229-234
Mechanism: _create_workqueue_common error path kfree(wq) only frees wq struct. wq->workers (separately kmalloc'd) leaked. Worker threads 0..i-1 orphaned, spinning in lksleep forever.
Hardware dependency: Requires DRM driver using Linux workqueue compat (compile-time path).
Fix: Free wq->workers array and wake orphaned threads on error path.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/drm/linux_workqueue.c:229-234 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- l
- i
- n
- u
- x
- _
- w
- o
- r
- k
- q
- u
- e
- u
- e
- .
- c
- :
- 2
- 2
- 9
- -
- 2
- 3
- 4
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/drm/linux_workqueue.c:229-234. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: Error path leaks wq->workers + orphans threads. Free workers + wake threads.
Verified recommended fix
See fix.diff. Error path leaks wq->workers + orphans threads. Free workers + wake threads.
Verdict
REPRODUCED (source-only). sys/dev/drm/linux_workqueue.c:229-234: Error path leaks wq->workers + orphans threads. Free workers + wake threads.
No comments yet.