]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/default_permissions.cc
fusefs: Fix another obscure permission handling bug
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / default_permissions.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 /*
32  * Tests for the "default_permissions" mount option.  They must be in their own
33  * file so they can be run as an unprivileged user
34  */
35
36 extern "C" {
37 #include <sys/types.h>
38 #include <sys/extattr.h>
39
40 #include <fcntl.h>
41 #include <unistd.h>
42 }
43
44 #include "mockfs.hh"
45 #include "utils.hh"
46
47 using namespace testing;
48
49 class DefaultPermissions: public FuseTest {
50
51 virtual void SetUp() {
52         m_default_permissions = true;
53         FuseTest::SetUp();
54         if (HasFatalFailure() || IsSkipped())
55                 return;
56
57         if (geteuid() == 0) {
58                 GTEST_SKIP() << "This test requires an unprivileged user";
59         }
60         
61         /* With -o default_permissions, FUSE_ACCESS should never be called */
62         EXPECT_CALL(*m_mock, process(
63                 ResultOf([=](auto in) {
64                         return (in->header.opcode == FUSE_ACCESS);
65                 }, Eq(true)),
66                 _)
67         ).Times(0);
68 }
69
70 public:
71 void expect_chmod(uint64_t ino, mode_t mode)
72 {
73         EXPECT_CALL(*m_mock, process(
74                 ResultOf([=](auto in) {
75                         return (in->header.opcode == FUSE_SETATTR &&
76                                 in->header.nodeid == ino &&
77                                 in->body.setattr.valid == FATTR_MODE &&
78                                 in->body.setattr.mode == mode);
79                 }, Eq(true)),
80                 _)
81         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
82                 SET_OUT_HEADER_LEN(out, attr);
83                 out->body.attr.attr.ino = ino;  // Must match nodeid
84                 out->body.attr.attr.mode = S_IFREG | mode;
85                 out->body.attr.attr_valid = UINT64_MAX;
86         })));
87 }
88
89 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
90         uid_t uid = 0, gid_t gid = 0)
91 {
92         EXPECT_CALL(*m_mock, process(
93                 ResultOf([=](auto in) {
94                         return (in->header.opcode == FUSE_GETATTR &&
95                                 in->header.nodeid == ino);
96                 }, Eq(true)),
97                 _)
98         ).Times(times)
99         .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
100                 SET_OUT_HEADER_LEN(out, attr);
101                 out->body.attr.attr.ino = ino;  // Must match nodeid
102                 out->body.attr.attr.mode = mode;
103                 out->body.attr.attr.size = 0;
104                 out->body.attr.attr.uid = uid;
105                 out->body.attr.attr.uid = gid;
106                 out->body.attr.attr_valid = attr_valid;
107         })));
108 }
109
110 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
111         uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
112 {
113         FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
114 }
115
116 };
117
118 class Access: public DefaultPermissions {};
119 class Chown: public DefaultPermissions {};
120 class Chgrp: public DefaultPermissions {};
121 class Lookup: public DefaultPermissions {};
122 class Open: public DefaultPermissions {};
123 class Setattr: public DefaultPermissions {};
124 class Unlink: public DefaultPermissions {};
125 class Write: public DefaultPermissions {};
126
127 /* 
128  * Test permission handling during create, mkdir, mknod, link, symlink, and
129  * rename vops (they all share a common path for permission checks in
130  * VOP_LOOKUP)
131  */
132 class Create: public DefaultPermissions {
133 public:
134
135 void expect_create(const char *relpath, uint64_t ino)
136 {
137         EXPECT_CALL(*m_mock, process(
138                 ResultOf([=](auto in) {
139                         const char *name = (const char*)in->body.bytes +
140                                 sizeof(fuse_open_in);
141                         return (in->header.opcode == FUSE_CREATE &&
142                                 (0 == strcmp(relpath, name)));
143                 }, Eq(true)),
144                 _)
145         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
146                 SET_OUT_HEADER_LEN(out, create);
147                 out->body.create.entry.attr.mode = S_IFREG | 0644;
148                 out->body.create.entry.nodeid = ino;
149                 out->body.create.entry.entry_valid = UINT64_MAX;
150                 out->body.create.entry.attr_valid = UINT64_MAX;
151         })));
152 }
153
154 };
155
156 class Deleteextattr: public DefaultPermissions {
157 public:
158 void expect_removexattr()
159 {
160         EXPECT_CALL(*m_mock, process(
161                 ResultOf([=](auto in) {
162                         return (in->header.opcode == FUSE_REMOVEXATTR);
163                 }, Eq(true)),
164                 _)
165         ).WillOnce(Invoke(ReturnErrno(0)));
166 }
167 };
168
169 class Getextattr: public DefaultPermissions {
170 public:
171 void expect_getxattr(ProcessMockerT r)
172 {
173         EXPECT_CALL(*m_mock, process(
174                 ResultOf([=](auto in) {
175                         return (in->header.opcode == FUSE_GETXATTR);
176                 }, Eq(true)),
177                 _)
178         ).WillOnce(Invoke(r));
179 }
180 };
181
182 class Listextattr: public DefaultPermissions {
183 public:
184 void expect_listxattr()
185 {
186         EXPECT_CALL(*m_mock, process(
187                 ResultOf([=](auto in) {
188                         return (in->header.opcode == FUSE_LISTXATTR);
189                 }, Eq(true)),
190                 _)
191         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
192                 out->body.listxattr.size = 0;
193                 SET_OUT_HEADER_LEN(out, listxattr);
194         })));
195 }
196 };
197
198 class Rename: public DefaultPermissions {
199 public:
200         /* 
201          * Expect a rename and respond with the given error.  Don't both to
202          * validate arguments; the tests in rename.cc do that.
203          */
204         void expect_rename(int error)
205         {
206                 EXPECT_CALL(*m_mock, process(
207                         ResultOf([=](auto in) {
208                                 return (in->header.opcode == FUSE_RENAME);
209                         }, Eq(true)),
210                         _)
211                 ).WillOnce(Invoke(ReturnErrno(error)));
212         }
213 };
214
215 class Setextattr: public DefaultPermissions {
216 public:
217 void expect_setxattr(int error)
218 {
219         EXPECT_CALL(*m_mock, process(
220                 ResultOf([=](auto in) {
221                         return (in->header.opcode == FUSE_SETXATTR);
222                 }, Eq(true)),
223                 _)
224         ).WillOnce(Invoke(ReturnErrno(error)));
225 }
226 };
227
228 /* Return a group to which this user does not belong */
229 static gid_t excluded_group()
230 {
231         int i, ngroups = 64;
232         gid_t newgid, groups[ngroups];
233
234         getgrouplist(getlogin(), getegid(), groups, &ngroups);
235         for (newgid = 0; newgid >= 0; newgid++) {
236                 bool belongs = false;
237
238                 for (i = 0; i < ngroups; i++) {
239                         if (groups[i] == newgid)
240                                 belongs = true;
241                 }
242                 if (!belongs)
243                         break;
244         }
245         /* newgid is now a group to which the current user does not belong */
246         return newgid;
247 }
248
249 TEST_F(Access, eacces)
250 {
251         const char FULLPATH[] = "mountpoint/some_file.txt";
252         const char RELPATH[] = "some_file.txt";
253         uint64_t ino = 42;
254         mode_t  access_mode = X_OK;
255
256         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
257         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
258
259         ASSERT_NE(0, access(FULLPATH, access_mode));
260         ASSERT_EQ(EACCES, errno);
261 }
262
263 TEST_F(Access, eacces_no_cached_attrs)
264 {
265         const char FULLPATH[] = "mountpoint/some_file.txt";
266         const char RELPATH[] = "some_file.txt";
267         uint64_t ino = 42;
268         mode_t  access_mode = X_OK;
269
270         expect_getattr(1, S_IFDIR | 0755, 0, 1);
271         expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
272         expect_getattr(ino, S_IFREG | 0644, 0, 1);
273         /* 
274          * Once default_permissions is properly implemented, there might be
275          * another FUSE_GETATTR or something in here.  But there should not be
276          * a FUSE_ACCESS
277          */
278
279         ASSERT_NE(0, access(FULLPATH, access_mode));
280         ASSERT_EQ(EACCES, errno);
281 }
282
283 TEST_F(Access, ok)
284 {
285         const char FULLPATH[] = "mountpoint/some_file.txt";
286         const char RELPATH[] = "some_file.txt";
287         uint64_t ino = 42;
288         mode_t  access_mode = R_OK;
289
290         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
291         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
292         /* 
293          * Once default_permissions is properly implemented, there might be
294          * another FUSE_GETATTR or something in here.
295          */
296
297         ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
298 }
299
300 /* Only root may change a file's owner */
301 TEST_F(Chown, eperm)
302 {
303         const char FULLPATH[] = "mountpoint/some_file.txt";
304         const char RELPATH[] = "some_file.txt";
305         const uint64_t ino = 42;
306         const mode_t mode = 0755;
307
308         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
309         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
310         EXPECT_CALL(*m_mock, process(
311                 ResultOf([](auto in) {
312                         return (in->header.opcode == FUSE_SETATTR);
313                 }, Eq(true)),
314                 _)
315         ).Times(0);
316
317         EXPECT_NE(0, chown(FULLPATH, 0, -1));
318         EXPECT_EQ(EPERM, errno);
319 }
320
321 /* non-root users may only chgrp a file to a group they belong to */
322 TEST_F(Chgrp, eperm)
323 {
324         const char FULLPATH[] = "mountpoint/some_file.txt";
325         const char RELPATH[] = "some_file.txt";
326         const uint64_t ino = 42;
327         const mode_t mode = 0755;
328         uid_t uid;
329         gid_t gid, newgid;
330
331         uid = geteuid();
332         gid = getegid();
333         newgid = excluded_group();
334
335         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
336         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
337         EXPECT_CALL(*m_mock, process(
338                 ResultOf([](auto in) {
339                         return (in->header.opcode == FUSE_SETATTR);
340                 }, Eq(true)),
341                 _)
342         ).Times(0);
343
344         EXPECT_NE(0, chown(FULLPATH, -1, newgid));
345         EXPECT_EQ(EPERM, errno);
346 }
347
348 TEST_F(Chgrp, ok)
349 {
350         const char FULLPATH[] = "mountpoint/some_file.txt";
351         const char RELPATH[] = "some_file.txt";
352         const uint64_t ino = 42;
353         const mode_t mode = 0755;
354         uid_t uid;
355         gid_t gid, newgid;
356
357         uid = geteuid();
358         gid = 0;
359         newgid = getegid();
360
361         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
362         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
363         EXPECT_CALL(*m_mock, process(
364                 ResultOf([](auto in) {
365                         return (in->header.opcode == FUSE_SETATTR);
366                 }, Eq(true)),
367                 _)
368         ).Times(0);
369         EXPECT_CALL(*m_mock, process(
370                 ResultOf([](auto in) {
371                         return (in->header.opcode == FUSE_SETATTR &&
372                                 in->header.nodeid == ino);
373                 }, Eq(true)),
374                 _)
375         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
376                 SET_OUT_HEADER_LEN(out, attr);
377                 out->body.attr.attr.mode = S_IFREG | mode;
378                 out->body.attr.attr.uid = uid;
379                 out->body.attr.attr.gid = newgid;
380         })));
381
382         EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
383 }
384
385 TEST_F(Create, ok)
386 {
387         const char FULLPATH[] = "mountpoint/some_file.txt";
388         const char RELPATH[] = "some_file.txt";
389         uint64_t ino = 42;
390         int fd;
391
392         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
393         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
394         expect_create(RELPATH, ino);
395
396         fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
397         EXPECT_LE(0, fd) << strerror(errno);
398         /* Deliberately leak fd.  close(2) will be tested in release.cc */
399 }
400
401 TEST_F(Create, eacces)
402 {
403         const char FULLPATH[] = "mountpoint/some_file.txt";
404         const char RELPATH[] = "some_file.txt";
405
406         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
407         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
408
409         EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
410         EXPECT_EQ(EACCES, errno);
411 }
412
413 TEST_F(Deleteextattr, eacces)
414 {
415         const char FULLPATH[] = "mountpoint/some_file.txt";
416         const char RELPATH[] = "some_file.txt";
417         uint64_t ino = 42;
418         int ns = EXTATTR_NAMESPACE_USER;
419
420         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
421         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
422
423         ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
424         ASSERT_EQ(EACCES, errno);
425 }
426
427 TEST_F(Deleteextattr, ok)
428 {
429         const char FULLPATH[] = "mountpoint/some_file.txt";
430         const char RELPATH[] = "some_file.txt";
431         uint64_t ino = 42;
432         int ns = EXTATTR_NAMESPACE_USER;
433
434         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
435         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
436         expect_removexattr();
437
438         ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
439                 << strerror(errno);
440 }
441
442 /* Delete system attributes requires superuser privilege */
443 TEST_F(Deleteextattr, system)
444 {
445         const char FULLPATH[] = "mountpoint/some_file.txt";
446         const char RELPATH[] = "some_file.txt";
447         uint64_t ino = 42;
448         int ns = EXTATTR_NAMESPACE_SYSTEM;
449
450         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
451         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
452
453         ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
454         ASSERT_EQ(EPERM, errno);
455 }
456
457 /* Deleting user attributes merely requires WRITE privilege */
458 TEST_F(Deleteextattr, user)
459 {
460         const char FULLPATH[] = "mountpoint/some_file.txt";
461         const char RELPATH[] = "some_file.txt";
462         uint64_t ino = 42;
463         int ns = EXTATTR_NAMESPACE_USER;
464
465         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
466         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
467         expect_removexattr();
468
469         ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
470                 << strerror(errno);
471 }
472
473 TEST_F(Getextattr, eacces)
474 {
475         const char FULLPATH[] = "mountpoint/some_file.txt";
476         const char RELPATH[] = "some_file.txt";
477         uint64_t ino = 42;
478         char data[80];
479         int ns = EXTATTR_NAMESPACE_USER;
480
481         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
482         expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
483
484         ASSERT_EQ(-1,
485                 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
486         ASSERT_EQ(EACCES, errno);
487 }
488
489 TEST_F(Getextattr, ok)
490 {
491         const char FULLPATH[] = "mountpoint/some_file.txt";
492         const char RELPATH[] = "some_file.txt";
493         uint64_t ino = 42;
494         char data[80];
495         const char value[] = "whatever";
496         ssize_t value_len = strlen(value) + 1;
497         int ns = EXTATTR_NAMESPACE_USER;
498         ssize_t r;
499
500         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
501         /* Getting user attributes only requires read access */
502         expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
503         expect_getxattr(
504                 ReturnImmediate([&](auto in __unused, auto out) {
505                         memcpy((void*)out->body.bytes, value, value_len);
506                         out->header.len = sizeof(out->header) + value_len;
507                 })
508         );
509
510         r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
511         ASSERT_EQ(value_len, r)  << strerror(errno);
512         EXPECT_STREQ(value, data);
513 }
514
515 /* Getting system attributes requires superuser privileges */
516 TEST_F(Getextattr, system)
517 {
518         const char FULLPATH[] = "mountpoint/some_file.txt";
519         const char RELPATH[] = "some_file.txt";
520         uint64_t ino = 42;
521         char data[80];
522         int ns = EXTATTR_NAMESPACE_SYSTEM;
523
524         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
525         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
526
527         ASSERT_EQ(-1,
528                 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
529         ASSERT_EQ(EPERM, errno);
530 }
531
532 TEST_F(Listextattr, eacces)
533 {
534         const char FULLPATH[] = "mountpoint/some_file.txt";
535         const char RELPATH[] = "some_file.txt";
536         uint64_t ino = 42;
537         int ns = EXTATTR_NAMESPACE_USER;
538
539         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
540         expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
541
542         ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
543         ASSERT_EQ(EACCES, errno);
544 }
545
546 TEST_F(Listextattr, ok)
547 {
548         const char FULLPATH[] = "mountpoint/some_file.txt";
549         const char RELPATH[] = "some_file.txt";
550         uint64_t ino = 42;
551         int ns = EXTATTR_NAMESPACE_USER;
552
553         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
554         /* Listing user extended attributes merely requires read access */
555         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
556         expect_listxattr();
557
558         ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
559                 << strerror(errno);
560 }
561
562 /* Listing system xattrs requires superuser privileges */
563 TEST_F(Listextattr, system)
564 {
565         const char FULLPATH[] = "mountpoint/some_file.txt";
566         const char RELPATH[] = "some_file.txt";
567         uint64_t ino = 42;
568         int ns = EXTATTR_NAMESPACE_SYSTEM;
569
570         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
571         /* Listing user extended attributes merely requires read access */
572         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
573
574         ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
575         ASSERT_EQ(EPERM, errno);
576 }
577
578 /* A component of the search path lacks execute permissions */
579 TEST_F(Lookup, eacces)
580 {
581         const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
582         const char RELDIRPATH[] = "some_dir";
583         uint64_t dir_ino = 42;
584
585         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
586         expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
587
588         EXPECT_EQ(-1, access(FULLPATH, F_OK));
589         EXPECT_EQ(EACCES, errno);
590 }
591
592 TEST_F(Open, eacces)
593 {
594         const char FULLPATH[] = "mountpoint/some_file.txt";
595         const char RELPATH[] = "some_file.txt";
596         uint64_t ino = 42;
597
598         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
599         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
600
601         EXPECT_NE(0, open(FULLPATH, O_RDWR));
602         EXPECT_EQ(EACCES, errno);
603 }
604
605 TEST_F(Open, ok)
606 {
607         const char FULLPATH[] = "mountpoint/some_file.txt";
608         const char RELPATH[] = "some_file.txt";
609         uint64_t ino = 42;
610         int fd;
611
612         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
613         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
614         expect_open(ino, 0, 1);
615
616         fd = open(FULLPATH, O_RDONLY);
617         EXPECT_LE(0, fd) << strerror(errno);
618         /* Deliberately leak fd.  close(2) will be tested in release.cc */
619 }
620
621 TEST_F(Rename, eacces_on_srcdir)
622 {
623         const char FULLDST[] = "mountpoint/d/dst";
624         const char RELDST[] = "d/dst";
625         const char FULLSRC[] = "mountpoint/src";
626         const char RELSRC[] = "src";
627         uint64_t ino = 42;
628
629         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
630         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
631         EXPECT_LOOKUP(1, RELDST)
632                 .Times(AnyNumber())
633                 .WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
634
635         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
636         ASSERT_EQ(EACCES, errno);
637 }
638
639 TEST_F(Rename, eacces_on_dstdir_for_creating)
640 {
641         const char FULLDST[] = "mountpoint/d/dst";
642         const char RELDSTDIR[] = "d";
643         const char RELDST[] = "dst";
644         const char FULLSRC[] = "mountpoint/src";
645         const char RELSRC[] = "src";
646         uint64_t src_ino = 42;
647         uint64_t dstdir_ino = 43;
648
649         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
650         expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
651         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
652         EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
653
654         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
655         ASSERT_EQ(EACCES, errno);
656 }
657
658 TEST_F(Rename, eacces_on_dstdir_for_removing)
659 {
660         const char FULLDST[] = "mountpoint/d/dst";
661         const char RELDSTDIR[] = "d";
662         const char RELDST[] = "dst";
663         const char FULLSRC[] = "mountpoint/src";
664         const char RELSRC[] = "src";
665         uint64_t src_ino = 42;
666         uint64_t dstdir_ino = 43;
667
668         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
669         expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
670         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
671         EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
672
673         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
674         ASSERT_EQ(EACCES, errno);
675 }
676
677 TEST_F(Rename, eperm_on_sticky_srcdir)
678 {
679         const char FULLDST[] = "mountpoint/d/dst";
680         const char FULLSRC[] = "mountpoint/src";
681         const char RELSRC[] = "src";
682         uint64_t ino = 42;
683
684         expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
685         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
686
687         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
688         ASSERT_EQ(EPERM, errno);
689 }
690
691 TEST_F(Rename, eperm_on_sticky_dstdir)
692 {
693         const char FULLDST[] = "mountpoint/d/dst";
694         const char RELDSTDIR[] = "d";
695         const char RELDST[] = "dst";
696         const char FULLSRC[] = "mountpoint/src";
697         const char RELSRC[] = "src";
698         uint64_t src_ino = 42;
699         uint64_t dstdir_ino = 43;
700         uint64_t dst_ino = 44;
701
702         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
703         expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
704         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
705         EXPECT_LOOKUP(dstdir_ino, RELDST)
706         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
707                 SET_OUT_HEADER_LEN(out, entry);
708                 out->body.entry.attr.mode = S_IFREG | 0644;
709                 out->body.entry.nodeid = dst_ino;
710                 out->body.entry.attr_valid = UINT64_MAX;
711                 out->body.entry.entry_valid = UINT64_MAX;
712                 out->body.entry.attr.uid = 0;
713         })));
714
715         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
716         ASSERT_EQ(EPERM, errno);
717 }
718
719 /* Successfully rename a file, overwriting the destination */
720 TEST_F(Rename, ok)
721 {
722         const char FULLDST[] = "mountpoint/dst";
723         const char RELDST[] = "dst";
724         const char FULLSRC[] = "mountpoint/src";
725         const char RELSRC[] = "src";
726         // The inode of the already-existing destination file
727         uint64_t dst_ino = 2;
728         uint64_t ino = 42;
729
730         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
731         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
732         expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
733         expect_rename(0);
734
735         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
736 }
737
738 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
739 {
740         const char FULLDST[] = "mountpoint/dst";
741         const char RELDST[] = "dst";
742         const char FULLSRC[] = "mountpoint/src";
743         const char RELSRC[] = "src";
744         uint64_t ino = 42;
745
746         expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
747         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
748         EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
749         expect_rename(0);
750
751         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
752 }
753
754 TEST_F(Setattr, ok)
755 {
756         const char FULLPATH[] = "mountpoint/some_file.txt";
757         const char RELPATH[] = "some_file.txt";
758         const uint64_t ino = 42;
759         const mode_t oldmode = 0755;
760         const mode_t newmode = 0644;
761
762         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
763         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
764         EXPECT_CALL(*m_mock, process(
765                 ResultOf([](auto in) {
766                         return (in->header.opcode == FUSE_SETATTR &&
767                                 in->header.nodeid == ino &&
768                                 in->body.setattr.mode == newmode);
769                 }, Eq(true)),
770                 _)
771         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
772                 SET_OUT_HEADER_LEN(out, attr);
773                 out->body.attr.attr.mode = S_IFREG | newmode;
774         })));
775
776         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
777 }
778
779 TEST_F(Setattr, eacces)
780 {
781         const char FULLPATH[] = "mountpoint/some_file.txt";
782         const char RELPATH[] = "some_file.txt";
783         const uint64_t ino = 42;
784         const mode_t oldmode = 0755;
785         const mode_t newmode = 0644;
786
787         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
788         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
789         EXPECT_CALL(*m_mock, process(
790                 ResultOf([](auto in) {
791                         return (in->header.opcode == FUSE_SETATTR);
792                 }, Eq(true)),
793                 _)
794         ).Times(0);
795
796         EXPECT_NE(0, chmod(FULLPATH, newmode));
797         EXPECT_EQ(EPERM, errno);
798 }
799
800 /* 
801  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
802  * to the file's group
803  */
804 TEST_F(Setattr, sgid_by_non_group_member)
805 {
806         const char FULLPATH[] = "mountpoint/some_file.txt";
807         const char RELPATH[] = "some_file.txt";
808         const uint64_t ino = 42;
809         const mode_t oldmode = 0755;
810         const mode_t newmode = 02755;
811         uid_t uid = geteuid();
812         gid_t gid = excluded_group();
813
814         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
815         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
816         EXPECT_CALL(*m_mock, process(
817                 ResultOf([](auto in) {
818                         return (in->header.opcode == FUSE_SETATTR);
819                 }, Eq(true)),
820                 _)
821         ).Times(0);
822
823         EXPECT_NE(0, chmod(FULLPATH, newmode));
824         EXPECT_EQ(EPERM, errno);
825 }
826
827 /* Only the superuser may set the sticky bit on a non-directory */
828 TEST_F(Setattr, sticky_regular_file)
829 {
830         const char FULLPATH[] = "mountpoint/some_file.txt";
831         const char RELPATH[] = "some_file.txt";
832         const uint64_t ino = 42;
833         const mode_t oldmode = 0644;
834         const mode_t newmode = 01644;
835
836         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
837         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
838         EXPECT_CALL(*m_mock, process(
839                 ResultOf([](auto in) {
840                         return (in->header.opcode == FUSE_SETATTR);
841                 }, Eq(true)),
842                 _)
843         ).Times(0);
844
845         EXPECT_NE(0, chmod(FULLPATH, newmode));
846         EXPECT_EQ(EFTYPE, errno);
847 }
848
849 TEST_F(Setextattr, ok)
850 {
851         const char FULLPATH[] = "mountpoint/some_file.txt";
852         const char RELPATH[] = "some_file.txt";
853         uint64_t ino = 42;
854         const char value[] = "whatever";
855         ssize_t value_len = strlen(value) + 1;
856         int ns = EXTATTR_NAMESPACE_USER;
857         ssize_t r;
858
859         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
860         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
861         expect_setxattr(0);
862
863         r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
864         ASSERT_EQ(value_len, r) << strerror(errno);
865 }
866
867 TEST_F(Setextattr, eacces)
868 {
869         const char FULLPATH[] = "mountpoint/some_file.txt";
870         const char RELPATH[] = "some_file.txt";
871         uint64_t ino = 42;
872         const char value[] = "whatever";
873         ssize_t value_len = strlen(value) + 1;
874         int ns = EXTATTR_NAMESPACE_USER;
875
876         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
877         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
878
879         ASSERT_EQ(-1,
880                 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
881         ASSERT_EQ(EACCES, errno);
882 }
883
884 // Setting system attributes requires superuser privileges
885 TEST_F(Setextattr, system)
886 {
887         const char FULLPATH[] = "mountpoint/some_file.txt";
888         const char RELPATH[] = "some_file.txt";
889         uint64_t ino = 42;
890         const char value[] = "whatever";
891         ssize_t value_len = strlen(value) + 1;
892         int ns = EXTATTR_NAMESPACE_SYSTEM;
893
894         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
895         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
896
897         ASSERT_EQ(-1,
898                 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
899         ASSERT_EQ(EPERM, errno);
900 }
901
902 // Setting user attributes merely requires write privileges
903 TEST_F(Setextattr, user)
904 {
905         const char FULLPATH[] = "mountpoint/some_file.txt";
906         const char RELPATH[] = "some_file.txt";
907         uint64_t ino = 42;
908         const char value[] = "whatever";
909         ssize_t value_len = strlen(value) + 1;
910         int ns = EXTATTR_NAMESPACE_USER;
911         ssize_t r;
912
913         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
914         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
915         expect_setxattr(0);
916
917         r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
918         ASSERT_EQ(value_len, r) << strerror(errno);
919 }
920
921 TEST_F(Unlink, ok)
922 {
923         const char FULLPATH[] = "mountpoint/some_file.txt";
924         const char RELPATH[] = "some_file.txt";
925         uint64_t ino = 42;
926
927         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
928         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
929         expect_unlink(1, RELPATH, 0);
930
931         ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
932 }
933
934 /*
935  * Ensure that a cached name doesn't cause unlink to bypass permission checks
936  * in VOP_LOOKUP.
937  *
938  * This test should pass because lookup(9) purges the namecache entry by doing
939  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
940  */
941 TEST_F(Unlink, cached_unwritable_directory)
942 {
943         const char FULLPATH[] = "mountpoint/some_file.txt";
944         const char RELPATH[] = "some_file.txt";
945         uint64_t ino = 42;
946
947         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
948         EXPECT_LOOKUP(1, RELPATH)
949         .Times(AnyNumber())
950         .WillRepeatedly(Invoke(
951                 ReturnImmediate([=](auto i __unused, auto out) {
952                         SET_OUT_HEADER_LEN(out, entry);
953                         out->body.entry.attr.mode = S_IFREG | 0644;
954                         out->body.entry.nodeid = ino;
955                         out->body.entry.entry_valid = UINT64_MAX;
956                 }))
957         );
958
959         /* Fill name cache */
960         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
961         /* Despite cached name , unlink should fail */
962         ASSERT_EQ(-1, unlink(FULLPATH));
963         ASSERT_EQ(EACCES, errno);
964 }
965
966 TEST_F(Unlink, unwritable_directory)
967 {
968         const char FULLPATH[] = "mountpoint/some_file.txt";
969         const char RELPATH[] = "some_file.txt";
970         uint64_t ino = 42;
971
972         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
973         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
974
975         ASSERT_EQ(-1, unlink(FULLPATH));
976         ASSERT_EQ(EACCES, errno);
977 }
978
979 TEST_F(Unlink, sticky_directory)
980 {
981         const char FULLPATH[] = "mountpoint/some_file.txt";
982         const char RELPATH[] = "some_file.txt";
983         uint64_t ino = 42;
984
985         expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
986         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
987
988         ASSERT_EQ(-1, unlink(FULLPATH));
989         ASSERT_EQ(EPERM, errno);
990 }
991
992 /* A write by a non-owner should clear a file's SUID bit */
993 TEST_F(Write, clear_suid)
994 {
995         const char FULLPATH[] = "mountpoint/some_file.txt";
996         const char RELPATH[] = "some_file.txt";
997         struct stat sb;
998         uint64_t ino = 42;
999         mode_t oldmode = 04777;
1000         mode_t newmode = 0777;
1001         char wbuf[1] = {'x'};
1002         int fd;
1003
1004         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1005         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1006         expect_open(ino, 0, 1);
1007         expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1008         expect_chmod(ino, newmode);
1009
1010         fd = open(FULLPATH, O_WRONLY);
1011         ASSERT_LE(0, fd) << strerror(errno);
1012         ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1013         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1014         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1015         /* Deliberately leak fd.  close(2) will be tested in release.cc */
1016 }
1017
1018 /* A write by a non-owner should clear a file's SGID bit */
1019 TEST_F(Write, clear_sgid)
1020 {
1021         const char FULLPATH[] = "mountpoint/some_file.txt";
1022         const char RELPATH[] = "some_file.txt";
1023         struct stat sb;
1024         uint64_t ino = 42;
1025         mode_t oldmode = 02777;
1026         mode_t newmode = 0777;
1027         char wbuf[1] = {'x'};
1028         int fd;
1029
1030         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1031         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1032         expect_open(ino, 0, 1);
1033         expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1034         expect_chmod(ino, newmode);
1035
1036         fd = open(FULLPATH, O_WRONLY);
1037         ASSERT_LE(0, fd) << strerror(errno);
1038         ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1039         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1040         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1041         /* Deliberately leak fd.  close(2) will be tested in release.cc */
1042 }