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