xref: /freebsd/tests/sys/kern/resolve_beneath_test.c (revision 8b24b19d08f9dc10e6af9a1695b9b0d62fb80cc2)
1*8b24b19dSMark Johnston /*
2*8b24b19dSMark Johnston  * Copyright (c) 2026 The FreeBSD Foundation
3*8b24b19dSMark Johnston  *
4*8b24b19dSMark Johnston  * This software was written by Mark Johnston under sponsorship from
5*8b24b19dSMark Johnston  * the FreeBSD Foundation.
6*8b24b19dSMark Johnston  *
7*8b24b19dSMark Johnston  * SPDX-License-Identifier: BSD-2-Clause
8*8b24b19dSMark Johnston  */
9*8b24b19dSMark Johnston 
10*8b24b19dSMark Johnston #include <sys/stat.h>
11*8b24b19dSMark Johnston #include <sys/mount.h>
12*8b24b19dSMark Johnston 
13*8b24b19dSMark Johnston #include <errno.h>
14*8b24b19dSMark Johnston #include <fcntl.h>
15*8b24b19dSMark Johnston #include <unistd.h>
16*8b24b19dSMark Johnston 
17*8b24b19dSMark Johnston #include <atf-c.h>
18*8b24b19dSMark Johnston 
19*8b24b19dSMark Johnston /*
20*8b24b19dSMark Johnston  * Verify that AT_RESOLVE_BENEATH is respected by various system calls.
21*8b24b19dSMark Johnston  */
22*8b24b19dSMark Johnston 
23*8b24b19dSMark Johnston static int
setup(void)24*8b24b19dSMark Johnston setup(void)
25*8b24b19dSMark Johnston {
26*8b24b19dSMark Johnston 	int fd, tfd;
27*8b24b19dSMark Johnston 
28*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, mkdir("base", 0755));
29*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, mkdir("outside", 0755));
30*8b24b19dSMark Johnston 	ATF_REQUIRE((tfd = open("outside/target", O_CREAT | O_WRONLY, 0644)) >=
31*8b24b19dSMark Johnston 	    0);
32*8b24b19dSMark Johnston 	ATF_REQUIRE(close(tfd) == 0);
33*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, mkdir("outside/dir", 0755));
34*8b24b19dSMark Johnston 	ATF_REQUIRE((tfd = open("base/file", O_CREAT | O_WRONLY, 0644)) >= 0);
35*8b24b19dSMark Johnston 	ATF_REQUIRE(close(tfd) == 0);
36*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, mkdir("base/subdir", 0755));
37*8b24b19dSMark Johnston 	ATF_REQUIRE((fd = open("base", O_DIRECTORY | O_RDONLY)) >= 0);
38*8b24b19dSMark Johnston 	return (fd);
39*8b24b19dSMark Johnston }
40*8b24b19dSMark Johnston 
41*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(faccessat_beneath);
ATF_TC_BODY(faccessat_beneath,tc)42*8b24b19dSMark Johnston ATF_TC_BODY(faccessat_beneath, tc)
43*8b24b19dSMark Johnston {
44*8b24b19dSMark Johnston 	int fd;
45*8b24b19dSMark Johnston 
46*8b24b19dSMark Johnston 	fd = setup();
47*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
48*8b24b19dSMark Johnston 	    faccessat(fd, "file", F_OK, AT_RESOLVE_BENEATH));
49*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
50*8b24b19dSMark Johnston 	    faccessat(fd, "../outside/target", F_OK,
51*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
52*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
53*8b24b19dSMark Johnston }
54*8b24b19dSMark Johnston 
55*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(chflagsat_beneath);
ATF_TC_BODY(chflagsat_beneath,tc)56*8b24b19dSMark Johnston ATF_TC_BODY(chflagsat_beneath, tc)
57*8b24b19dSMark Johnston {
58*8b24b19dSMark Johnston 	int fd, ret;
59*8b24b19dSMark Johnston 
60*8b24b19dSMark Johnston 	fd = setup();
61*8b24b19dSMark Johnston 	ret = chflagsat(fd, "file", UF_NODUMP, AT_RESOLVE_BENEATH);
62*8b24b19dSMark Johnston 	if (ret != 0)
63*8b24b19dSMark Johnston 		ATF_REQUIRE_EQ(EOPNOTSUPP, errno);
64*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
65*8b24b19dSMark Johnston 	    chflagsat(fd, "../outside/target", UF_NODUMP,
66*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
67*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
68*8b24b19dSMark Johnston }
69*8b24b19dSMark Johnston 
70*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(fchmodat_beneath);
ATF_TC_BODY(fchmodat_beneath,tc)71*8b24b19dSMark Johnston ATF_TC_BODY(fchmodat_beneath, tc)
72*8b24b19dSMark Johnston {
73*8b24b19dSMark Johnston 	int fd;
74*8b24b19dSMark Johnston 
75*8b24b19dSMark Johnston 	fd = setup();
76*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
77*8b24b19dSMark Johnston 	    fchmodat(fd, "file", 0644, AT_RESOLVE_BENEATH));
78*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
79*8b24b19dSMark Johnston 	    fchmodat(fd, "../outside/target", 0644,
80*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
81*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
82*8b24b19dSMark Johnston }
83*8b24b19dSMark Johnston 
84*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(fchownat_beneath);
ATF_TC_BODY(fchownat_beneath,tc)85*8b24b19dSMark Johnston ATF_TC_BODY(fchownat_beneath, tc)
86*8b24b19dSMark Johnston {
87*8b24b19dSMark Johnston 	int fd;
88*8b24b19dSMark Johnston 
89*8b24b19dSMark Johnston 	fd = setup();
90*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
91*8b24b19dSMark Johnston 	    fchownat(fd, "file", -1, -1, AT_RESOLVE_BENEATH));
92*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
93*8b24b19dSMark Johnston 	    fchownat(fd, "../outside/target", 0, 0,
94*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
95*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
96*8b24b19dSMark Johnston }
97*8b24b19dSMark Johnston 
98*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(utimensat_beneath);
ATF_TC_BODY(utimensat_beneath,tc)99*8b24b19dSMark Johnston ATF_TC_BODY(utimensat_beneath, tc)
100*8b24b19dSMark Johnston {
101*8b24b19dSMark Johnston 	int fd;
102*8b24b19dSMark Johnston 
103*8b24b19dSMark Johnston 	fd = setup();
104*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
105*8b24b19dSMark Johnston 	    utimensat(fd, "file", NULL, AT_RESOLVE_BENEATH));
106*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
107*8b24b19dSMark Johnston 	    utimensat(fd, "../outside/target", NULL,
108*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
109*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
110*8b24b19dSMark Johnston }
111*8b24b19dSMark Johnston 
112*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(linkat_beneath);
ATF_TC_BODY(linkat_beneath,tc)113*8b24b19dSMark Johnston ATF_TC_BODY(linkat_beneath, tc)
114*8b24b19dSMark Johnston {
115*8b24b19dSMark Johnston 	int fd;
116*8b24b19dSMark Johnston 
117*8b24b19dSMark Johnston 	fd = setup();
118*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
119*8b24b19dSMark Johnston 	    linkat(fd, "file", fd, "hardlink", AT_RESOLVE_BENEATH));
120*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
121*8b24b19dSMark Johnston 	    linkat(fd, "../outside/target", fd, "hardlink2",
122*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
123*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
124*8b24b19dSMark Johnston }
125*8b24b19dSMark Johnston 
126*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(unlinkat_beneath);
ATF_TC_BODY(unlinkat_beneath,tc)127*8b24b19dSMark Johnston ATF_TC_BODY(unlinkat_beneath, tc)
128*8b24b19dSMark Johnston {
129*8b24b19dSMark Johnston 	int fd;
130*8b24b19dSMark Johnston 
131*8b24b19dSMark Johnston 	fd = setup();
132*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
133*8b24b19dSMark Johnston 	    unlinkat(fd, "file", AT_RESOLVE_BENEATH));
134*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
135*8b24b19dSMark Johnston 	    unlinkat(fd, "../outside/target",
136*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
137*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
138*8b24b19dSMark Johnston }
139*8b24b19dSMark Johnston 
140*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(unlinkat_rmdir_beneath);
ATF_TC_BODY(unlinkat_rmdir_beneath,tc)141*8b24b19dSMark Johnston ATF_TC_BODY(unlinkat_rmdir_beneath, tc)
142*8b24b19dSMark Johnston {
143*8b24b19dSMark Johnston 	int fd;
144*8b24b19dSMark Johnston 
145*8b24b19dSMark Johnston 	fd = setup();
146*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
147*8b24b19dSMark Johnston 	    unlinkat(fd, "subdir",
148*8b24b19dSMark Johnston 	    AT_REMOVEDIR | AT_RESOLVE_BENEATH));
149*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
150*8b24b19dSMark Johnston 	    unlinkat(fd, "../outside/dir",
151*8b24b19dSMark Johnston 	    AT_REMOVEDIR | AT_RESOLVE_BENEATH) == -1);
152*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, access("outside/dir", F_OK));
153*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
154*8b24b19dSMark Johnston }
155*8b24b19dSMark Johnston 
156*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(funlinkat_beneath);
ATF_TC_BODY(funlinkat_beneath,tc)157*8b24b19dSMark Johnston ATF_TC_BODY(funlinkat_beneath, tc)
158*8b24b19dSMark Johnston {
159*8b24b19dSMark Johnston 	int fd;
160*8b24b19dSMark Johnston 
161*8b24b19dSMark Johnston 	fd = setup();
162*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
163*8b24b19dSMark Johnston 	    funlinkat(fd, "file", FD_NONE, AT_RESOLVE_BENEATH));
164*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
165*8b24b19dSMark Johnston 	    funlinkat(fd, "../outside/target", FD_NONE,
166*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
167*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
168*8b24b19dSMark Johnston }
169*8b24b19dSMark Johnston 
170*8b24b19dSMark Johnston ATF_TC_WITHOUT_HEAD(funlinkat_rmdir_beneath);
ATF_TC_BODY(funlinkat_rmdir_beneath,tc)171*8b24b19dSMark Johnston ATF_TC_BODY(funlinkat_rmdir_beneath, tc)
172*8b24b19dSMark Johnston {
173*8b24b19dSMark Johnston 	int fd;
174*8b24b19dSMark Johnston 
175*8b24b19dSMark Johnston 	fd = setup();
176*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, mkdir("base/subdir2", 0755));
177*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
178*8b24b19dSMark Johnston 	    funlinkat(fd, "subdir2", FD_NONE,
179*8b24b19dSMark Johnston 	    AT_REMOVEDIR | AT_RESOLVE_BENEATH));
180*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
181*8b24b19dSMark Johnston 	    funlinkat(fd, "../outside/dir", FD_NONE,
182*8b24b19dSMark Johnston 	    AT_REMOVEDIR | AT_RESOLVE_BENEATH) == -1);
183*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0, access("outside/dir", F_OK));
184*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
185*8b24b19dSMark Johnston }
186*8b24b19dSMark Johnston 
187*8b24b19dSMark Johnston ATF_TC(getfhat_beneath);
ATF_TC_HEAD(getfhat_beneath,tc)188*8b24b19dSMark Johnston ATF_TC_HEAD(getfhat_beneath, tc)
189*8b24b19dSMark Johnston {
190*8b24b19dSMark Johnston 	atf_tc_set_md_var(tc, "require.user", "root");
191*8b24b19dSMark Johnston }
ATF_TC_BODY(getfhat_beneath,tc)192*8b24b19dSMark Johnston ATF_TC_BODY(getfhat_beneath, tc)
193*8b24b19dSMark Johnston {
194*8b24b19dSMark Johnston 	fhandle_t fh;
195*8b24b19dSMark Johnston 	int fd;
196*8b24b19dSMark Johnston 
197*8b24b19dSMark Johnston 	fd = setup();
198*8b24b19dSMark Johnston 	ATF_REQUIRE_EQ(0,
199*8b24b19dSMark Johnston 	    getfhat(fd, "file", &fh, AT_RESOLVE_BENEATH));
200*8b24b19dSMark Johnston 	ATF_REQUIRE_ERRNO(ENOTCAPABLE,
201*8b24b19dSMark Johnston 	    getfhat(fd, "../outside/target", &fh,
202*8b24b19dSMark Johnston 	    AT_RESOLVE_BENEATH) == -1);
203*8b24b19dSMark Johnston 	ATF_REQUIRE(close(fd) == 0);
204*8b24b19dSMark Johnston }
205*8b24b19dSMark Johnston 
ATF_TP_ADD_TCS(tp)206*8b24b19dSMark Johnston ATF_TP_ADD_TCS(tp)
207*8b24b19dSMark Johnston {
208*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, faccessat_beneath);
209*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, chflagsat_beneath);
210*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, fchmodat_beneath);
211*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, fchownat_beneath);
212*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, utimensat_beneath);
213*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, linkat_beneath);
214*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, unlinkat_beneath);
215*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, unlinkat_rmdir_beneath);
216*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, funlinkat_beneath);
217*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, funlinkat_rmdir_beneath);
218*8b24b19dSMark Johnston 	ATF_TP_ADD_TC(tp, getfhat_beneath);
219*8b24b19dSMark Johnston 	return (atf_no_error());
220*8b24b19dSMark Johnston }
221