1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026 Alan Somers
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 /* Miscellaneous tests that don't relate to any particular fuse operation */
29
30 extern "C" {
31 #include <sys/wait.h>
32
33 #include <fcntl.h>
34 #include <unistd.h>
35 }
36
37 #include "mockfs.hh"
38 #include "utils.hh"
39
40 using namespace testing;
41
42
43 class Execv: public FuseTest {
44 public:
SetUp()45 virtual void SetUp() {
46 /* Enable FUSE_ASYNC_READ to allow shared vnode locks */
47 m_init_flags = FUSE_ASYNC_READ;
48 FuseTest::SetUp();
49 }
50 };
51 class Fexecv: public Execv {};
52
53 class FexecvDefaultPermissions: public Fexecv {
SetUp()54 virtual void SetUp() {
55 m_default_permissions = true;
56 Fexecv::SetUp();
57 }
58 };
59
60 /*
61 * Execute a file mounted on a fusefs file system. The server should get the
62 * FUSE_RELEASE request when sys_fexecve closes the file.
63 *
64 * Crucially, execve ignores the file system's MNTK_EXTENDED_SHARED flag.
65 *
66 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295957
67 */
TEST_F(Execv,close)68 TEST_F(Execv, close)
69 {
70 const static char FULLPATH[] = "mountpoint/true";
71 const static char RELPATH[] = "true";
72 const static size_t BUFSIZE = 16384;
73 FILE *true_file;
74 uint64_t ino = 42;
75 size_t true_len;
76 int status;
77 char *buf;
78
79 buf = new char[BUFSIZE];
80 true_file = fopen("/usr/bin/true", "r");
81 ASSERT_TRUE(true_file) << strerror(errno);
82 true_len = fread(buf, 1, BUFSIZE, true_file);
83 ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
84 fclose(true_file);
85
86 fork(false, &status, [&] {
87 expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
88 UINT64_MAX);
89 expect_open(ino, 0, 1);
90 expect_read(ino, 0, true_len, true_len, buf);
91 expect_flush(ino, 1, ReturnErrno(ENOSYS));
92 EXPECT_CALL(*m_mock, process(
93 ResultOf([=](auto in) {
94 return (in.header.opcode == FUSE_RELEASE &&
95 in.header.nodeid == ino);
96 }, Eq(true)),
97 _)
98 ).Times(1)
99 .WillRepeatedly(Invoke(ReturnErrno(0)));
100
101 }, [&] {
102 char *const argv[] = {__DECONST(char *, "true"), NULL};
103 char *const env[] = {NULL};
104
105 execve(FULLPATH, argv, env);
106 fprintf(stderr, "execv: %s\n", strerror(errno));
107 return 1;
108 });
109 ASSERT_EQ(0, WEXITSTATUS(status));
110
111 delete[] buf;
112 }
113
TEST_F(Fexecv,close)114 TEST_F(Fexecv, close)
115 {
116 const static char FULLPATH[] = "mountpoint/true";
117 const static char RELPATH[] = "true";
118 const static size_t BUFSIZE = 16384;
119 FILE *true_file;
120 uint64_t ino = 42;
121 size_t true_len;
122 int status;
123 char *buf;
124
125 buf = new char[BUFSIZE];
126 true_file = fopen("/usr/bin/true", "r");
127 ASSERT_TRUE(true_file) << strerror(errno);
128 true_len = fread(buf, 1, BUFSIZE, true_file);
129 ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
130 fclose(true_file);
131
132 fork(false, &status, [&] {
133 expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
134 UINT64_MAX);
135 expect_open(ino, 0, 2);
136 expect_read(ino, 0, true_len, true_len, buf);
137 expect_flush(ino, 1, ReturnErrno(ENOSYS));
138 EXPECT_CALL(*m_mock, process(
139 ResultOf([=](auto in) {
140 return (in.header.opcode == FUSE_RELEASE &&
141 in.header.nodeid == ino);
142 }, Eq(true)),
143 _)
144 ).Times(2)
145 .WillRepeatedly(Invoke(ReturnErrno(0)));
146
147 }, [&] {
148 char *const argv[] = {__DECONST(char *, "true"), NULL};
149 char *const env[] = {NULL};
150 int fd;
151
152 fd = open(FULLPATH, O_EXEC);
153 if (fd < 0) {
154 fprintf(stderr, "open: %s\n", strerror(errno));
155 return 1;
156 }
157 fexecve(fd, argv, env);
158 fprintf(stderr, "execv: %s\n", strerror(errno));
159 return 1;
160 });
161 ASSERT_EQ(0, WEXITSTATUS(status));
162
163 delete[] buf;
164 }
165
166 /*
167 * Execute a file stored on a fusefs file system that does not implement
168 * FUSE_OPEN
169 */
TEST_F(Fexecv,close_noopen)170 TEST_F(Fexecv, close_noopen)
171 {
172 const static char FULLPATH[] = "mountpoint/true";
173 const static char RELPATH[] = "true";
174 const static size_t BUFSIZE = 16384;
175 FILE *true_file;
176 uint64_t ino = 42;
177 size_t true_len;
178 int status;
179 char *buf;
180
181 buf = new char[BUFSIZE];
182 true_file = fopen("/usr/bin/true", "r");
183 ASSERT_TRUE(true_file) << strerror(errno);
184 true_len = fread(buf, 1, BUFSIZE, true_file);
185 ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
186 fclose(true_file);
187
188 fork(false, &status, [&] {
189 expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
190 UINT64_MAX);
191 EXPECT_CALL(*m_mock, process(
192 ResultOf([=](auto in) {
193 return (in.header.opcode == FUSE_OPEN &&
194 in.header.nodeid == ino);
195 }, Eq(true)),
196 _)
197 ).Times(1)
198 .WillOnce(Invoke(ReturnErrno(ENOSYS)));
199 expect_read(ino, 0, true_len, true_len, buf, -1, 0);
200 expect_flush(ino, 1, ReturnErrno(ENOSYS));
201 }, [&] {
202 char *const argv[] = {__DECONST(char *, "true"), NULL};
203 char *const env[] = {NULL};
204 int fd;
205
206 fd = open(FULLPATH, O_EXEC);
207 if (fd < 0) {
208 fprintf(stderr, "open: %s\n", strerror(errno));
209 return 1;
210 }
211 fexecve(fd, argv, env);
212 fprintf(stderr, "execv: %s\n", strerror(errno));
213 return 1;
214 });
215 ASSERT_EQ(0, WEXITSTATUS(status));
216
217 delete[] buf;
218 }
219
220 /*
221 * When execute a file with a dirty atime, fusefs must send FUSE_SETATTR to the
222 * daemon during close.
223 */
TEST_F(FexecvDefaultPermissions,atime)224 TEST_F(FexecvDefaultPermissions, atime)
225 {
226 const static char FULLPATH[] = "mountpoint/true";
227 const static char RELPATH[] = "true";
228 const static size_t BUFSIZE = 16384;
229 FILE *true_file;
230 uint64_t ino = 42;
231 size_t true_len;
232 int status;
233 char *buf;
234
235 buf = new char[BUFSIZE];
236 true_file = fopen("/usr/bin/true", "r");
237 ASSERT_TRUE(true_file) << strerror(errno);
238 true_len = fread(buf, 1, BUFSIZE, true_file);
239 ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
240 fclose(true_file);
241
242 fork(false, &status, [&] {
243 expect_lookup(RELPATH, ino, S_IFREG | 0777, true_len, 1,
244 UINT64_MAX);
245 EXPECT_CALL(*m_mock, process(
246 ResultOf([=](auto in) {
247 return (in.header.opcode == FUSE_GETATTR &&
248 in.header.nodeid == FUSE_ROOT_ID);
249 }, Eq(true)),
250 _)
251 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
252 SET_OUT_HEADER_LEN(out, attr);
253 out.body.attr.attr.ino = FUSE_ROOT_ID;
254 out.body.attr.attr.mode = S_IFDIR | 0777;
255 out.body.attr.attr.size = 0;
256 out.body.attr.attr_valid = UINT64_MAX;
257 })));
258 EXPECT_CALL(*m_mock, process(
259 ResultOf([=](auto in) {
260 return (in.header.opcode == FUSE_OPEN &&
261 in.header.nodeid == ino);
262 }, Eq(true)),
263 _)
264 ).Times(1)
265 .WillOnce(Invoke(ReturnErrno(ENOSYS)));
266 expect_read(ino, 0, true_len, true_len, buf, -1, 0);
267 expect_flush(ino, 1, ReturnErrno(ENOSYS));
268 EXPECT_CALL(*m_mock, process(
269 ResultOf([&](auto in) {
270 return (in.header.opcode == FUSE_SETATTR &&
271 in.header.nodeid == ino &&
272 in.body.setattr.valid == FATTR_ATIME);
273 }, Eq(true)),
274 _)
275 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
276 SET_OUT_HEADER_LEN(out, attr);
277 out.body.attr.attr.ino = ino;
278 out.body.attr.attr.mode = S_IFREG | 0777;
279 })));
280 }, [&] {
281 char *const argv[] = {__DECONST(char *, "true"), NULL};
282 char *const env[] = {NULL};
283 char cbuf[8];
284 int fd;
285
286 /* Note that fexecve doesn't actually require O_EXEC */
287 fd = open(FULLPATH, O_RDONLY);
288 if (fd < 0) {
289 fprintf(stderr, "open: %s\n", strerror(errno));
290 return 1;
291 }
292 /* Read a few bytes, just to dirty the file's atime */
293 if (read(fd, cbuf, sizeof(cbuf)) < 0) {
294 fprintf(stderr, "read: %s\n", strerror(errno));
295 return 1;
296 }
297 fexecve(fd, argv, env);
298 fprintf(stderr, "execv: %s\n", strerror(errno));
299 return 1;
300 });
301 ASSERT_EQ(0, WEXITSTATUS(status));
302
303 delete[] buf;
304 }
305