1*96bf3490SMark Johnston /*
2*96bf3490SMark Johnston * Copyright (c) 2026 The FreeBSD Foundation
3*96bf3490SMark Johnston *
4*96bf3490SMark Johnston * This software was developed by Mark Johnston under sponsorship from
5*96bf3490SMark Johnston * the FreeBSD Foundation.
6*96bf3490SMark Johnston *
7*96bf3490SMark Johnston * SPDX-License-Identifier: BSD-2-Clause
8*96bf3490SMark Johnston */
9*96bf3490SMark Johnston
10*96bf3490SMark Johnston #include <sys/sysctl.h>
11*96bf3490SMark Johnston #include <sys/ucred.h>
12*96bf3490SMark Johnston
13*96bf3490SMark Johnston #include <errno.h>
14*96bf3490SMark Johnston #include <libgen.h>
15*96bf3490SMark Johnston #include <limits.h>
16*96bf3490SMark Johnston #include <pwd.h>
17*96bf3490SMark Johnston #include <stdio.h>
18*96bf3490SMark Johnston #include <stdlib.h>
19*96bf3490SMark Johnston #include <string.h>
20*96bf3490SMark Johnston
21*96bf3490SMark Johnston #include <atf-c.h>
22*96bf3490SMark Johnston
23*96bf3490SMark Johnston /*
24*96bf3490SMark Johnston * Regression test for a bug which erroneously allowed the setcred() call below.
25*96bf3490SMark Johnston */
26*96bf3490SMark Johnston ATF_TC(empty_supplementary_group_list);
ATF_TC_HEAD(empty_supplementary_group_list,tc)27*96bf3490SMark Johnston ATF_TC_HEAD(empty_supplementary_group_list, tc)
28*96bf3490SMark Johnston {
29*96bf3490SMark Johnston atf_tc_set_md_var(tc, "require.user", "root");
30*96bf3490SMark Johnston }
ATF_TC_BODY(empty_supplementary_group_list,tc)31*96bf3490SMark Johnston ATF_TC_BODY(empty_supplementary_group_list, tc)
32*96bf3490SMark Johnston {
33*96bf3490SMark Johnston struct setcred cred = SETCRED_INITIALIZER;
34*96bf3490SMark Johnston struct passwd *passwd;
35*96bf3490SMark Johnston const char *user;
36*96bf3490SMark Johnston char path[PATH_MAX], *progname, *rule;
37*96bf3490SMark Johnston int flags;
38*96bf3490SMark Johnston
39*96bf3490SMark Johnston if (!atf_tc_has_config_var(tc, "unprivileged_user"))
40*96bf3490SMark Johnston atf_tc_skip("unprivileged_user not set");
41*96bf3490SMark Johnston
42*96bf3490SMark Johnston user = atf_tc_get_config_var(tc, "unprivileged_user");
43*96bf3490SMark Johnston passwd = getpwnam(user);
44*96bf3490SMark Johnston ATF_REQUIRE(passwd != NULL);
45*96bf3490SMark Johnston ATF_REQUIRE_MSG(passwd->pw_uid != 0,
46*96bf3490SMark Johnston "unprivileged user must not be root");
47*96bf3490SMark Johnston ATF_REQUIRE_MSG(passwd->pw_gid != 0,
48*96bf3490SMark Johnston "unprivileged user group must not be wheel");
49*96bf3490SMark Johnston
50*96bf3490SMark Johnston (void)asprintf(&rule, "uid=%d>uid=%d;gid=0>uid=0",
51*96bf3490SMark Johnston passwd->pw_uid, passwd->pw_uid + 1);
52*96bf3490SMark Johnston ATF_REQUIRE(sysctlbyname("security.mac.do.rules",
53*96bf3490SMark Johnston NULL, NULL, rule, strlen(rule)) == 0);
54*96bf3490SMark Johnston
55*96bf3490SMark Johnston progname = basename(strdup(getprogname()));
56*96bf3490SMark Johnston (void)snprintf(path, sizeof(path), "%s/%s",
57*96bf3490SMark Johnston atf_tc_get_config_var(tc, "srcdir"), progname);
58*96bf3490SMark Johnston ATF_REQUIRE(sysctlbyname("security.mac.do.exec_paths",
59*96bf3490SMark Johnston NULL, NULL, path, strlen(path)) == 0);
60*96bf3490SMark Johnston
61*96bf3490SMark Johnston ATF_REQUIRE(setgroups(0, NULL) == 0);
62*96bf3490SMark Johnston ATF_REQUIRE(setgid(passwd->pw_gid) == 0);
63*96bf3490SMark Johnston ATF_REQUIRE(setuid(passwd->pw_uid) == 0);
64*96bf3490SMark Johnston
65*96bf3490SMark Johnston /*
66*96bf3490SMark Johnston * Request the UID transition permitted by the first rule while also
67*96bf3490SMark Johnston * setting all primary GIDs to 0. MAC/do must reject this because GID 0
68*96bf3490SMark Johnston * is not a primary GID of the current credential.
69*96bf3490SMark Johnston */
70*96bf3490SMark Johnston cred.sc_uid = cred.sc_ruid = cred.sc_svuid = passwd->pw_uid + 1;
71*96bf3490SMark Johnston cred.sc_gid = cred.sc_rgid = cred.sc_svgid = 0;
72*96bf3490SMark Johnston flags = SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID |
73*96bf3490SMark Johnston SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID;
74*96bf3490SMark Johnston ATF_REQUIRE_ERRNO(EPERM,
75*96bf3490SMark Johnston setcred(flags, &cred, sizeof(cred)) == -1);
76*96bf3490SMark Johnston }
77*96bf3490SMark Johnston
ATF_TP_ADD_TCS(tp)78*96bf3490SMark Johnston ATF_TP_ADD_TCS(tp)
79*96bf3490SMark Johnston {
80*96bf3490SMark Johnston ATF_TP_ADD_TC(tp, empty_supplementary_group_list);
81*96bf3490SMark Johnston
82*96bf3490SMark Johnston return (atf_no_error());
83*96bf3490SMark Johnston }
84