1*068957e0SAlexander Leidinger /*-
2*068957e0SAlexander Leidinger * SPDX-License-Identifier: BSD-2-Clause
3*068957e0SAlexander Leidinger *
4*068957e0SAlexander Leidinger * Copyright (c) 2026 Alexander Leidinger <netchild@FreeBSD.org>
5*068957e0SAlexander Leidinger *
6*068957e0SAlexander Leidinger * Redistribution and use in source and binary forms, with or without
7*068957e0SAlexander Leidinger * modification, are permitted provided that the following conditions
8*068957e0SAlexander Leidinger * are met:
9*068957e0SAlexander Leidinger * 1. Redistributions of source code must retain the above copyright
10*068957e0SAlexander Leidinger * notice, this list of conditions and the following disclaimer.
11*068957e0SAlexander Leidinger * 2. Redistributions in binary form must reproduce the above copyright
12*068957e0SAlexander Leidinger * notice, this list of conditions and the following disclaimer in the
13*068957e0SAlexander Leidinger * documentation and/or other advertising materials provided with the
14*068957e0SAlexander Leidinger * distribution.
15*068957e0SAlexander Leidinger *
16*068957e0SAlexander Leidinger * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17*068957e0SAlexander Leidinger * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18*068957e0SAlexander Leidinger * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19*068957e0SAlexander Leidinger * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20*068957e0SAlexander Leidinger * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21*068957e0SAlexander Leidinger * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22*068957e0SAlexander Leidinger * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23*068957e0SAlexander Leidinger * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24*068957e0SAlexander Leidinger * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25*068957e0SAlexander Leidinger * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26*068957e0SAlexander Leidinger * SUCH DAMAGE.
27*068957e0SAlexander Leidinger */
28*068957e0SAlexander Leidinger
29*068957e0SAlexander Leidinger /**
30*068957e0SAlexander Leidinger * @file
31*068957e0SAlexander Leidinger * Regression tests for a process-mode PMC whose target exec()s a program
32*068957e0SAlexander Leidinger * that changes its credentials: what FreeBSD-SA-26:56.hwpmc fixed.
33*068957e0SAlexander Leidinger *
34*068957e0SAlexander Leidinger * pmc_process_exec() must drop such a PMC unless its owner may still trace
35*068957e0SAlexander Leidinger * the new credentials, and the detach must unlink the process descriptor
36*068957e0SAlexander Leidinger * exactly once.
37*068957e0SAlexander Leidinger *
38*068957e0SAlexander Leidinger * The owner must be unprivileged: root may trace anything, so as root
39*068957e0SAlexander Leidinger * neither case reaches the branch under test - hence require.user. The
40*068957e0SAlexander Leidinger * test must not drop privileges itself either: setuid(2) sets P_SUGID,
41*068957e0SAlexander Leidinger * fork(2) passes it to the child, and p_candebug() then refuses the target
42*068957e0SAlexander Leidinger * to its unprivileged owner, so pmc_attach() would fail with EPERM first.
43*068957e0SAlexander Leidinger *
44*068957e0SAlexander Leidinger * The privileged exec target is picked at run time from base binaries, and
45*068957e0SAlexander Leidinger * a case skips if none of them carries a set-id bit any more.
46*068957e0SAlexander Leidinger */
47*068957e0SAlexander Leidinger
48*068957e0SAlexander Leidinger #include <sys/types.h>
49*068957e0SAlexander Leidinger #include <sys/stat.h>
50*068957e0SAlexander Leidinger #include <sys/wait.h>
51*068957e0SAlexander Leidinger
52*068957e0SAlexander Leidinger #include <errno.h>
53*068957e0SAlexander Leidinger #include <pmc.h>
54*068957e0SAlexander Leidinger #include <signal.h>
55*068957e0SAlexander Leidinger #include <stdlib.h>
56*068957e0SAlexander Leidinger #include <string.h>
57*068957e0SAlexander Leidinger #include <unistd.h>
58*068957e0SAlexander Leidinger
59*068957e0SAlexander Leidinger #include <atf-c.h>
60*068957e0SAlexander Leidinger
61*068957e0SAlexander Leidinger static const char *counting_events[] = {
62*068957e0SAlexander Leidinger "instructions",
63*068957e0SAlexander Leidinger "cycles",
64*068957e0SAlexander Leidinger "branches",
65*068957e0SAlexander Leidinger "unhalted-core-cycles",
66*068957e0SAlexander Leidinger "inst_retired.any",
67*068957e0SAlexander Leidinger "cpu_clk_unhalted.thread",
68*068957e0SAlexander Leidinger "ls_not_halted_cyc",
69*068957e0SAlexander Leidinger NULL
70*068957e0SAlexander Leidinger };
71*068957e0SAlexander Leidinger
72*068957e0SAlexander Leidinger /**
73*068957e0SAlexander Leidinger * @internal
74*068957e0SAlexander Leidinger * Exec targets. 'setid' is the bit the entry needs; the argument vectors
75*068957e0SAlexander Leidinger * are chosen so the program exits at once and touches nothing. wall(1)
76*068957e0SAlexander Leidinger * reads its message from stdin, which the caller holds open on a pipe, so
77*068957e0SAlexander Leidinger * that target stays alive long enough to be inspected.
78*068957e0SAlexander Leidinger */
79*068957e0SAlexander Leidinger struct exec_target {
80*068957e0SAlexander Leidinger const char *path;
81*068957e0SAlexander Leidinger const char *const argv[5];
82*068957e0SAlexander Leidinger mode_t setid;
83*068957e0SAlexander Leidinger int blocks_on_stdin;
84*068957e0SAlexander Leidinger };
85*068957e0SAlexander Leidinger
86*068957e0SAlexander Leidinger static const struct exec_target setgid_targets[] = {
87*068957e0SAlexander Leidinger { "/usr/bin/wall", { "wall", NULL }, S_ISGID, 1 },
88*068957e0SAlexander Leidinger { NULL, { NULL }, 0, 0 }
89*068957e0SAlexander Leidinger };
90*068957e0SAlexander Leidinger
91*068957e0SAlexander Leidinger static const struct exec_target setuid_targets[] = {
92*068957e0SAlexander Leidinger { "/sbin/ping", { "ping", "-c", "1", "127.0.0.1", NULL }, S_ISUID, 0 },
93*068957e0SAlexander Leidinger { NULL, { NULL }, 0, 0 }
94*068957e0SAlexander Leidinger };
95*068957e0SAlexander Leidinger
96*068957e0SAlexander Leidinger static const struct exec_target *
pick_target(const struct exec_target * tab)97*068957e0SAlexander Leidinger pick_target(const struct exec_target *tab)
98*068957e0SAlexander Leidinger {
99*068957e0SAlexander Leidinger struct stat sb;
100*068957e0SAlexander Leidinger int i;
101*068957e0SAlexander Leidinger
102*068957e0SAlexander Leidinger for (i = 0; tab[i].path != NULL; i++) {
103*068957e0SAlexander Leidinger if (stat(tab[i].path, &sb) != 0)
104*068957e0SAlexander Leidinger continue;
105*068957e0SAlexander Leidinger if ((sb.st_mode & tab[i].setid) != 0)
106*068957e0SAlexander Leidinger return (&tab[i]);
107*068957e0SAlexander Leidinger }
108*068957e0SAlexander Leidinger return (NULL);
109*068957e0SAlexander Leidinger }
110*068957e0SAlexander Leidinger
111*068957e0SAlexander Leidinger static pmc_id_t
allocate_counting_pmc(void)112*068957e0SAlexander Leidinger allocate_counting_pmc(void)
113*068957e0SAlexander Leidinger {
114*068957e0SAlexander Leidinger pmc_id_t id = PMC_ID_INVALID;
115*068957e0SAlexander Leidinger int i;
116*068957e0SAlexander Leidinger
117*068957e0SAlexander Leidinger for (i = 0; counting_events[i] != NULL; i++) {
118*068957e0SAlexander Leidinger if (pmc_allocate(counting_events[i], PMC_MODE_TC, 0,
119*068957e0SAlexander Leidinger PMC_CPU_ANY, &id, 0) == 0)
120*068957e0SAlexander Leidinger return (id);
121*068957e0SAlexander Leidinger }
122*068957e0SAlexander Leidinger return (PMC_ID_INVALID);
123*068957e0SAlexander Leidinger }
124*068957e0SAlexander Leidinger
125*068957e0SAlexander Leidinger static void
spin(void)126*068957e0SAlexander Leidinger spin(void)
127*068957e0SAlexander Leidinger {
128*068957e0SAlexander Leidinger volatile unsigned long s = 0;
129*068957e0SAlexander Leidinger int i;
130*068957e0SAlexander Leidinger
131*068957e0SAlexander Leidinger for (i = 0; i < 2000000; i++)
132*068957e0SAlexander Leidinger s += i;
133*068957e0SAlexander Leidinger }
134*068957e0SAlexander Leidinger
135*068957e0SAlexander Leidinger static void
require_unprivileged_owner(void)136*068957e0SAlexander Leidinger require_unprivileged_owner(void)
137*068957e0SAlexander Leidinger {
138*068957e0SAlexander Leidinger
139*068957e0SAlexander Leidinger if (geteuid() == 0)
140*068957e0SAlexander Leidinger atf_tc_skip("the PMC owner must be unprivileged: root may "
141*068957e0SAlexander Leidinger "trace any credentials, so the check under test is never "
142*068957e0SAlexander Leidinger "reached");
143*068957e0SAlexander Leidinger }
144*068957e0SAlexander Leidinger
145*068957e0SAlexander Leidinger /**
146*068957e0SAlexander Leidinger * @internal
147*068957e0SAlexander Leidinger * Fork a target, attach a running counting PMC to it, and let it exec the
148*068957e0SAlexander Leidinger * privileged program. Returns with the PMC released; *still_attached is
149*068957e0SAlexander Leidinger * only meaningful for a target that blocks after the exec.
150*068957e0SAlexander Leidinger */
151*068957e0SAlexander Leidinger static void
run_target(const struct exec_target * t,int * still_attached)152*068957e0SAlexander Leidinger run_target(const struct exec_target *t, int *still_attached)
153*068957e0SAlexander Leidinger {
154*068957e0SAlexander Leidinger pmc_id_t id;
155*068957e0SAlexander Leidinger pid_t target;
156*068957e0SAlexander Leidinger char token;
157*068957e0SAlexander Leidinger int gopipe[2], inpipe[2], status;
158*068957e0SAlexander Leidinger
159*068957e0SAlexander Leidinger ATF_REQUIRE(pipe(gopipe) == 0);
160*068957e0SAlexander Leidinger ATF_REQUIRE(pipe(inpipe) == 0);
161*068957e0SAlexander Leidinger ATF_REQUIRE((target = fork()) >= 0);
162*068957e0SAlexander Leidinger
163*068957e0SAlexander Leidinger if (target == 0) {
164*068957e0SAlexander Leidinger (void)close(gopipe[1]);
165*068957e0SAlexander Leidinger if (t->blocks_on_stdin)
166*068957e0SAlexander Leidinger (void)dup2(inpipe[0], STDIN_FILENO);
167*068957e0SAlexander Leidinger (void)close(inpipe[1]);
168*068957e0SAlexander Leidinger if (read(gopipe[0], &token, 1) != 1)
169*068957e0SAlexander Leidinger _exit(1);
170*068957e0SAlexander Leidinger spin();
171*068957e0SAlexander Leidinger (void)execv(t->path, __DECONST(char **, t->argv));
172*068957e0SAlexander Leidinger _exit(1);
173*068957e0SAlexander Leidinger }
174*068957e0SAlexander Leidinger (void)close(gopipe[0]);
175*068957e0SAlexander Leidinger (void)close(inpipe[0]);
176*068957e0SAlexander Leidinger
177*068957e0SAlexander Leidinger if (pmc_init() != 0) {
178*068957e0SAlexander Leidinger (void)kill(target, SIGKILL);
179*068957e0SAlexander Leidinger (void)waitpid(target, &status, 0);
180*068957e0SAlexander Leidinger atf_tc_skip("hwpmc(4) is not available");
181*068957e0SAlexander Leidinger }
182*068957e0SAlexander Leidinger if ((id = allocate_counting_pmc()) == PMC_ID_INVALID) {
183*068957e0SAlexander Leidinger (void)kill(target, SIGKILL);
184*068957e0SAlexander Leidinger (void)waitpid(target, &status, 0);
185*068957e0SAlexander Leidinger atf_tc_skip("no process-mode counting event is allocatable");
186*068957e0SAlexander Leidinger }
187*068957e0SAlexander Leidinger
188*068957e0SAlexander Leidinger ATF_REQUIRE_MSG(pmc_attach(id, target) == 0, "pmc_attach: %s",
189*068957e0SAlexander Leidinger strerror(errno));
190*068957e0SAlexander Leidinger ATF_REQUIRE(pmc_start(id) == 0);
191*068957e0SAlexander Leidinger
192*068957e0SAlexander Leidinger /* Into execve(2). */
193*068957e0SAlexander Leidinger ATF_REQUIRE(write(gopipe[1], "g", 1) == 1);
194*068957e0SAlexander Leidinger (void)close(gopipe[1]);
195*068957e0SAlexander Leidinger
196*068957e0SAlexander Leidinger if (t->blocks_on_stdin) {
197*068957e0SAlexander Leidinger (void)usleep(400000);
198*068957e0SAlexander Leidinger errno = 0;
199*068957e0SAlexander Leidinger *still_attached = pmc_detach(id, target) == 0;
200*068957e0SAlexander Leidinger if (!*still_attached)
201*068957e0SAlexander Leidinger ATF_REQUIRE_MSG(errno == ESRCH, "pmc_detach: %s",
202*068957e0SAlexander Leidinger strerror(errno));
203*068957e0SAlexander Leidinger (void)kill(target, SIGKILL);
204*068957e0SAlexander Leidinger } else {
205*068957e0SAlexander Leidinger *still_attached = 0;
206*068957e0SAlexander Leidinger }
207*068957e0SAlexander Leidinger (void)waitpid(target, &status, 0);
208*068957e0SAlexander Leidinger (void)pmc_release(id);
209*068957e0SAlexander Leidinger }
210*068957e0SAlexander Leidinger
211*068957e0SAlexander Leidinger ATF_TC(exec_setgid_drops_pmc);
ATF_TC_HEAD(exec_setgid_drops_pmc,tc)212*068957e0SAlexander Leidinger ATF_TC_HEAD(exec_setgid_drops_pmc, tc)
213*068957e0SAlexander Leidinger {
214*068957e0SAlexander Leidinger
215*068957e0SAlexander Leidinger atf_tc_set_md_var(tc, "descr",
216*068957e0SAlexander Leidinger "a process-mode PMC is detached when its target execs into "
217*068957e0SAlexander Leidinger "credentials its owner may not trace");
218*068957e0SAlexander Leidinger atf_tc_set_md_var(tc, "require.user", "unprivileged");
219*068957e0SAlexander Leidinger }
ATF_TC_BODY(exec_setgid_drops_pmc,tc)220*068957e0SAlexander Leidinger ATF_TC_BODY(exec_setgid_drops_pmc, tc)
221*068957e0SAlexander Leidinger {
222*068957e0SAlexander Leidinger const struct exec_target *t;
223*068957e0SAlexander Leidinger int still_attached;
224*068957e0SAlexander Leidinger
225*068957e0SAlexander Leidinger require_unprivileged_owner();
226*068957e0SAlexander Leidinger if ((t = pick_target(setgid_targets)) == NULL)
227*068957e0SAlexander Leidinger atf_tc_skip("no set-gid exec target available");
228*068957e0SAlexander Leidinger
229*068957e0SAlexander Leidinger run_target(t, &still_attached);
230*068957e0SAlexander Leidinger ATF_REQUIRE_MSG(!still_attached,
231*068957e0SAlexander Leidinger "the PMC survived an exec into credentials its owner may not "
232*068957e0SAlexander Leidinger "trace");
233*068957e0SAlexander Leidinger }
234*068957e0SAlexander Leidinger
235*068957e0SAlexander Leidinger ATF_TC(exec_setuid_no_double_unlink);
ATF_TC_HEAD(exec_setuid_no_double_unlink,tc)236*068957e0SAlexander Leidinger ATF_TC_HEAD(exec_setuid_no_double_unlink, tc)
237*068957e0SAlexander Leidinger {
238*068957e0SAlexander Leidinger
239*068957e0SAlexander Leidinger atf_tc_set_md_var(tc, "descr",
240*068957e0SAlexander Leidinger "detaching a process-mode PMC at a credential-changing exec "
241*068957e0SAlexander Leidinger "unlinks the process descriptor exactly once");
242*068957e0SAlexander Leidinger atf_tc_set_md_var(tc, "require.user", "unprivileged");
243*068957e0SAlexander Leidinger }
ATF_TC_BODY(exec_setuid_no_double_unlink,tc)244*068957e0SAlexander Leidinger ATF_TC_BODY(exec_setuid_no_double_unlink, tc)
245*068957e0SAlexander Leidinger {
246*068957e0SAlexander Leidinger const struct exec_target *t;
247*068957e0SAlexander Leidinger int still_attached;
248*068957e0SAlexander Leidinger
249*068957e0SAlexander Leidinger require_unprivileged_owner();
250*068957e0SAlexander Leidinger if ((t = pick_target(setuid_targets)) == NULL)
251*068957e0SAlexander Leidinger atf_tc_skip("no set-uid exec target available");
252*068957e0SAlexander Leidinger
253*068957e0SAlexander Leidinger /* Completing at all is the assertion. */
254*068957e0SAlexander Leidinger run_target(t, &still_attached);
255*068957e0SAlexander Leidinger }
256*068957e0SAlexander Leidinger
ATF_TP_ADD_TCS(tp)257*068957e0SAlexander Leidinger ATF_TP_ADD_TCS(tp)
258*068957e0SAlexander Leidinger {
259*068957e0SAlexander Leidinger
260*068957e0SAlexander Leidinger ATF_TP_ADD_TC(tp, exec_setgid_drops_pmc);
261*068957e0SAlexander Leidinger ATF_TP_ADD_TC(tp, exec_setuid_no_double_unlink);
262*068957e0SAlexander Leidinger
263*068957e0SAlexander Leidinger return (atf_no_error());
264*068957e0SAlexander Leidinger }
265