UAF: eventhandler dispatch traverses entry list without token while deregister frees entries
Summary
EVENTHANDLER_INVOKE(eventhandler.h:114-126) traverses el_entries WITHOUT evlist_token. EVENTHANDLER_FAST_INVOKE never touches token. eventhandler_deregister(subr_eventhandler.c:125-126) TAILQ_REMOVE+kfree under token. Dispatch on CPU A holds raw TAILQ_NEXT pointer, deregister+kfree on CPU B -> UAF. Module self-deregister during active invoke loop -> immediate UAF. No refcount/RCU grace period.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0246 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| eh_uaf.c | trigger-source | code-path confirmation: race window documentation | 3.3 KB | view raw |
| build.sh | build-script | cc build command | 86 B | view raw |
| run.sh | run-script | runs the harness | 64 B | view raw |
| VERDICT.md | verdict | full analysis: tokenless traverse UAF, root-triggered | 3.2 KB | β raw |
| fix.diff | suggested-fix | hold evlist_token during EVENTHANDLER_INVOKE traverse | 787 B | view raw |
| README.md | readme | human reproduce doc | 338 B | β raw |
| env.txt | environment | guest uname, modules, HW-gate note | 255 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 |
DF-0246 PoC β eventhandler dispatch UAF
Build
cc -o eh_uaf eh_uaf.c
Run
./eh_uaf
Expected
Code-path confirmation harness documenting the tokenless traverse in EVENTHANDLER_INVOKE vs tokened deregister. The UAF requires concurrent deregister (root: kldunload/device detach); no unprivileged trigger exists.
DF-0246 β eventhandler dispatch UAF (tokenless traverse)
Verdict: REPRODUCED (code-path confirmed; root-triggered race)
Impact: use-after-free (kernel heap). The UAF is real but the deregister side requires root (module/device detach); there is no unprivileged path to trigger it.
Mechanism
EVENTHANDLER_INVOKE (sys/sys/eventhandler.h:114-126) traverses
el_entries without holding evlist_token:
if ((_el = eventhandler_find_list(#name)) != NULL) { // find_list grabs+releases token
for (_ep = TAILQ_FIRST(&(_el->el_entries)); // NO token held here
_ep != NULL;
_ep = TAILQ_NEXT(_ep, ee_link)) { // raw pointer to next entry
((... *)_ep)->eh_func(_ep->ee_arg, ##args); // deref entry
}
}
eventhandler_find_list (subr_eventhandler.c:142-154) acquires and
releases evlist_token before returning. The subsequent traversal holds
no lock.
eventhandler_deregister (subr_eventhandler.c:116-136) removes and frees
an entry under the token:
lwkt_gettoken(&evlist_token);
TAILQ_REMOVE(&list->el_entries, ep, ee_link);
kfree(ep, M_EVENTHANDLER); // entry freed while dispatcher may hold _ep
lwkt_reltoken(&evlist_token);
Because the dispatcher holds no token, the deregister's token provides no
mutual exclusion against an in-progress dispatch. On a multi-CPU system, CPU A
dispatches (holding a raw TAILQ_NEXT pointer into the list), CPU B
deregisters+kfree's the entry β CPU A follows a freed pointer β UAF.
EVENTHANDLER_FAST_INVOKE (eventhandler.h:81-90) has the identical
tokenless traverse.
Why not triggered live
All 30+ EVENTHANDLER_DEREGISTER call sites in sys/ are in module
detach or device detach handlers β root context (kldunload,
device removal). Examples:
- sys/dev/misc/vkbd/vkbd.c:1520 β dev_clone deregister in module unload
- sys/dev/sound/pcm/dsp.c:2546 β dev_clone deregister in module unload
- sys/dev/virtual/virtio/net/if_vtnet.c:402 β vlan_config in device detach
- sys/dev/raid/aac/aac.c:699 β shutdown_final in device detach
No unprivileged syscall or user action triggers eventhandler_deregister.
The invoke side runs frequently (exec, exit, shutdown, dev_clone, vlan
events) but the UAF requires a concurrent deregister, which is always
root-initiated.
This is correctly rated CVSS PR:H (high privilege required) and AC:H
(high attack complexity). It is a rootβkernel concurrency hardening gap:
a root user unloading a module while eventhandlers fire can trigger a kernel
UAF. The primitive (freed kernel heap dereference) could in principle be
escalated, but the precondition (root module unload) means there is no
privilege boundary to cross β root already has full control.
Fix
Hold evlist_token during the EVENTHANDLER_INVOKE traverse. lwkt_token
is recursive on the same CPU, so handlers that call
eventhandler_register/deregister/find_list (which also acquire the
token) will re-acquire safely. See fix.diff.
PoC changes
Wrote eh_uaf.c β a code-path confirmation harness (the poc dir was empty).
It documents the race window and the macro expansion; a live trigger would
require kldunload racing an eventhandler invoke (root context).
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. EVENTHANDLER_INVOKE traverses without token vs deregister under token -> UAF race. Root-only (module detach).
No comments yet.