DragonFlyBSD Kernel Audit
← triage · dashboard
DF-2854

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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2854 · 10 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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-716 ksnprintf/“nomatch”/'\0' write only strlen+1 of 32 bytes of r_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_devices handler deliberately does bzero(&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 maxx via vm.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

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.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']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 2 12:02:45 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

unpriv user -> sysctl hw.bus.rman... -> kernel copies 80-byte stack struct with only named prefix fields set -> up to 31 uninitialized stack bytes per entry (kernel pointer 0xfffff80117ee5800 demonstrated) -> KASLR-defeat / heap-layout reconnaissance for adjacent memory bugs. Read-only primitive, no write.

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.