2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
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
37 #include <sys/types.h>
38 #include <sys/extattr.h>
47 using namespace testing;
49 class DefaultPermissions: public FuseTest {
51 virtual void SetUp() {
52 m_default_permissions = true;
54 if (HasFatalFailure() || IsSkipped())
58 GTEST_SKIP() << "This test requires an unprivileged user";
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);
71 void expect_chmod(uint64_t ino, mode_t mode)
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);
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;
89 void expect_create(const char *relpath, uint64_t ino)
91 EXPECT_CALL(*m_mock, process(
92 ResultOf([=](auto in) {
93 const char *name = (const char*)in->body.bytes +
95 return (in->header.opcode == FUSE_CREATE &&
96 (0 == strcmp(relpath, name)));
99 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
100 SET_OUT_HEADER_LEN(out, create);
101 out->body.create.entry.attr.mode = S_IFREG | 0644;
102 out->body.create.entry.nodeid = ino;
103 out->body.create.entry.entry_valid = UINT64_MAX;
104 out->body.create.entry.attr_valid = UINT64_MAX;
108 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
109 uid_t uid = 0, gid_t gid = 0)
111 EXPECT_CALL(*m_mock, process(
112 ResultOf([=](auto in) {
113 return (in->header.opcode == FUSE_GETATTR &&
114 in->header.nodeid == ino);
118 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
119 SET_OUT_HEADER_LEN(out, attr);
120 out->body.attr.attr.ino = ino; // Must match nodeid
121 out->body.attr.attr.mode = mode;
122 out->body.attr.attr.size = 0;
123 out->body.attr.attr.uid = uid;
124 out->body.attr.attr.uid = gid;
125 out->body.attr.attr_valid = attr_valid;
129 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
130 uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
132 FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
137 class Access: public DefaultPermissions {};
138 class Chown: public DefaultPermissions {};
139 class Chgrp: public DefaultPermissions {};
140 class Lookup: public DefaultPermissions {};
141 class Open: public DefaultPermissions {};
142 class Setattr: public DefaultPermissions {};
143 class Unlink: public DefaultPermissions {};
144 class Write: public DefaultPermissions {};
147 * Test permission handling during create, mkdir, mknod, link, symlink, and
148 * rename vops (they all share a common path for permission checks in
151 class Create: public DefaultPermissions {};
153 class Deleteextattr: public DefaultPermissions {
155 void expect_removexattr()
157 EXPECT_CALL(*m_mock, process(
158 ResultOf([=](auto in) {
159 return (in->header.opcode == FUSE_REMOVEXATTR);
162 ).WillOnce(Invoke(ReturnErrno(0)));
166 class Getextattr: public DefaultPermissions {
168 void expect_getxattr(ProcessMockerT r)
170 EXPECT_CALL(*m_mock, process(
171 ResultOf([=](auto in) {
172 return (in->header.opcode == FUSE_GETXATTR);
175 ).WillOnce(Invoke(r));
179 class Listextattr: public DefaultPermissions {
181 void expect_listxattr()
183 EXPECT_CALL(*m_mock, process(
184 ResultOf([=](auto in) {
185 return (in->header.opcode == FUSE_LISTXATTR);
188 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
189 out->body.listxattr.size = 0;
190 SET_OUT_HEADER_LEN(out, listxattr);
195 class Rename: public DefaultPermissions {
198 * Expect a rename and respond with the given error. Don't both to
199 * validate arguments; the tests in rename.cc do that.
201 void expect_rename(int error)
203 EXPECT_CALL(*m_mock, process(
204 ResultOf([=](auto in) {
205 return (in->header.opcode == FUSE_RENAME);
208 ).WillOnce(Invoke(ReturnErrno(error)));
212 class Setextattr: public DefaultPermissions {
214 void expect_setxattr(int error)
216 EXPECT_CALL(*m_mock, process(
217 ResultOf([=](auto in) {
218 return (in->header.opcode == FUSE_SETXATTR);
221 ).WillOnce(Invoke(ReturnErrno(error)));
225 /* Return a group to which this user does not belong */
226 static gid_t excluded_group()
229 gid_t newgid, groups[ngroups];
231 getgrouplist(getlogin(), getegid(), groups, &ngroups);
232 for (newgid = 0; newgid >= 0; newgid++) {
233 bool belongs = false;
235 for (i = 0; i < ngroups; i++) {
236 if (groups[i] == newgid)
242 /* newgid is now a group to which the current user does not belong */
246 TEST_F(Access, eacces)
248 const char FULLPATH[] = "mountpoint/some_file.txt";
249 const char RELPATH[] = "some_file.txt";
251 mode_t access_mode = X_OK;
253 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
254 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
256 ASSERT_NE(0, access(FULLPATH, access_mode));
257 ASSERT_EQ(EACCES, errno);
260 TEST_F(Access, eacces_no_cached_attrs)
262 const char FULLPATH[] = "mountpoint/some_file.txt";
263 const char RELPATH[] = "some_file.txt";
265 mode_t access_mode = X_OK;
267 expect_getattr(1, S_IFDIR | 0755, 0, 1);
268 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
269 expect_getattr(ino, S_IFREG | 0644, 0, 1);
271 * Once default_permissions is properly implemented, there might be
272 * another FUSE_GETATTR or something in here. But there should not be
276 ASSERT_NE(0, access(FULLPATH, access_mode));
277 ASSERT_EQ(EACCES, errno);
282 const char FULLPATH[] = "mountpoint/some_file.txt";
283 const char RELPATH[] = "some_file.txt";
285 mode_t access_mode = R_OK;
287 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
288 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
290 * Once default_permissions is properly implemented, there might be
291 * another FUSE_GETATTR or something in here.
294 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
297 /* Only root may change a file's owner */
300 const char FULLPATH[] = "mountpoint/some_file.txt";
301 const char RELPATH[] = "some_file.txt";
302 const uint64_t ino = 42;
303 const mode_t mode = 0755;
305 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
306 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
307 EXPECT_CALL(*m_mock, process(
308 ResultOf([](auto in) {
309 return (in->header.opcode == FUSE_SETATTR);
314 EXPECT_NE(0, chown(FULLPATH, 0, -1));
315 EXPECT_EQ(EPERM, errno);
318 /* non-root users may only chgrp a file to a group they belong to */
321 const char FULLPATH[] = "mountpoint/some_file.txt";
322 const char RELPATH[] = "some_file.txt";
323 const uint64_t ino = 42;
324 const mode_t mode = 0755;
330 newgid = excluded_group();
332 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
333 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
334 EXPECT_CALL(*m_mock, process(
335 ResultOf([](auto in) {
336 return (in->header.opcode == FUSE_SETATTR);
341 EXPECT_NE(0, chown(FULLPATH, -1, newgid));
342 EXPECT_EQ(EPERM, errno);
347 const char FULLPATH[] = "mountpoint/some_file.txt";
348 const char RELPATH[] = "some_file.txt";
349 const uint64_t ino = 42;
350 const mode_t mode = 0755;
358 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
359 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
360 EXPECT_CALL(*m_mock, process(
361 ResultOf([](auto in) {
362 return (in->header.opcode == FUSE_SETATTR);
366 EXPECT_CALL(*m_mock, process(
367 ResultOf([](auto in) {
368 return (in->header.opcode == FUSE_SETATTR &&
369 in->header.nodeid == ino);
372 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
373 SET_OUT_HEADER_LEN(out, attr);
374 out->body.attr.attr.mode = S_IFREG | mode;
375 out->body.attr.attr.uid = uid;
376 out->body.attr.attr.gid = newgid;
379 EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
384 const char FULLPATH[] = "mountpoint/some_file.txt";
385 const char RELPATH[] = "some_file.txt";
389 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
390 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
391 expect_create(RELPATH, ino);
393 fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
394 EXPECT_LE(0, fd) << strerror(errno);
395 /* Deliberately leak fd. close(2) will be tested in release.cc */
398 TEST_F(Create, eacces)
400 const char FULLPATH[] = "mountpoint/some_file.txt";
401 const char RELPATH[] = "some_file.txt";
403 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
404 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
406 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
407 EXPECT_EQ(EACCES, errno);
410 TEST_F(Deleteextattr, eacces)
412 const char FULLPATH[] = "mountpoint/some_file.txt";
413 const char RELPATH[] = "some_file.txt";
415 int ns = EXTATTR_NAMESPACE_USER;
417 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
418 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
420 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
421 ASSERT_EQ(EACCES, errno);
424 TEST_F(Deleteextattr, ok)
426 const char FULLPATH[] = "mountpoint/some_file.txt";
427 const char RELPATH[] = "some_file.txt";
429 int ns = EXTATTR_NAMESPACE_USER;
431 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
432 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
433 expect_removexattr();
435 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
439 /* Delete system attributes requires superuser privilege */
440 TEST_F(Deleteextattr, system)
442 const char FULLPATH[] = "mountpoint/some_file.txt";
443 const char RELPATH[] = "some_file.txt";
445 int ns = EXTATTR_NAMESPACE_SYSTEM;
447 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
448 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
450 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
451 ASSERT_EQ(EPERM, errno);
454 /* Deleting user attributes merely requires WRITE privilege */
455 TEST_F(Deleteextattr, user)
457 const char FULLPATH[] = "mountpoint/some_file.txt";
458 const char RELPATH[] = "some_file.txt";
460 int ns = EXTATTR_NAMESPACE_USER;
462 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
463 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
464 expect_removexattr();
466 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
470 TEST_F(Getextattr, eacces)
472 const char FULLPATH[] = "mountpoint/some_file.txt";
473 const char RELPATH[] = "some_file.txt";
476 int ns = EXTATTR_NAMESPACE_USER;
478 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
479 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
482 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
483 ASSERT_EQ(EACCES, errno);
486 TEST_F(Getextattr, ok)
488 const char FULLPATH[] = "mountpoint/some_file.txt";
489 const char RELPATH[] = "some_file.txt";
492 const char value[] = "whatever";
493 ssize_t value_len = strlen(value) + 1;
494 int ns = EXTATTR_NAMESPACE_USER;
497 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
498 /* Getting user attributes only requires read access */
499 expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
501 ReturnImmediate([&](auto in __unused, auto out) {
502 memcpy((void*)out->body.bytes, value, value_len);
503 out->header.len = sizeof(out->header) + value_len;
507 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
508 ASSERT_EQ(value_len, r) << strerror(errno);
509 EXPECT_STREQ(value, data);
512 /* Getting system attributes requires superuser privileges */
513 TEST_F(Getextattr, system)
515 const char FULLPATH[] = "mountpoint/some_file.txt";
516 const char RELPATH[] = "some_file.txt";
519 int ns = EXTATTR_NAMESPACE_SYSTEM;
521 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
522 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
525 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
526 ASSERT_EQ(EPERM, errno);
529 TEST_F(Listextattr, eacces)
531 const char FULLPATH[] = "mountpoint/some_file.txt";
532 const char RELPATH[] = "some_file.txt";
534 int ns = EXTATTR_NAMESPACE_USER;
536 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
537 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
539 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
540 ASSERT_EQ(EACCES, errno);
543 TEST_F(Listextattr, ok)
545 const char FULLPATH[] = "mountpoint/some_file.txt";
546 const char RELPATH[] = "some_file.txt";
548 int ns = EXTATTR_NAMESPACE_USER;
550 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
551 /* Listing user extended attributes merely requires read access */
552 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
555 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
559 /* Listing system xattrs requires superuser privileges */
560 TEST_F(Listextattr, system)
562 const char FULLPATH[] = "mountpoint/some_file.txt";
563 const char RELPATH[] = "some_file.txt";
565 int ns = EXTATTR_NAMESPACE_SYSTEM;
567 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
568 /* Listing user extended attributes merely requires read access */
569 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
571 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
572 ASSERT_EQ(EPERM, errno);
575 /* A component of the search path lacks execute permissions */
576 TEST_F(Lookup, eacces)
578 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
579 const char RELDIRPATH[] = "some_dir";
580 uint64_t dir_ino = 42;
582 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
583 expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
585 EXPECT_EQ(-1, access(FULLPATH, F_OK));
586 EXPECT_EQ(EACCES, errno);
591 const char FULLPATH[] = "mountpoint/some_file.txt";
592 const char RELPATH[] = "some_file.txt";
595 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
596 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
598 EXPECT_NE(0, open(FULLPATH, O_RDWR));
599 EXPECT_EQ(EACCES, errno);
604 const char FULLPATH[] = "mountpoint/some_file.txt";
605 const char RELPATH[] = "some_file.txt";
609 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
610 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
611 expect_open(ino, 0, 1);
613 fd = open(FULLPATH, O_RDONLY);
614 EXPECT_LE(0, fd) << strerror(errno);
615 /* Deliberately leak fd. close(2) will be tested in release.cc */
618 TEST_F(Rename, eacces_on_srcdir)
620 const char FULLDST[] = "mountpoint/d/dst";
621 const char RELDST[] = "d/dst";
622 const char FULLSRC[] = "mountpoint/src";
623 const char RELSRC[] = "src";
626 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
627 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
628 EXPECT_LOOKUP(1, RELDST)
630 .WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
632 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
633 ASSERT_EQ(EACCES, errno);
636 TEST_F(Rename, eacces_on_dstdir_for_creating)
638 const char FULLDST[] = "mountpoint/d/dst";
639 const char RELDSTDIR[] = "d";
640 const char RELDST[] = "dst";
641 const char FULLSRC[] = "mountpoint/src";
642 const char RELSRC[] = "src";
643 uint64_t src_ino = 42;
644 uint64_t dstdir_ino = 43;
646 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
647 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
648 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
649 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
651 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
652 ASSERT_EQ(EACCES, errno);
655 TEST_F(Rename, eacces_on_dstdir_for_removing)
657 const char FULLDST[] = "mountpoint/d/dst";
658 const char RELDSTDIR[] = "d";
659 const char RELDST[] = "dst";
660 const char FULLSRC[] = "mountpoint/src";
661 const char RELSRC[] = "src";
662 uint64_t src_ino = 42;
663 uint64_t dstdir_ino = 43;
665 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
666 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
667 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
668 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
670 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
671 ASSERT_EQ(EACCES, errno);
674 TEST_F(Rename, eperm_on_sticky_srcdir)
676 const char FULLDST[] = "mountpoint/d/dst";
677 const char FULLSRC[] = "mountpoint/src";
678 const char RELSRC[] = "src";
681 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
682 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
684 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
685 ASSERT_EQ(EPERM, errno);
688 TEST_F(Rename, eperm_on_sticky_dstdir)
690 const char FULLDST[] = "mountpoint/d/dst";
691 const char RELDSTDIR[] = "d";
692 const char RELDST[] = "dst";
693 const char FULLSRC[] = "mountpoint/src";
694 const char RELSRC[] = "src";
695 uint64_t src_ino = 42;
696 uint64_t dstdir_ino = 43;
697 uint64_t dst_ino = 44;
699 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
700 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
701 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
702 EXPECT_LOOKUP(dstdir_ino, RELDST)
703 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
704 SET_OUT_HEADER_LEN(out, entry);
705 out->body.entry.attr.mode = S_IFREG | 0644;
706 out->body.entry.nodeid = dst_ino;
707 out->body.entry.attr_valid = UINT64_MAX;
708 out->body.entry.entry_valid = UINT64_MAX;
709 out->body.entry.attr.uid = 0;
712 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
713 ASSERT_EQ(EPERM, errno);
716 /* Successfully rename a file, overwriting the destination */
719 const char FULLDST[] = "mountpoint/dst";
720 const char RELDST[] = "dst";
721 const char FULLSRC[] = "mountpoint/src";
722 const char RELSRC[] = "src";
723 // The inode of the already-existing destination file
724 uint64_t dst_ino = 2;
727 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
728 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
729 expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
732 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
735 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
737 const char FULLDST[] = "mountpoint/dst";
738 const char RELDST[] = "dst";
739 const char FULLSRC[] = "mountpoint/src";
740 const char RELSRC[] = "src";
743 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
744 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
745 EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
748 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
753 const char FULLPATH[] = "mountpoint/some_file.txt";
754 const char RELPATH[] = "some_file.txt";
755 const uint64_t ino = 42;
756 const mode_t oldmode = 0755;
757 const mode_t newmode = 0644;
759 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
760 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
761 EXPECT_CALL(*m_mock, process(
762 ResultOf([](auto in) {
763 return (in->header.opcode == FUSE_SETATTR &&
764 in->header.nodeid == ino &&
765 in->body.setattr.mode == newmode);
768 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
769 SET_OUT_HEADER_LEN(out, attr);
770 out->body.attr.attr.mode = S_IFREG | newmode;
773 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
776 TEST_F(Setattr, eacces)
778 const char FULLPATH[] = "mountpoint/some_file.txt";
779 const char RELPATH[] = "some_file.txt";
780 const uint64_t ino = 42;
781 const mode_t oldmode = 0755;
782 const mode_t newmode = 0644;
784 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
785 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
786 EXPECT_CALL(*m_mock, process(
787 ResultOf([](auto in) {
788 return (in->header.opcode == FUSE_SETATTR);
793 EXPECT_NE(0, chmod(FULLPATH, newmode));
794 EXPECT_EQ(EPERM, errno);
798 * ftruncate() of a file without writable permissions should succeed as long as
799 * the file descriptor is writable. This is important when combined with
802 TEST_F(Setattr, ftruncate_of_newly_created_file)
804 const char FULLPATH[] = "mountpoint/some_file.txt";
805 const char RELPATH[] = "some_file.txt";
806 const uint64_t ino = 42;
807 const mode_t mode = 0000;
810 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
811 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
812 expect_create(RELPATH, ino);
813 EXPECT_CALL(*m_mock, process(
814 ResultOf([](auto in) {
815 return (in->header.opcode == FUSE_SETATTR &&
816 in->header.nodeid == ino &&
817 (in->body.setattr.valid & FATTR_SIZE));
820 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
821 SET_OUT_HEADER_LEN(out, attr);
822 out->body.attr.attr.ino = ino;
823 out->body.attr.attr.mode = S_IFREG | mode;
824 out->body.attr.attr_valid = UINT64_MAX;
827 fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
828 ASSERT_LE(0, fd) << strerror(errno);
829 ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
830 /* Deliberately leak fd */
834 * Setting the sgid bit should fail for an unprivileged user who doesn't belong
835 * to the file's group
837 TEST_F(Setattr, sgid_by_non_group_member)
839 const char FULLPATH[] = "mountpoint/some_file.txt";
840 const char RELPATH[] = "some_file.txt";
841 const uint64_t ino = 42;
842 const mode_t oldmode = 0755;
843 const mode_t newmode = 02755;
844 uid_t uid = geteuid();
845 gid_t gid = excluded_group();
847 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
848 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
849 EXPECT_CALL(*m_mock, process(
850 ResultOf([](auto in) {
851 return (in->header.opcode == FUSE_SETATTR);
856 EXPECT_NE(0, chmod(FULLPATH, newmode));
857 EXPECT_EQ(EPERM, errno);
860 /* Only the superuser may set the sticky bit on a non-directory */
861 TEST_F(Setattr, sticky_regular_file)
863 const char FULLPATH[] = "mountpoint/some_file.txt";
864 const char RELPATH[] = "some_file.txt";
865 const uint64_t ino = 42;
866 const mode_t oldmode = 0644;
867 const mode_t newmode = 01644;
869 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
870 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
871 EXPECT_CALL(*m_mock, process(
872 ResultOf([](auto in) {
873 return (in->header.opcode == FUSE_SETATTR);
878 EXPECT_NE(0, chmod(FULLPATH, newmode));
879 EXPECT_EQ(EFTYPE, errno);
882 TEST_F(Setextattr, ok)
884 const char FULLPATH[] = "mountpoint/some_file.txt";
885 const char RELPATH[] = "some_file.txt";
887 const char value[] = "whatever";
888 ssize_t value_len = strlen(value) + 1;
889 int ns = EXTATTR_NAMESPACE_USER;
892 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
893 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
896 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
897 ASSERT_EQ(value_len, r) << strerror(errno);
900 TEST_F(Setextattr, eacces)
902 const char FULLPATH[] = "mountpoint/some_file.txt";
903 const char RELPATH[] = "some_file.txt";
905 const char value[] = "whatever";
906 ssize_t value_len = strlen(value) + 1;
907 int ns = EXTATTR_NAMESPACE_USER;
909 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
910 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
913 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
914 ASSERT_EQ(EACCES, errno);
917 // Setting system attributes requires superuser privileges
918 TEST_F(Setextattr, system)
920 const char FULLPATH[] = "mountpoint/some_file.txt";
921 const char RELPATH[] = "some_file.txt";
923 const char value[] = "whatever";
924 ssize_t value_len = strlen(value) + 1;
925 int ns = EXTATTR_NAMESPACE_SYSTEM;
927 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
928 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
931 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
932 ASSERT_EQ(EPERM, errno);
935 // Setting user attributes merely requires write privileges
936 TEST_F(Setextattr, user)
938 const char FULLPATH[] = "mountpoint/some_file.txt";
939 const char RELPATH[] = "some_file.txt";
941 const char value[] = "whatever";
942 ssize_t value_len = strlen(value) + 1;
943 int ns = EXTATTR_NAMESPACE_USER;
946 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
947 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
950 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
951 ASSERT_EQ(value_len, r) << strerror(errno);
956 const char FULLPATH[] = "mountpoint/some_file.txt";
957 const char RELPATH[] = "some_file.txt";
960 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
961 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
962 expect_unlink(1, RELPATH, 0);
964 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
968 * Ensure that a cached name doesn't cause unlink to bypass permission checks
971 * This test should pass because lookup(9) purges the namecache entry by doing
972 * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
974 TEST_F(Unlink, cached_unwritable_directory)
976 const char FULLPATH[] = "mountpoint/some_file.txt";
977 const char RELPATH[] = "some_file.txt";
980 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
981 EXPECT_LOOKUP(1, RELPATH)
983 .WillRepeatedly(Invoke(
984 ReturnImmediate([=](auto i __unused, auto out) {
985 SET_OUT_HEADER_LEN(out, entry);
986 out->body.entry.attr.mode = S_IFREG | 0644;
987 out->body.entry.nodeid = ino;
988 out->body.entry.entry_valid = UINT64_MAX;
992 /* Fill name cache */
993 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
994 /* Despite cached name , unlink should fail */
995 ASSERT_EQ(-1, unlink(FULLPATH));
996 ASSERT_EQ(EACCES, errno);
999 TEST_F(Unlink, unwritable_directory)
1001 const char FULLPATH[] = "mountpoint/some_file.txt";
1002 const char RELPATH[] = "some_file.txt";
1005 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1006 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1008 ASSERT_EQ(-1, unlink(FULLPATH));
1009 ASSERT_EQ(EACCES, errno);
1012 TEST_F(Unlink, sticky_directory)
1014 const char FULLPATH[] = "mountpoint/some_file.txt";
1015 const char RELPATH[] = "some_file.txt";
1018 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
1019 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1021 ASSERT_EQ(-1, unlink(FULLPATH));
1022 ASSERT_EQ(EPERM, errno);
1025 /* A write by a non-owner should clear a file's SUID bit */
1026 TEST_F(Write, clear_suid)
1028 const char FULLPATH[] = "mountpoint/some_file.txt";
1029 const char RELPATH[] = "some_file.txt";
1032 mode_t oldmode = 04777;
1033 mode_t newmode = 0777;
1034 char wbuf[1] = {'x'};
1037 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1038 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1039 expect_open(ino, 0, 1);
1040 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1041 expect_chmod(ino, newmode);
1043 fd = open(FULLPATH, O_WRONLY);
1044 ASSERT_LE(0, fd) << strerror(errno);
1045 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1046 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1047 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1048 /* Deliberately leak fd. close(2) will be tested in release.cc */
1051 /* A write by a non-owner should clear a file's SGID bit */
1052 TEST_F(Write, clear_sgid)
1054 const char FULLPATH[] = "mountpoint/some_file.txt";
1055 const char RELPATH[] = "some_file.txt";
1058 mode_t oldmode = 02777;
1059 mode_t newmode = 0777;
1060 char wbuf[1] = {'x'};
1063 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1064 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1065 expect_open(ino, 0, 1);
1066 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1067 expect_chmod(ino, newmode);
1069 fd = open(FULLPATH, O_WRONLY);
1070 ASSERT_LE(0, fd) << strerror(errno);
1071 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1072 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1073 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1074 /* Deliberately leak fd. close(2) will be tested in release.cc */