sysctl_rman (hw.bus.rman) leaks uninitialized kernel stack beyond DF-0092's padding: r_devname/rm_descr bytes after the NUL (up to 31/entry, incl. live kernel pointers) exported to unprivileged readers
| Field | Value |
|---|---|
| ID | DF-2854 |
| 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-908 / CWE-200 |
| File | sys/kern/subr_rman.c |
| Lines | 653-654, 684, 690, 707-716, 722 (node :734) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
struct u_resource / struct u_rman are uninitialized stack locals; the string fields are only partially written (strlcpy/ksnprintf write strlen+1 of 32 bytes) yet SYSCTL_OUT copies the FULL sizeof(struct) (80 bytes). The node is CTLFLAG_RD world-readable. DF-0092 documented only the 4 trailing padding bytes — the post-NUL tails are a ~8× larger distinct surface. Reproduced unprivileged (uid 1001): 99 leak marks/run over 88 exported resources, byte-identical across 3 runs, with the live kernel pointer 0xfffff80117ee5800 in every r_devname tail. The sibling sysctl_devices handler correctly bzeros its export struct — sysctl_rman forgot.
Recommended fix
Zero both structs before filling (validated fix.diff in
findings/poc/DF-2854/): bzero(&urm, sizeof(urm)); /
bzero(&ures, sizeof(ures));.
Timeline
- 2026-09-02 Discovered during pass-2 audit of subr_rman.c (GLM 5.3); unpriv leak reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2854 · 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| rman_leak.c | — | 4.2 KB | view raw | |
| run.sh | — | 146 B | view raw | |
| run.log | — | 33.5 KB | view raw | |
| leak_sample.txt | — | 3.4 KB | view raw | |
| env.txt | — | 209 B | view raw | |
| fix.diff | — | 595 B | view raw | |
| VERDICT.md | — | 3.3 KB | ↓ raw | |
| verdict.json | — | 3.8 KB | view raw | |
| manifest.json | — | 981 B | view raw | |
| fix_run.log | — | 28.7 KB | view raw |
DF-2854 VERDICT — REPRODUCED (baseline) / fixed by bzero (validated)
Bottom line
On the stock kernel, an unprivileged user (uid 1001, no groups,
securelevel -1) reading hw.bus.rman receives, per exported entry,
up to 31 bytes of uninitialized kernel stack from
struct u_resource.r_devname (and per-rman rm_descr), plus the 4
trailing pad bytes (DF-0092). On this guest the residue is a live
kernel virtual address repeated in every entry:
r_devname tail: "acpi0" | raw: 61 63 70 69 30 00 ff ff 00 58 ee 17 01 f8 ff ff
a c p i 0 \0 … [ = 0xfffff80117ee5800 ]
tail pad bytes 76-79: ff ff ff ff
88 resources + 5 rman descriptors were exported → 99 “UNINITIALIZED” marks per run; byte-for-byte identical across 3 runs (stable stack residue; deterministic on this boot). See run.log / leak_sample.txt.
Why it leaks (path:line)
- sys/kern/subr_rman.c:653-654 —
struct u_rman urm; struct u_resource ures;are uninitialized stack locals. - :684
strlcpy(urm.rm_descr, …)writes only strlen+1 of 32 bytes; :707-716ksnprintf/“nomatch”/'\0'write only strlen+1 of 32 bytes ofr_devname. - :690 and :722
SYSCTL_OUT(req, &x, sizeof(x))copy the entire struct (80 bytes) to userland. - :734 the node is
CTLFLAG_RD— world-readable (lib/libdevinfo walks it as a normal user; our PoC needed no privileges). - Contrast: the sibling
sysctl_deviceshandler deliberately doesbzero(&udev, sizeof(udev))(sys/kern/subr_bus.c:3892) — sysctl_rman simply forgot.
Relation to DF-0092 (not a re-report)
DF-0092 covers the 4 trailing padding bytes only and asserts the
named fields are set. They are not: the partially-initialized string
fields leak up to 31 bytes each — a distinct, ~8x larger surface in the
same function, with kernel-pointer content demonstrated. DF-0092’s
suggested struct u_resource ures = {}; would incidentally fix this
too; the root-cause statement (“named fields set”) is what this finding
corrects.
Impact ceiling
Kernel-stack disclosure to any local user: kernel pointers (KASLR / heap-layout reconnaissance; useful to weaponize adjacent memory bugs), possibly other residue depending on prior use of the reader’s kstack. No write primitive. Rubric: “local info leak of limited kernel memory” → Medium, bucket kernleak.
PoC changes (poc_changes)
- No seed; walker mirrors lib/libdevinfo/devinfo.c:263-345 (OID resolution via sysctl(0,3), generation from hw.bus.info).
- First cut had a wrong
struct u_businfo(used a generation-first layout; kernel’s is{int ub_version; int ub_generation}per sys/sys/bus.h:67) — every request returned EINVAL until fixed. - Run as unprivileged
maxxviavm.sh run_user.
Fix validation
fix.diff:bzero(&urm, sizeof(urm))/bzero(&ures, sizeof(ures))before filling (mirrors subr_bus.c:3892).- Applied in guest together with DF-2853’s guard; single
make nativekernel+ installkernel + reboot. - Patched-kernel expectation (met): all r_devname/rm_descr tails and
the 4 pad bytes read as zero;
grep -c UNINITIALIZED→ 0.
Kernel references
- sys/kern/subr_rman.c:653-654, :684, :702-722, :734
- sys/kern/subr_bus.c:3892 (correct bzero pattern)
- lib/libdevinfo/devinfo.c:263-345 (unprivileged walk precedent)
Fix verification
fixedfix.diff (two bzeros) applied together with DF-2853's guard; single nativekernel rebuild + installkernel + reboot. Exact PoC re-run as unprivileged user: leakmarks=0 (was 99), r_devname/rm_descr tails and tail pad bytes all zero. Leak is gone.
['findings/poc/DF-2854/fix_run.log (patched-kernel full run)', 'findings/poc/DF-2854/fix.diff']
Confirmed kernel references
- sys/kern/subr_rman.c:653-654
- sys/kern/subr_rman.c:684
- sys/kern/subr_rman.c:702-722
- sys/kern/subr_rman.c:734
- sys/kern/subr_bus.c:3892
- lib/libdevinfo/devinfo.c:263-345
Detail
Exploit chain
unpriv user -> sysctl hw.bus.rman.
Evidence (decisive lines)
["run.log: r_devname tail of 'acpi0' entry: '61 63 70 69 30 00 ff ff 00 58 ee 17 01 f8 ff ff' (0xfffff80117ee5800 leaked)", "run.log: 'tail pad bytes 76-79: ff ff ff ff' (DF-0092 subset)", 'leak_sample.txt: identical output across runs 2 and 3 (stable residue)', 'fix_run.log: patched kernel => leakmarks=0, r_devname tails and pad bytes all zero']
PoC changes
No seed. Walker mirrors lib/libdevinfo OID resolution. First cut mis-declared struct u_businfo ({u32 generation,int devices} instead of kernel's {int ub_version,int ub_generation}, sys/sys/bus.h:67) so every sysctl returned EINVAL until fixed. Run via vm.sh run_user (unprivileged maxx).
Verified recommended fix
bzero(&urm,sizeof(urm)) and bzero(&ures,sizeof(ures)) before filling in sysctl_rman (mirrors sysctl_devices' bzero at subr_bus.c:3892); subsumes DF-0092's padding fix.
Verdict
REPRODUCED on stock kernel as UNPRIVILEGED user (uid 1001, securelevel -1): reading hw.bus.rman returns partially-initialized kernel stack - u_resource.r_devname[32] and u_rman.rm_descr[32] are only written up to their NUL (strlcpy at :684; ksnprintf/'nomatch'/'\0' at :707-716) yet SYSCTL_OUT copies the full 80/64-byte structs (:690, :722). 88 resources + 5 rman descriptors exported; 99 leak marks per run, byte-identical across 3 runs; the residue contains the live kernel pointer 0xfffff80117ee5800 in every r_devname tail plus ff-byte tail padding (DF-0092's 4 bytes are a subset). Sibling handler sysctl_devices bzeros its export struct (subr_bus.c:3892) - sysctl_rman forgot; this finding is the partial-field-initialization surface beyond DF-0092's documented padding-only scope. Fix (bzero before fill) validated on rebuilt kernel: 0 leak marks, all tails zero.
No comments yet.