Unprivileged kernel-log/kernel-pointer disclosure channel: default-config UCONSOLE lets any user's pty become constty and subr_prf.c streams all kernel messages to it, bypassing security.unprivileged_read_msgbuf and nullifying ptr_restrict against the %p leak population
| Field | Value |
|---|---|
| ID | DF-2759 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| CWE | CWE-200 / CWE-552 |
| File | sys/kern/subr_prf.c |
| Lines | 396-405, 913-985, 1134-1143 (gate tty.c:969-976, config :36) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
The stock amd64 kernel config ships options UCONSOLE, which compiles
the caps_priv_check gate out of the TIOCCONS handler. Any unprivileged
local user therefore installs their own pty as constty. subr_prf.c
then treats that pty as the trusted console intercept: kputchar
diverts all console-destined output to TOLOG|TOWAKEUP whenever constty
is set, constty_daemon streams the msgbuf to constty's tty forever,
and cnwrite additionally redirects /dev/console writes to
constty->t_dev. Verified live on the stock guest: uid-1001 TIOCCONS
succeeds; root console markers reach the unprivileged pty instantly
(cnwrite redirect) and again β€1s later as <118>-tagged constty_daemon
msgbuf copies; a pure kprintf is delivered; and delivery continues
after root sets security.unprivileged_read_msgbuf=0 while the same
user's dmesg gets EPERM β that knob only gates the sysctl path, not
the constty channel. With ptr_restrict=0 default this is a silent,
persistent, real-time collection channel for every %p kernel-pointer
print in the tree (amplifies DF-2736, DF-2614, DF-2637, and any future
%p print).
Threat model & preconditions
Unprivileged local user obtains a live kernel-message feed on their own terminal: kernel/heap pointers (KASLR and slab-layout defeat, feeding any companion memory-corruption bug), kernel operational logs, and all future kprintf content β surviving admin attempts to lock dmesg away. Persistent and undetectable by the console operator (output is duplicated, not diverted). No userβroot route (info-disclosure class).
Proof of contest
VERIFIED on the stock guest (findings/poc/DF-2759/uconsole_grab.c): unpriv TIOCCONS SUCCESS; instant + delayed msgbuf-tagged marker delivery; delivery continues after unprivileged_read_msgbuf=0; pure kprintf delivered. Fix (drop UCONSOLE from X86_64_GENERIC β the resolution FreeBSD adopted for GENERIC in 2003 β and make the TIOCCONS privilege check unconditional in tty.c) validated in-guest: PoC now gets EPERM with zero kernel content on the pty, root TIOCCONS still works.
Recommended fix
Both hunks in findings/poc/DF-2759/fix.diff (either alone closes the default-config hole; both shipped).
Timeline
- 2026-08-30 Discovered during pass-2 audit of subr_prf.c (GLM 5.3); unpriv live-feed reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2759 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.3 KB | β raw | |
| VERDICT.md | β | 5.2 KB | β raw | |
| uconsole_grab.c | β | 2.8 KB | view raw | |
| build.sh | β | 173 B | view raw | |
| run.sh | β | 1.7 KB | view raw | |
| build.log | β | 68 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| run_patched.log | β | 132 B | view raw | |
| env.txt | β | 265 B | view raw | |
| fix.diff | β | 923 B | view raw | |
| fix_build.log | β | 951 B | view raw | |
| verdict.json | β | 4.9 KB | view raw |
DF-2759 β Unprivileged kernel-log/kernel-pointer disclosure via UCONSOLE TIOCCONS + constty_daemon
What
The stock DragonFly amd64 kernel config sys/config/X86_64_GENERIC (ident the
guest actually runs: X86_64_GENERIC) is built with options UCONSOLE
(line 36). That option compiles the caps_priv_check_td() gate out of the
TIOCCONS handler (sys/kern/tty.c:969-976), so any unprivileged user can
install their own pty as the kernel console intercept:
sys/kern/tty.c:977βconstty = tp;with no privilege check.sys/kern/subr_prf.c:396-397(kputchar) β onceconsttyis set, all TOCONS output is also copied to the msgbuf and the constty daemon is woken.sys/kern/subr_prf.c:913-985(constty_daemon) β streams msgbuf content toconstty's tty, forever, with no additional authorization.sys/kern/tty_cons.c:465(cnwrite) β every write(2) to/dev/consoleis redirected toconstty->t_dev(the attacker's pty).
Impact
An unprivileged local user receives a real-time stream of all kernel messages on their own terminal:
- bypasses
security.unprivileged_read_msgbuf=0(that knob only gates the sysctl read path,subr_prf.c:1134-1143); - turns every
%pkernel-pointer print (defaultsecurity.ptr_restrict=0,subr_prf.c:121) into an unprivileged, live pointer leak β amplifying the filed consumer-side leaks (DF-2736, DF-2614, DF-2637, ...); - persistent and silent: the operator's console still shows everything, the attacker just gets a duplicate feed.
Reproduce (host, guest already up)
scp -F dfbsd-qemu/config uconsole_grab.c dfbsd-maxx:/tmp/ vm.sh run_root 'mkdir -p /tmp/df2759 && chmod 777 /tmp/df2759 && mv /tmp/uconsole_grab.c /tmp/df2759/ && chown maxx:maxx /tmp/df2759/uconsole_grab.c' vm.sh run_user 'cd /tmp/df2759 && cc -O -Wall -o uconsole_grab uconsole_grab.c' # build.sh sh findings/poc/DF-2759/run.sh # run.sh
Expected (baseline, stock kernel)
[0.00] uid=1001 ... TIOCCONS: SUCCESS - constty now points at this UNPRIVILEGED pty
[3.02] master received 29 bytes: DF2759-MARKER1-direct+daemon <- direct cnwrite redirect
[3.56] master received 35 bytes: <118>DF2759-MARKER1-direct+daemon <- constty_daemon msgbuf stream
dmesg: sysctl kern.msgbuf: Operation not permitted <- user locked out of msgbuf
[8.56] master received 79 bytes: <118>DF2759-MARKER2-after-msgbuf-locked
tap0: MAC address: 00:bd:ab:30:01:00 <- pure kprintf delivered too
The <118>-tagged copies arriving AFTER security.unprivileged_read_msgbuf
was set to 0 prove both the msgbuf-stream channel and the knob bypass.
Fix validation
fix.diff removes options UCONSOLE from X86_64_GENERIC and makes the
TIOCCONS privilege check unconditional in sys/kern/tty.c. With the patched
kernel the same PoC prints:
TIOCCONS FAILED: Operation not permitted (UCONSOLE gate not present in this kernel)
and no kernel content reaches the pty. See VERDICT.md.
Contents
uconsole_grab.cβ PoC (unprivileged pty grab + master-side capture)build.sh/run.shβ exact commandsbuild.log/run.logβ untrimmed logsenv.txtβ guest identity, knobsfix.diffβ git-apply-able fix (validated in-guest)VERDICT.md,manifest.json,verdict.json
DF-2759 β VERDICT
Status: REPRODUCED (impact: leak β unprivileged kernel-log/kernel-pointer disclosure channel)
What was claimed
options UCONSOLE in the default sys/config/X86_64_GENERIC:36 removes the
privilege check from TIOCCONS (sys/kern/tty.c:969-976), so any unprivileged
user can point the kernel's constty at their own pty. subr_prf.c then treats
that pty as the console intercept:
sys/kern/subr_prf.c:396-405βkputchar(): withconsttyset, all console-destined kprintf output is additionally logged to the msgbuf and wakes the constty daemon (TOWAKEUP).sys/kern/subr_prf.c:913-985βconstty_daemon(): streams the msgbuf (all subsequent kernel messages) toconstty's tty, no authorization.sys/kern/tty_cons.c:465βcnwrite(): user writes to/dev/consoleare redirected toconstty->t_dev.
Bypass relationship: security.unprivileged_read_msgbuf gates only the
sysctl read path (sys/kern/subr_prf.c:1134-1143); it does not touch the
constty channel. security.ptr_restrict defaults to 0
(sys/kern/subr_prf.c:121), so %p prints (the DF-2736/DF-2614/DF-2637
consumer-side leak population) flow through this channel unmasked.
How it was verified (stock kernel #0, X86_64_GENERIC, guest "dfbsd")
- Built
uconsole_grab.cas usermaxx(uid 1001) in-guest (build.log). ioctl(slave, TIOCCONS, 1)as uid 1001 β SUCCESS (run.log line[0.00] TIOCCONS: SUCCESS).- Root
echo DF2759-MARKER1 > /dev/console: *[3.02]instant copy on the attacker's master (cnwrite redirect), and *[3.56] <118>DF2759-MARKER1...β the constty_daemon msgbuf stream copy (syslog pri tag<>is added bymsglogchar,subr_prf.c:1014-1023β unambiguous marker of the daemon/msgbuf path). - Root
sysctl -w security.unprivileged_read_msgbuf=0; userdmesgβsysctl kern.msgbuf: Operation not permitted; rootecho DF2759-MARKER2 > /dev/consoleβ[8.56] <118>MARKER2still delivered to the unprivileged pty β the security knob is bypassed. ifconfig tap0 create(root; a purekprintf, no console write) βtap0: MAC address: 00:bd:ab:30:01:00delivered to the unprivileged pty β the channel carries arbitrary kernel log content, i.e. every%pprint would equally be delivered (ptr_restrict=0 confirmed live in env.txt).
Why it is not higher than Medium
The channel discloses kernel log content (including kernel/heap pointers via
existing %p prints) to unprivileged users on the default config, defeating
two admin-facing mitigations. It is not itself an arbitrary-read primitive.
It is the amplifier that makes the filed %p consumer leaks (rated Low
individually) trivially collectible by any local user.
Fix validation (fix.diff β applied to guest /usr/src, kernel rebuilt)
- fix.diff (git-apply-verified on the host repo) removes
options UCONSOLEfromsys/config/X86_64_GENERICAND makes the TIOCCONScaps_priv_check_td(SYSCAP_RESTRICTEDROOT)unconditional insys/kern/tty.c(either hunk alone closes the default-config hole). make -j6 nativekernel && make installkernelin-guest (fix_build via /tmp/fixbuild.log), rebooted intoDragonFly 6.5-DEVELOPMENT #1: Mon Aug 31 21:24:15 UTC 2026.- Re-ran the identical PoC as uid 1001 (run_patched.log):
[0.00] TIOCCONS FAILED: Operation not permittedβ no kernel content reached the pty (marker writes and the tap0 kprintf produced zero bytes on the master). - Root cross-check on the patched kernel:
TIOCCONS: SUCCESSfor uid 0 β the gate restores intended semantics, not breakage.
False-positive considerations β killed candidates from this pass
(pass-2 depth; recorded so the negative result has weight)
* constty dangling-pointer UAF (constty_daemon unlocked tp = constty):
ptys never free pti (sys/kern/tty_pty.c:279 "pti structure remains
intact"), nmdm never frees its softc (no kfree in sys/dev/misc/nmdm/),
serial softcs persist; a stale constty always lands in live memory and
tputchar returns -1 once TS_CONNECTED is cleared (ttyclose,
sys/kern/tty.c:251-262).
* uprintf/tprintf torn s_ttyvp/s_ttyp read β NULL tputchar:
s_ttyp is stored before s_ttyvp (tty.c:1197 β devfs_vnops.c:1606),
and both old/new ttys are ttyhold()-referenced (tty.c:1189-1199);
s_ttyvp != NULL implies a valid s_ttyp.
* %px ptr_restrict escape: zero in-tree users.
* kprintf_logging/log_console_output RW sysctls: writes are
caps-gated (sys/kern/kern_sysctl.c:1446-1447).
* %pb%i divide-by-zero on base char \0 / 1 << (n-1) UB: all 163
in-tree bit-strings are compile-time constants (rg-verified).
* width-accumulation int overflow in the digits loop (subr_prf.c:652-657):
kernel-constant formats only; PCHAR output is bounded by the sink.
* sysctl_kern_msgbuf third-branch length n - rindex_modulo: KNOWN
(DF-0035 / DF-2586 family) β not re-reported; msgbuf_clear races and
msgaddchar MP races stay within the tolerant chunk-advance protocol
(subr_prf.c:1060-1071).
* msg_size < 2048 breaking xindex - msg_size + 2048: unreachable
(MSGBUF_SIZE = 1 MB, sys/sys/msgbuf.h:65).
* kvsnprintf truncation/NUL logic (subr_prf.c:456-467, 493-502): correct.
Fix verification
fixedfix.diff (config UCONSOLE removal + unconditional caps gate in tty.c) applied to guest /usr/src, nativekernel rebuilt and installed, guest rebooted into kernel #1. Identical PoC as uid 1001: TIOCCONS now fails with EPERM and no kernel content reaches the pty (run_patched.log); root TIOCCONS still succeeds β gate restored, feature intact for privileged use.
['fix.diff (git apply --check clean on the audit repo)', "fix_build.log: '>>> Kernel install for X86_64_GENERIC completed' + patched-kernel uname #1", "run_patched.log: 'TIOCCONS FAILED: Operation not permitted' / zero bytes captured", 'root cross-check: uid=0 TIOCCONS SUCCESS on patched kernel']
Confirmed kernel references
- sys/config/X86_64_GENERIC:36
- sys/kern/tty.c:962-980
- sys/kern/subr_prf.c:396-405
- sys/kern/subr_prf.c:913-985
- sys/kern/subr_prf.c:1014-1023
- sys/kern/subr_prf.c:1134-1143
- sys/kern/subr_prf.c:121
- sys/kern/tty_cons.c:460-477
Detail
Exploit chain
unpriv user: posix_openpt -> ioctl(slave, TIOCCONS, 1) [no privilege needed under UCONSOLE] -> read(master): real-time full kernel-log stream incl. every %p pointer print; persists silently after the user locks dmesg via security.unprivileged_read_msgbuf=0. Pointer values feed KASLR/heap-layout disclosure for any companion memory-corruption bug.
Evidence (decisive lines)
["run.log: '[0.00] TIOCCONS: SUCCESS - constty now points at this UNPRIVILEGED pty' (uid=1001)", "run.log: [3.56] '<118>DF2759-MARKER1-direct+daemon' β constty_daemon msgbuf copy (pri tag from msglogchar subr_prf.c:1014-1023)", "run.log: dmesg EPERM + [8.56] '<118>DF2759-MARKER2-after-msgbuf-locked' β security.unprivileged_read_msgbuf bypass", "run.log: 'tap0: MAC address: ...' β pure kprintf content delivered to the unpriv pty", "run_patched.log: 'TIOCCONS FAILED: Operation not permitted' on the fix.diff kernel; root cross-check still succeeds", 'env.txt: ptr_restrict=0 default confirmed live; uname shows X86_64_GENERIC']
PoC changes
Seed PoC was written fresh for this finding (no prior draft): posix_openpt/grantpt/unlockpt pty setup, raw termios on the slave to avoid echo/CR mangling, TIOCCONS with int 1, poll+read loop on the master with monotonic timestamps so the direct-forward vs delayed daemon copies are distinguishable.
Verified recommended fix
Remove 'options UCONSOLE' from sys/config/X86_64_GENERIC and make the TIOCCONS caps_priv_check_td(SYSCAP_RESTRICTEDROOT) unconditional in sys/kern/tty.c (fix.diff, validated in-guest).
Verdict
Reproduced on the stock guest kernel (X86_64_GENERIC, which ships options UCONSOLE at sys/config/X86_64_GENERIC:36). Unprivileged uid-1001 ioctl(TIOCCONS) succeeds because the caps_priv_check gate is compiled out (sys/kern/tty.c:969-976); constty then points at the attacker's pty and subr_prf.c's kputchar (subr_prf.c:396-405) + constty_daemon (subr_prf.c:913-985) stream all kernel messages to it, while cnwrite (tty_cons.c:465) redirects /dev/console writes there too. Demonstrated live: root console markers arriving on the unprivileged pty both instantly and as <118>-tagged constty_daemon msgbuf copies; a pure kprintf (tap0 attach) delivered; and delivery continuing after security.unprivileged_read_msgbuf=0 while the same user's dmesg gets EPERM β a direct bypass of the admin knob, and with ptr_restrict=0 default it is a live collection channel for the entire %p kernel-pointer leak population (DF-2736/2614/2637 consumer side).
No comments yet.