NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
kern/60035: CTLFLAG_PRIVATE can be bypassed for sysctl nodes with custom handlers in sysctl_dispatch()
>Number: 60035
>Category: kern
>Synopsis: CTLFLAG_PRIVATE can be bypassed for sysctl nodes with custom handlers in sysctl_dispatch()
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Wed Feb 25 01:55:00 +0000 2026
>Originator: Matthias Petermann
>Release: 10.1_STABLE
>Organization:
>Environment:
NetBSD vhost.lan 10.1_STABLE NetBSD 10.1_STABLE (GENERIC) #0: Tue Feb 24 23:08:59 CET 2026 mpeterma%builder.lan@localhost:/u/sysbuild/obj/sys/arch/amd64/compile/GENERIC amd64
>Description:
Overview / motivation:
----------------------
I am trying to harden kernel message buffer access. In loginit(), I marked both kern.msgbufsize and kern.msgbuf as private (CTLFLAG_PERMANENT | CTLFLAG_PRIVATE).
In addition, in a custom jail secmodel, I deny KAUTH_REQ_SYSTEM_SYSCTL_PRVT from jail context to block private sysctl reads (including kern.msgbuf).
Despite that, dmesg still succeeds in scenarios where access should be denied.
Observed behavior:
------------------
dmesg reads the message buffer via sysctl(CTL_KERN, KERN_MSGBUF), and the sysctl calls return success even when private-node access should be blocked by policy.
Expected behavior:
------------------
Any read of a CTLFLAG_PRIVATE node should consistently require successful KAUTH_REQ_SYSTEM_SYSCTL_PRVT authorization, regardless of whether the node is served by sysctl_lookup() or by a custom sysctl_func handler.
>How-To-Repeat:
Root cause analysis:
--------------------
1. sysctl_dispatch() resolves the node via sysctl_locate().
2. If the resolved node has sysctl_func, dispatch calls that handler directly; it does not go through sysctl_lookup().
3. The strict final-node private authorization check is in sysctl_lookup() (rnode->sysctl_flags & CTLFLAG_PRIVATE => kauth_authorize_system(...SYSCTL_PRVT...)).
4. Therefore, for custom-handler nodes, final-node private enforcement can be skipped unless explicitly re-checked in dispatch.
5. kern.msgbuf/kern.msgbufsize are exactly such custom-handler nodes (sysctl_msgbuf), so they are affected by this gap.
>Fix:
In sysctl_dispatch(), after sysctl_locate() succeeds and before invoking a custom sysctl_func, perform a final target-node private authorization check equivalent to sysctl_lookup()?s check. This makes CTLFLAG_PRIVATE semantics consistent across both generic and custom-handler nodes.
---
diff --git a/sys/kern/kern_sysctl.c b/sys/kern/kern_sysctl.c
index 51a125541fbd423c33e8b32960ab152e09b7443a..7db8f660840f0b7160570a51f5ace971eefb516a 100644
--- a/sys/kern/kern_sysctl.c
+++ b/sys/kern/kern_sysctl.c
@@ -380,50 +380,59 @@ sysctl_relock(void)
/*
* ********************************************************************
* the main sysctl dispatch routine. scans the given tree and picks a
* function to call based on what it finds.
* ********************************************************************
*/
int
sysctl_dispatch(SYSCTLFN_ARGS)
{
int error;
sysctlfn fn;
int ni;
KASSERT(rw_lock_held(&sysctl_treelock));
if (rnode && SYSCTL_VERS(rnode->sysctl_flags) != SYSCTL_VERSION) {
printf("sysctl_dispatch: rnode %p wrong version\n", rnode);
error = EINVAL;
goto out;
}
fn = NULL;
error = sysctl_locate(l, name, namelen, &rnode, &ni);
+ /*
+ * Enforce private-node read permissions on the final target even when
+ * dispatching through a custom sysctl handler rather than sysctl_lookup().
+ */
+ if (error == 0 && l != NULL && (rnode->sysctl_flags & CTLFLAG_PRIVATE) &&
+ (error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_SYSCTL,
+ KAUTH_REQ_SYSTEM_SYSCTL_PRVT, NULL, NULL, NULL)) != 0)
+ goto out;
+
if (rnode->sysctl_func != NULL) {
/*
* the node we ended up at has a function, so call it. it can
* hand off to query or create if it wants to.
*/
fn = rnode->sysctl_func;
} else if (error == 0) {
/*
* we found the node they were looking for, so do a lookup.
*/
fn = (sysctlfn)sysctl_lookup; /* XXX may write to rnode */
} else if (error == ENOENT && (ni + 1) == namelen && name[ni] < 0) {
/*
* prospective parent node found, but the terminal node was
* not. generic operations associate with the parent.
*/
switch (name[ni]) {
case CTL_QUERY:
fn = sysctl_query;
break;
case CTL_CREATE:
#if NKSYMS > 0
case CTL_CREATESYM:
#endif /* NKSYMS > 0 */
if (newp == NULL) {
Home |
Main Index |
Thread Index |
Old Index