]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/utils.cc
fusefs: fix some permission checks with -o default_permissions
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / utils.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30
31 extern "C" {
32 #include <sys/param.h>
33 #include <sys/mman.h>
34 #include <sys/module.h>
35 #include <sys/sysctl.h>
36 #include <sys/wait.h>
37
38 #include <pwd.h>
39 #include <semaphore.h>
40 #include <unistd.h>
41 }
42
43 #include <gtest/gtest.h>
44
45 #include "mockfs.hh"
46 #include "utils.hh"
47
48 using namespace testing;
49
50 /* Check that fusefs(4) is accessible and the current user can mount(2) */
51 void check_environment()
52 {
53         const char *devnode = "/dev/fuse";
54         const char *usermount_node = "vfs.usermount";
55         int usermount_val = 0;
56         size_t usermount_size = sizeof(usermount_val);
57         if (eaccess(devnode, R_OK | W_OK)) {
58                 if (errno == ENOENT) {
59                         GTEST_SKIP() << devnode << " does not exist";
60                 } else if (errno == EACCES) {
61                         GTEST_SKIP() << devnode <<
62                             " is not accessible by the current user";
63                 } else {
64                         GTEST_SKIP() << strerror(errno);
65                 }
66         }
67         sysctlbyname(usermount_node, &usermount_val, &usermount_size,
68                      NULL, 0);
69         if (geteuid() != 0 && !usermount_val)
70                 GTEST_SKIP() << "current user is not allowed to mount";
71 }
72
73 class FuseEnv: public Environment {
74         virtual void SetUp() {
75         }
76 };
77
78 void FuseTest::SetUp() {
79         const char *node = "vfs.maxbcachebuf";
80         int val = 0;
81         size_t size = sizeof(val);
82
83         /*
84          * XXX check_environment should be called from FuseEnv::SetUp, but
85          * can't due to https://github.com/google/googletest/issues/2189
86          */
87         check_environment();
88         if (IsSkipped())
89                 return;
90
91         ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
92                 << strerror(errno);
93         m_maxbcachebuf = val;
94
95         try {
96                 m_mock = new MockFS(m_maxreadahead, m_allow_other,
97                         m_default_permissions, m_push_symlinks_in, m_ro,
98                         m_init_flags);
99                 /* 
100                  * FUSE_ACCESS is called almost universally.  Expecting it in
101                  * each test case would be super-annoying.  Instead, set a
102                  * default expectation for FUSE_ACCESS and return ENOSYS.
103                  *
104                  * Individual test cases can override this expectation since
105                  * googlemock evaluates expectations in LIFO order.
106                  */
107                 EXPECT_CALL(*m_mock, process(
108                         ResultOf([=](auto in) {
109                                 return (in->header.opcode == FUSE_ACCESS);
110                         }, Eq(true)),
111                         _)
112                 ).Times(AnyNumber())
113                 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
114         } catch (std::system_error err) {
115                 FAIL() << err.what();
116         }
117 }
118
119 void
120 FuseTest::expect_access(uint64_t ino, mode_t access_mode, int error)
121 {
122         EXPECT_CALL(*m_mock, process(
123                 ResultOf([=](auto in) {
124                         return (in->header.opcode == FUSE_ACCESS &&
125                                 in->header.nodeid == ino &&
126                                 in->body.access.mask == access_mode);
127                 }, Eq(true)),
128                 _)
129         ).WillOnce(Invoke(ReturnErrno(error)));
130 }
131
132 void
133 FuseTest::expect_flush(uint64_t ino, int times, ProcessMockerT r)
134 {
135         EXPECT_CALL(*m_mock, process(
136                 ResultOf([=](auto in) {
137                         return (in->header.opcode == FUSE_FLUSH &&
138                                 in->header.nodeid == ino);
139                 }, Eq(true)),
140                 _)
141         ).Times(times)
142         .WillRepeatedly(Invoke(r));
143 }
144
145 void
146 FuseTest::expect_forget(uint64_t ino, uint64_t nlookup)
147 {
148         EXPECT_CALL(*m_mock, process(
149                 ResultOf([=](auto in) {
150                         return (in->header.opcode == FUSE_FORGET &&
151                                 in->header.nodeid == ino &&
152                                 in->body.forget.nlookup == nlookup);
153                 }, Eq(true)),
154                 _)
155         ).WillOnce(Invoke([](auto in __unused, auto &out __unused) {
156                 /* FUSE_FORGET has no response! */
157         }));
158 }
159
160 void FuseTest::expect_getattr(uint64_t ino, uint64_t size)
161 {
162         EXPECT_CALL(*m_mock, process(
163                 ResultOf([=](auto in) {
164                         return (in->header.opcode == FUSE_GETATTR &&
165                                 in->header.nodeid == ino);
166                 }, Eq(true)),
167                 _)
168         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
169                 SET_OUT_HEADER_LEN(out, attr);
170                 out->body.attr.attr.ino = ino;  // Must match nodeid
171                 out->body.attr.attr.mode = S_IFREG | 0644;
172                 out->body.attr.attr.size = size;
173                 out->body.attr.attr_valid = UINT64_MAX;
174         })));
175 }
176
177 void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
178         uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid)
179 {
180         EXPECT_LOOKUP(1, relpath)
181         .Times(times)
182         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
183                 SET_OUT_HEADER_LEN(out, entry);
184                 out->body.entry.attr.mode = mode;
185                 out->body.entry.nodeid = ino;
186                 out->body.entry.attr.nlink = 1;
187                 out->body.entry.attr_valid = attr_valid;
188                 out->body.entry.attr.size = size;
189                 out->body.entry.attr.uid = uid;
190                 out->body.entry.attr.gid = gid;
191         })));
192 }
193
194 void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times)
195 {
196         EXPECT_CALL(*m_mock, process(
197                 ResultOf([=](auto in) {
198                         return (in->header.opcode == FUSE_OPEN &&
199                                 in->header.nodeid == ino);
200                 }, Eq(true)),
201                 _)
202         ).Times(times)
203         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
204                 out->header.len = sizeof(out->header);
205                 SET_OUT_HEADER_LEN(out, open);
206                 out->body.open.fh = FH;
207                 out->body.open.open_flags = flags;
208         })));
209 }
210
211 void FuseTest::expect_opendir(uint64_t ino)
212 {
213         /* opendir(3) calls fstatfs */
214         EXPECT_CALL(*m_mock, process(
215                 ResultOf([](auto in) {
216                         return (in->header.opcode == FUSE_STATFS);
217                 }, Eq(true)),
218                 _)
219         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
220                 SET_OUT_HEADER_LEN(out, statfs);
221         })));
222
223         EXPECT_CALL(*m_mock, process(
224                 ResultOf([=](auto in) {
225                         return (in->header.opcode == FUSE_OPENDIR &&
226                                 in->header.nodeid == ino);
227                 }, Eq(true)),
228                 _)
229         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
230                 out->header.len = sizeof(out->header);
231                 SET_OUT_HEADER_LEN(out, open);
232                 out->body.open.fh = FH;
233         })));
234 }
235
236 void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
237         uint64_t osize, const void *contents)
238 {
239         EXPECT_CALL(*m_mock, process(
240                 ResultOf([=](auto in) {
241                         return (in->header.opcode == FUSE_READ &&
242                                 in->header.nodeid == ino &&
243                                 in->body.read.fh == FH &&
244                                 in->body.read.offset == offset &&
245                                 in->body.read.size == isize);
246                 }, Eq(true)),
247                 _)
248         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
249                 out->header.len = sizeof(struct fuse_out_header) + osize;
250                 memmove(out->body.bytes, contents, osize);
251         }))).RetiresOnSaturation();
252 }
253
254 void FuseTest::expect_release(uint64_t ino, uint64_t fh)
255 {
256         EXPECT_CALL(*m_mock, process(
257                 ResultOf([=](auto in) {
258                         return (in->header.opcode == FUSE_RELEASE &&
259                                 in->header.nodeid == ino &&
260                                 in->body.release.fh == fh);
261                 }, Eq(true)),
262                 _)
263         ).WillOnce(Invoke(ReturnErrno(0)));
264 }
265
266 void FuseTest::expect_releasedir(uint64_t ino, ProcessMockerT r)
267 {
268         EXPECT_CALL(*m_mock, process(
269                 ResultOf([=](auto in) {
270                         return (in->header.opcode == FUSE_RELEASEDIR &&
271                                 in->header.nodeid == ino &&
272                                 in->body.release.fh == FH);
273                 }, Eq(true)),
274                 _)
275         ).WillOnce(Invoke(r));
276 }
277
278 void FuseTest::expect_unlink(uint64_t parent, const char *path, int error)
279 {
280         EXPECT_CALL(*m_mock, process(
281                 ResultOf([=](auto in) {
282                         return (in->header.opcode == FUSE_UNLINK &&
283                                 0 == strcmp(path, in->body.unlink) &&
284                                 in->header.nodeid == parent);
285                 }, Eq(true)),
286                 _)
287         ).WillOnce(Invoke(ReturnErrno(error)));
288 }
289
290 void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
291         uint64_t osize, uint32_t flags, const void *contents)
292 {
293         EXPECT_CALL(*m_mock, process(
294                 ResultOf([=](auto in) {
295                         const char *buf = (const char*)in->body.bytes +
296                                 sizeof(struct fuse_write_in);
297                         bool pid_ok;
298
299                         if (in->body.write.write_flags & FUSE_WRITE_CACHE)
300                                 pid_ok = true;
301                         else
302                                 pid_ok = (pid_t)in->header.pid == getpid();
303
304                         return (in->header.opcode == FUSE_WRITE &&
305                                 in->header.nodeid == ino &&
306                                 in->body.write.fh == FH &&
307                                 in->body.write.offset == offset  &&
308                                 in->body.write.size == isize &&
309                                 pid_ok &&
310                                 in->body.write.write_flags == flags &&
311                                 0 == bcmp(buf, contents, isize));
312                 }, Eq(true)),
313                 _)
314         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
315                 SET_OUT_HEADER_LEN(out, write);
316                 out->body.write.size = osize;
317         })));
318 }
319
320 static void
321 get_unprivileged_uid(uid_t *uid)
322 {
323         struct passwd *pw;
324
325         /* 
326          * First try "tests", Kyua's default unprivileged user.  XXX after
327          * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API
328          */
329         pw = getpwnam("tests");
330         if (pw == NULL) {
331                 /* Fall back to "nobody" */
332                 pw = getpwnam("nobody");
333         }
334         if (pw == NULL)
335                 GTEST_SKIP() << "Test requires an unprivileged user";
336         *uid = pw->pw_uid;
337 }
338
339 void
340 FuseTest::fork(bool drop_privs, int *child_status,
341         std::function<void()> parent_func,
342         std::function<int()> child_func)
343 {
344         sem_t *sem;
345         int mprot = PROT_READ | PROT_WRITE;
346         int mflags = MAP_ANON | MAP_SHARED;
347         pid_t child;
348         uid_t uid;
349         
350         if (drop_privs) {
351                 get_unprivileged_uid(&uid);
352                 if (IsSkipped())
353                         return;
354         }
355
356         sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0);
357         ASSERT_NE(MAP_FAILED, sem) << strerror(errno);
358         ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno);
359
360         if ((child = ::fork()) == 0) {
361                 /* In child */
362                 int err = 0;
363
364                 if (sem_wait(sem)) {
365                         perror("sem_wait");
366                         err = 1;
367                         goto out;
368                 }
369
370                 if (drop_privs && 0 != setreuid(-1, uid)) {
371                         perror("setreuid");
372                         err = 1;
373                         goto out;
374                 }
375                 err = child_func();
376
377 out:
378                 sem_destroy(sem);
379                 _exit(err);
380         } else if (child > 0) {
381                 /* 
382                  * In parent.  Cleanup must happen here, because it's still
383                  * privileged.
384                  */
385                 m_mock->m_child_pid = child;
386                 ASSERT_NO_FATAL_FAILURE(parent_func());
387
388                 /* Signal the child process to go */
389                 ASSERT_EQ(0, sem_post(sem)) << strerror(errno);
390
391                 ASSERT_LE(0, wait(child_status)) << strerror(errno);
392         } else {
393                 FAIL() << strerror(errno);
394         }
395         munmap(sem, sizeof(*sem));
396         return;
397 }
398
399 static void usage(char* progname) {
400         fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname);
401         exit(2);
402 }
403
404 int main(int argc, char **argv) {
405         int ch;
406         FuseEnv *fuse_env = new FuseEnv;
407
408         InitGoogleTest(&argc, argv);
409         AddGlobalTestEnvironment(fuse_env);
410
411         while ((ch = getopt(argc, argv, "v")) != -1) {
412                 switch (ch) {
413                         case 'v':
414                                 verbosity++;
415                                 break;
416                         default:
417                                 usage(argv[0]);
418                                 break;
419                 }
420         }
421
422         return (RUN_ALL_TESTS());
423 }