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

World-readable sysctl kern.file exports raw kernel heap pointers (kinfo_file.f_file/f_data) and cross-user fd state (types/flags/offsets) to unprivileged users

Field Value
ID DF-2684
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
CWE CWE-200 Exposure of Sensitive Information
File sys/kern/kern_descrip.c
Lines 3490 (sink subr_kcore.c:64/69)
Area kern
Confidence certain
Discovered 2026-08-29
Pass 2 (GLM 5.3 second pass)
Bucket kernleak
Reported pending
Known CVE none
CVE match novel

Summary

sysctl_kern_file_callback (:3453-3503) filters by prison only; sysctl_root gates writes only, so kern.file is world-readable. kcore_make_file copies ufile->f_file = kfile and ufile->f_data = kfile->f_data β€” direct kernel-heap addresses of every open struct file and its vnode/socket β€” plus f_offset/f_flag/f_count of every process's fds. Userland sysctl(8) discards CTLTYPE_OPAQUE output, hiding the leak from casual inspection; a direct sysctl(3) read returns it all.

Threat model & preconditions

Unprivileged local info leak: defeats KASLR (were it enabled) and reveals objcache/slab layout for heap-grooming against UAF-class bugs (e.g. DF-2682); separately discloses other users' β€” including root's β€” descriptor types, flags and file offsets (demonstrated: root's read position on a password file visible to uid 1001).

Proof of concept

VERIFIED (findings/poc/DF-2684/kfile_leak.c): direct sysctl as uid 1001 β†’ "entries with kernel pointers: 136 / entries belonging to OTHER uids: 120 / RESULT: LEAK CONFIRMED" with direct-map f_file addresses. Fix validated in-guest (nativekernel + install + reboot): post-fix uid 1001 sees f_file=0x0/f_data=0x0 on all rows (0 pointers) while root still sees 143 β€” deterministic baseline-vs-patched.

Mask kf.f_file/kf.f_data for readers with td_ucred->cr_uid != 0 in sysctl_kern_file_callback (validated fix.diff in the pack); consider p_cansee()-style row filtering for the cross-user fd-offset disclosure as a follow-up.

Timeline

  • 2026-08-29 Discovered during pass-2 audit of kern_descrip.c (GLM 5.3); leak reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2684 Β· 11 files
FileTypeDescriptionSize
kfile_leak.c β€” 2.4 KB view raw
build.sh β€” 110 B view raw
run.sh β€” 74 B view raw
run.log β€” 1.1 KB view raw
run_fixed.log β€” 731 B view raw
leak_sample.txt β€” 460 B view raw
env.txt β€” 326 B view raw
VERDICT.md β€” 3.2 KB ↓ raw
fix.diff β€” 515 B view raw
verdict.json β€” 3.0 KB view raw
manifest.json β€” 774 B view raw
VERDICT.md
↓ download raw

DF-2684 β€” VERDICT

status: reproduced (impact: leak); fix validated (fixed).

Root cause

sysctl_kern_file (sys/kern/kern_descrip.c:3410-3450) and its callback sysctl_kern_file_callback (kern_descrip.c:3453-3503) export every process's descriptor table through kcore_make_file() (sys/kern/subr_kcore.c:58-77), which copies raw kernel pointers into the exported structure:

ufile->f_file = kfile;             /* address of struct file */
ufile->f_data = kfile->f_data;     /* vnode / socket / pipe */

The sysctl read path applies no privilege gate: sysctl_root() (sys/kern/kern_sysctl.c:1410+) privilege-checks only writes (CTLFLAG_WR / SYSCAP_NOSYSCTL_WR); kern.file is CTLFLAG_RD, and the callback's only filter is the prison check at kern_descrip.c:3464. Any unprivileged local user therefore receives, for every process in their jail (i.e. the whole system for the common non-jailed case):

  • the kernel-heap address of every open struct file (objcache slab layout disclosure -- defeats any future KASLR, aids heap grooming for UAF/overflow exploits elsewhere);
  • the kernel address of the underlying vnode/socket/pipe;
  • other users' fd state: type, flags, reference count and FILE OFFSET (e.g. root's read position on a sensitive file), an information disclosure across uid boundaries.

(Note: the base-system sysctl(8) tool discards CTLTYPE_OPAQUE output, which is why this is not commonly noticed -- a direct sysctl(3) call reads it fine.)

Reproduction

kfile_leak.c (unprivileged, uid 1001) calls sysctl({CTL_KERN, KERN_FILE}, ...) and parses struct kinfo_file. On the stock INVARIANTS guest:

uid=1001 fetched 136 kinfo_file entries (9792 bytes)
  pid=278  uid=0  fd=0  type=1 f_file=0xfffff80116c49380 f_data=0xfffff8008f521b00 off=112 fl=3
  ...
entries with kernel pointers: 136
entries belonging to OTHER uids: 120
RESULT: LEAK CONFIRMED

120 of 136 rows belong to other uids (0, 77, ...) -- root's fd offsets included (off=112 on root's open of the password file in the demonstration).

Fix validation (fixed)

fix.diff masks kf.f_file / kf.f_data for readers whose td_ucred->cr_uid != 0 in sysctl_kern_file_callback. Built into the guest kernel (make -j6 nativekernel KERNCONF=X86_64_GENERIC, rc=0, installed, rebooted; the same kernel also carried the independent sigio restructure under test for DF-2682/2683 -- the sysctl mask change is in a different function and branch) and re-ran the exact PoC:

as uid 1001: entries with kernel pointers: 0   (f_file=0x0, f_data=0x0)
as root    : entries with kernel pointers: 143 (unchanged behavior)

Baseline leak vs patched mask, deterministic. A complete upstream fix may additionally want p_cansee()-style filtering of other users' rows (as this pack's cross-uid offset disclosure documents); the shipped minimal fix addresses the kernel-pointer disclosure.

Bottom line

World-readable kern.file hands unprivileged users kernel heap pointers for every open file in the system plus cross-user descriptor state (including file offsets). Pointer disclosure confirmed and masked by the validated fix.

Fix verification

not_testable
↓ fix.diffper-fix-DF-2684

Confirmed kernel references

Detail

Exploit chain

unprivileged sysctl(3) {CTL_KERN, KERN_FILE} -> kernel-heap addresses of every struct file and its vnode/socket + other users' fd offsets; the addresses are direct-map slab pointers usable to defeat KASLR and to aim UAF-reclaim grooming (e.g. for DF-2682-class bugs) -- no further chain needed for the leak itself.

Evidence (decisive lines)

["run.log: 'entries with kernel pointers: 136 / entries belonging to OTHER uids: 120 / RESULT: LEAK CONFIRMED' as uid 1001", 'leak_sample.txt: pointer values across runs (0xfffff8xxxxxxxxxx direct-map slab addresses)', "run_fixed.log: post-fix 'entries with kernel pointers: 0' as uid 1001 and '143' as root", 'VERDICT.md: root cause, why sysctl(8) hides it (opaque type), fix validation narrative']

PoC changes

Direct sysctl(3) consumer written from scratch (userland sysctl(8) discards CTLTYPE_OPAQUE output, so the seed idea of shelling out to sysctl(8) showed nothing).

Verified recommended fix

Mask kinfo_file.f_file/f_data for unprivileged readers in sysctl_kern_file_callback (validated fix.diff); consider p_cansee()-based row filtering for cross-user fd state.

Verdict

sysctl kern.file (CTL_KERN/KERN_FILE) is world-readable: sysctl_root() gates only writes, and sysctl_kern_file_callback (kern_descrip.c:3453-3503) filters by prison only. kcore_make_file (subr_kcore.c:58-77) copies ufile->f_file = kfile and ufile->f_data = kfile->f_data -- raw kernel heap pointers -- into every exported kinfo_file. Unprivileged uid 1001 read 136 entries: 136 carried non-NULL kernel pointers (struct file and vnode/socket addresses) and 120 belonged to other uids including root's fd types/flags/offsets (off=112 on root's password-file read). Pointer disclosure aids KASLR defeat and heap grooming for memory-corruption exploits; cross-uid fd-offset disclosure is an information leak across uid boundaries. Validated fix masks f_file/f_data for uid!=0: post-fix unprivileged read shows 0 pointers while root still sees 143 -- baseline-vs-patched deterministic.