git: 96bf349029d4 - main - cred: Fix group_is_primary()
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 25 Aug 2026 15:59:21 UTC
The branch main has been updated by markj:
URL: https://cgit.FreeBSD.org/src/commit/?id=96bf349029d495c6aa2d67efc14b24aad7989e3b
commit 96bf349029d495c6aa2d67efc14b24aad7989e3b
Author: Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-08-24 18:13:29 +0000
Commit: Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-08-25 15:46:24 +0000
cred: Fix group_is_primary()
This helper wasn't updated in commit be1f7435ef21, so in reality it was
testing whether "gid" is the first supplemental group. If a user
doesn't belong to a supplementary group, then it's testing an
uninitialized slot; since ucreds are allocated with M_ZERO, this
typically means that we're testing gid == 0.
group_is_primary() has exactly one use, in mac_do. There, it's used to
determine whether the requested primary GID can be used in a setcred(2)
call when the ruleset does not explicitly specify a target primary GID.
I believe this is mostly exploitable by daemons which have explicitly
dropped privileges and called setgroups(0, NULL); logged in users will
have a non-empty supplementary group list by virtue of having gone
through initgroups(3).
Fix group_is_primary(), and add a regression test.
Approved by: so
Security: FreeBSD-SA-26:59.mac_do
Security: CVE-2026-58092
Reported by: Hazley Samsudin of GovTech CSG
Fixes: be1f7435ef21 ("kern: start tracking cr_gid outside of cr_groups[]")
Reviewed by: olce, kevans
Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D59051
---
sys/sys/ucred.h | 2 +-
tests/sys/mac/do/Makefile | 2 ++
tests/sys/mac/do/regression.c | 83 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 86 insertions(+), 1 deletion(-)
diff --git a/sys/sys/ucred.h b/sys/sys/ucred.h
index 4bf48a5e4b87..19b45a5541a7 100644
--- a/sys/sys/ucred.h
+++ b/sys/sys/ucred.h
@@ -267,7 +267,7 @@ bool cr_xids_subset(struct ucred *active_cred, struct ucred *obj_cred);
static inline bool
group_is_primary(const gid_t gid, const struct ucred *const cred)
{
- return (gid == cred->cr_groups[0] || gid == cred->cr_rgid ||
+ return (gid == cred->cr_gid || gid == cred->cr_rgid ||
gid == cred->cr_svgid);
}
bool group_is_supplementary(const gid_t gid, const struct ucred *const cred);
diff --git a/tests/sys/mac/do/Makefile b/tests/sys/mac/do/Makefile
index 0c40f65b65f6..0b88700c624c 100644
--- a/tests/sys/mac/do/Makefile
+++ b/tests/sys/mac/do/Makefile
@@ -2,6 +2,8 @@ PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/mac/do
+ATF_TESTS_C= regression
+
ATF_TESTS_SH+= valid_configs invalid_configs consistency
${PACKAGE}FILES+= common.sh
diff --git a/tests/sys/mac/do/regression.c b/tests/sys/mac/do/regression.c
new file mode 100644
index 000000000000..bbf284803e4a
--- /dev/null
+++ b/tests/sys/mac/do/regression.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2026 The FreeBSD Foundation
+ *
+ * This software was developed by Mark Johnston under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/sysctl.h>
+#include <sys/ucred.h>
+
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+/*
+ * Regression test for a bug which erroneously allowed the setcred() call below.
+ */
+ATF_TC(empty_supplementary_group_list);
+ATF_TC_HEAD(empty_supplementary_group_list, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(empty_supplementary_group_list, tc)
+{
+ struct setcred cred = SETCRED_INITIALIZER;
+ struct passwd *passwd;
+ const char *user;
+ char path[PATH_MAX], *progname, *rule;
+ int flags;
+
+ if (!atf_tc_has_config_var(tc, "unprivileged_user"))
+ atf_tc_skip("unprivileged_user not set");
+
+ user = atf_tc_get_config_var(tc, "unprivileged_user");
+ passwd = getpwnam(user);
+ ATF_REQUIRE(passwd != NULL);
+ ATF_REQUIRE_MSG(passwd->pw_uid != 0,
+ "unprivileged user must not be root");
+ ATF_REQUIRE_MSG(passwd->pw_gid != 0,
+ "unprivileged user group must not be wheel");
+
+ (void)asprintf(&rule, "uid=%d>uid=%d;gid=0>uid=0",
+ passwd->pw_uid, passwd->pw_uid + 1);
+ ATF_REQUIRE(sysctlbyname("security.mac.do.rules",
+ NULL, NULL, rule, strlen(rule)) == 0);
+
+ progname = basename(strdup(getprogname()));
+ (void)snprintf(path, sizeof(path), "%s/%s",
+ atf_tc_get_config_var(tc, "srcdir"), progname);
+ ATF_REQUIRE(sysctlbyname("security.mac.do.exec_paths",
+ NULL, NULL, path, strlen(path)) == 0);
+
+ ATF_REQUIRE(setgroups(0, NULL) == 0);
+ ATF_REQUIRE(setgid(passwd->pw_gid) == 0);
+ ATF_REQUIRE(setuid(passwd->pw_uid) == 0);
+
+ /*
+ * Request the UID transition permitted by the first rule while also
+ * setting all primary GIDs to 0. MAC/do must reject this because GID 0
+ * is not a primary GID of the current credential.
+ */
+ cred.sc_uid = cred.sc_ruid = cred.sc_svuid = passwd->pw_uid + 1;
+ cred.sc_gid = cred.sc_rgid = cred.sc_svgid = 0;
+ flags = SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID |
+ SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID;
+ ATF_REQUIRE_ERRNO(EPERM,
+ setcred(flags, &cred, sizeof(cred)) == -1);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, empty_supplementary_group_list);
+
+ return (atf_no_error());
+}