Missing privilege check on sys_kldstat()/sys_kldsym() leaks kernel symbol and module addresses
| Field | Value |
|---|---|
| ID | DF-0025 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-862 Missing Authorization |
| File | sys/kern/kern_linker.c |
| Lines | 940 (sys_kldstat), 1024 (sys_kldsym) |
| Area | kern |
| Confidence | likely |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
Only sys_kldload (:794) and sys_kldunload (:841) are gated by
caps_priv_check_self(SYSCAP_NOKLD). The query syscalls sys_kldstat
(:940) and sys_kldsym (:1024) have no privilege gate. sys_kldsym
resolves an arbitrary kernel symbol name to its absolute runtime address
(symval.value = ef->address + es->st_value, per link_elf.c/link_elf_obj.c),
and sys_kldstat returns each loaded module's base address (lf->address) and
size. Any local user can iterate fileids via kldnext and dump a complete
symbol→address map of the running kernel plus every loaded KLD — defeating
KASLR and handing an attacker developing another kernel exploit a ready symbol
map.
Root cause
/* sys_kldload :794 / sys_kldunload :841 -- gated */
if ((error = caps_priv_check_self(SYSCAP_NOKLD)) != 0)
return error;
/* sys_kldstat :940 / sys_kldsym :1024 -- NO gate */
The grep confirms caps_priv_check_self(SYSCAP_NOKLD) appears only at :794
and :841. Symbol resolution in sys/kern/link_elf.c /
sys/kern/link_elf_obj.c returns the absolute runtime address unmasked.
Threat model & preconditions
- Attacker position: any local unprivileged user.
- Privileges gained or impact: information disclosure — a complete kernel symbol→address map and the base address + size of every loaded module. A KASLR-defeat / symbol-map primitive that materially lowers the bar for exploiting any other kernel memory-safety bug (e.g. DF-0013). Standalone impact is info-leak only.
- Required config or capabilities: none; default kernel.
- Reachability:
kldsym(2)/kldstat(2)/kldnext(2)as any user.
Proof of concept
PoC source: findings/poc/DF-0025/kld_leak.c
Build & run (unprivileged)
cc -o kld_leak findings/poc/DF-0025/kld_leak.c ./kld_leak
Expected output
[+] kldsym("proc0") = 0xffffffff81xxxxxx (unprivileged KASLR/symbol leak)
[+] kldstat: loaded modules (base + size):
kernel id=1 base=0xffffffff80200000 size=1234567 refs=1
...
Impact
Unprivileged disclosure of the kernel's symbol map and module layout — a
KASLR-defeat / exploit-enabling primitive. Rated Low (info-leak only; matches
FreeBSD's historic ungated convention, but Linux restricts the analogous
/proc/kallsyms to root readers for exactly this reason).
Recommended fix
Gate sys_kldstat and sys_kldsym behind SYSCAP_NOKLD, matching
load/unload:
--- a/sys/kern/kern_linker.c
+++ b/sys/kern/kern_linker.c
@@ -940
+ if ((error = caps_priv_check_self(SYSCAP_NOKLD)) != 0)
+ return error;
@@ -1024
+ error = caps_priv_check_self(SYSCAP_NOKLD);
+ if (error)
+ return error;
(Confirm against any legitimate unprivileged consumer — e.g. the dynamic linker resolving symbols for a loaded module's userland glue — before restricting; if such a consumer exists, gate the address fields rather than the whole call, so name/existence remain queryable but addresses are redacted for unprivileged callers.)
References
sys/kern/kern_linker.c:940—sys_kldstat(no gate).sys/kern/kern_linker.c:1024—sys_kldsym(no gate).sys/kern/kern_linker.c:794,841— the gating pattern used by load/unload.- CWE-862 Missing Authorization.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/kern_linker.c. - pending Reported to DragonFlyBSD security contact.