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_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
90 uid_t uid = 0, gid_t gid = 0)
92 EXPECT_CALL(*m_mock, process(
93 ResultOf([=](auto in) {
94 return (in->header.opcode == FUSE_GETATTR &&
95 in->header.nodeid == ino);
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;
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)
113 FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
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 {};
128 * Test permission handling during create, mkdir, mknod, link, symlink, and
129 * rename vops (they all share a common path for permission checks in
132 class Create: public DefaultPermissions {
135 void expect_create(const char *relpath, uint64_t ino)
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)));
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;
156 class Deleteextattr: public DefaultPermissions {
158 void expect_removexattr()
160 EXPECT_CALL(*m_mock, process(
161 ResultOf([=](auto in) {
162 return (in->header.opcode == FUSE_REMOVEXATTR);
165 ).WillOnce(Invoke(ReturnErrno(0)));
169 class Getextattr: public DefaultPermissions {
171 void expect_getxattr(ProcessMockerT r)
173 EXPECT_CALL(*m_mock, process(
174 ResultOf([=](auto in) {
175 return (in->header.opcode == FUSE_GETXATTR);
178 ).WillOnce(Invoke(r));
182 class Listextattr: public DefaultPermissions {
184 void expect_listxattr()
186 EXPECT_CALL(*m_mock, process(
187 ResultOf([=](auto in) {
188 return (in->header.opcode == FUSE_LISTXATTR);
191 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) {
192 out->body.listxattr.size = 0;
193 SET_OUT_HEADER_LEN(out, listxattr);
198 class Rename: public DefaultPermissions {
201 * Expect a rename and respond with the given error. Don't both to
202 * validate arguments; the tests in rename.cc do that.
204 void expect_rename(int error)
206 EXPECT_CALL(*m_mock, process(
207 ResultOf([=](auto in) {
208 return (in->header.opcode == FUSE_RENAME);
211 ).WillOnce(Invoke(ReturnErrno(error)));
215 class Setextattr: public DefaultPermissions {
217 void expect_setxattr(int error)
219 EXPECT_CALL(*m_mock, process(
220 ResultOf([=](auto in) {
221 return (in->header.opcode == FUSE_SETXATTR);
224 ).WillOnce(Invoke(ReturnErrno(error)));
228 /* Return a group to which this user does not belong */
229 static gid_t excluded_group()
232 gid_t newgid, groups[ngroups];
234 getgrouplist(getlogin(), getegid(), groups, &ngroups);
235 for (newgid = 0; newgid >= 0; newgid++) {
236 bool belongs = false;
238 for (i = 0; i < ngroups; i++) {
239 if (groups[i] == newgid)
245 /* newgid is now a group to which the current user does not belong */
249 TEST_F(Access, eacces)
251 const char FULLPATH[] = "mountpoint/some_file.txt";
252 const char RELPATH[] = "some_file.txt";
254 mode_t access_mode = X_OK;
256 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
257 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
259 ASSERT_NE(0, access(FULLPATH, access_mode));
260 ASSERT_EQ(EACCES, errno);
263 TEST_F(Access, eacces_no_cached_attrs)
265 const char FULLPATH[] = "mountpoint/some_file.txt";
266 const char RELPATH[] = "some_file.txt";
268 mode_t access_mode = X_OK;
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);
274 * Once default_permissions is properly implemented, there might be
275 * another FUSE_GETATTR or something in here. But there should not be
279 ASSERT_NE(0, access(FULLPATH, access_mode));
280 ASSERT_EQ(EACCES, errno);
285 const char FULLPATH[] = "mountpoint/some_file.txt";
286 const char RELPATH[] = "some_file.txt";
288 mode_t access_mode = R_OK;
290 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
291 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
293 * Once default_permissions is properly implemented, there might be
294 * another FUSE_GETATTR or something in here.
297 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
300 /* Only root may change a file's owner */
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;
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);
317 EXPECT_NE(0, chown(FULLPATH, 0, -1));
318 EXPECT_EQ(EPERM, errno);
321 /* non-root users may only chgrp a file to a group they belong to */
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;
333 newgid = excluded_group();
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);
344 EXPECT_NE(0, chown(FULLPATH, -1, newgid));
345 EXPECT_EQ(EPERM, errno);
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;
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);
369 EXPECT_CALL(*m_mock, process(
370 ResultOf([](auto in) {
371 return (in->header.opcode == FUSE_SETATTR &&
372 in->header.nodeid == ino);
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;
382 EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
387 const char FULLPATH[] = "mountpoint/some_file.txt";
388 const char RELPATH[] = "some_file.txt";
392 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
393 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
394 expect_create(RELPATH, ino);
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 */
401 TEST_F(Create, eacces)
403 const char FULLPATH[] = "mountpoint/some_file.txt";
404 const char RELPATH[] = "some_file.txt";
406 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
407 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
409 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
410 EXPECT_EQ(EACCES, errno);
413 TEST_F(Deleteextattr, eacces)
415 const char FULLPATH[] = "mountpoint/some_file.txt";
416 const char RELPATH[] = "some_file.txt";
418 int ns = EXTATTR_NAMESPACE_USER;
420 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
421 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
423 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
424 ASSERT_EQ(EACCES, errno);
427 TEST_F(Deleteextattr, ok)
429 const char FULLPATH[] = "mountpoint/some_file.txt";
430 const char RELPATH[] = "some_file.txt";
432 int ns = EXTATTR_NAMESPACE_USER;
434 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
435 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
436 expect_removexattr();
438 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
442 /* Delete system attributes requires superuser privilege */
443 TEST_F(Deleteextattr, system)
445 const char FULLPATH[] = "mountpoint/some_file.txt";
446 const char RELPATH[] = "some_file.txt";
448 int ns = EXTATTR_NAMESPACE_SYSTEM;
450 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
451 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
453 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
454 ASSERT_EQ(EPERM, errno);
457 /* Deleting user attributes merely requires WRITE privilege */
458 TEST_F(Deleteextattr, user)
460 const char FULLPATH[] = "mountpoint/some_file.txt";
461 const char RELPATH[] = "some_file.txt";
463 int ns = EXTATTR_NAMESPACE_USER;
465 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
466 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
467 expect_removexattr();
469 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
473 TEST_F(Getextattr, eacces)
475 const char FULLPATH[] = "mountpoint/some_file.txt";
476 const char RELPATH[] = "some_file.txt";
479 int ns = EXTATTR_NAMESPACE_USER;
481 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
482 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
485 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
486 ASSERT_EQ(EACCES, errno);
489 TEST_F(Getextattr, ok)
491 const char FULLPATH[] = "mountpoint/some_file.txt";
492 const char RELPATH[] = "some_file.txt";
495 const char value[] = "whatever";
496 ssize_t value_len = strlen(value) + 1;
497 int ns = EXTATTR_NAMESPACE_USER;
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);
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;
510 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
511 ASSERT_EQ(value_len, r) << strerror(errno);
512 EXPECT_STREQ(value, data);
515 /* Getting system attributes requires superuser privileges */
516 TEST_F(Getextattr, system)
518 const char FULLPATH[] = "mountpoint/some_file.txt";
519 const char RELPATH[] = "some_file.txt";
522 int ns = EXTATTR_NAMESPACE_SYSTEM;
524 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
525 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
528 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
529 ASSERT_EQ(EPERM, errno);
532 TEST_F(Listextattr, eacces)
534 const char FULLPATH[] = "mountpoint/some_file.txt";
535 const char RELPATH[] = "some_file.txt";
537 int ns = EXTATTR_NAMESPACE_USER;
539 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
540 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
542 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
543 ASSERT_EQ(EACCES, errno);
546 TEST_F(Listextattr, ok)
548 const char FULLPATH[] = "mountpoint/some_file.txt";
549 const char RELPATH[] = "some_file.txt";
551 int ns = EXTATTR_NAMESPACE_USER;
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);
558 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
562 /* Listing system xattrs requires superuser privileges */
563 TEST_F(Listextattr, system)
565 const char FULLPATH[] = "mountpoint/some_file.txt";
566 const char RELPATH[] = "some_file.txt";
568 int ns = EXTATTR_NAMESPACE_SYSTEM;
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());
574 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
575 ASSERT_EQ(EPERM, errno);
578 /* A component of the search path lacks execute permissions */
579 TEST_F(Lookup, eacces)
581 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
582 const char RELDIRPATH[] = "some_dir";
583 uint64_t dir_ino = 42;
585 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
586 expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
588 EXPECT_EQ(-1, access(FULLPATH, F_OK));
589 EXPECT_EQ(EACCES, errno);
594 const char FULLPATH[] = "mountpoint/some_file.txt";
595 const char RELPATH[] = "some_file.txt";
598 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
599 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
601 EXPECT_NE(0, open(FULLPATH, O_RDWR));
602 EXPECT_EQ(EACCES, errno);
607 const char FULLPATH[] = "mountpoint/some_file.txt";
608 const char RELPATH[] = "some_file.txt";
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);
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 */
621 TEST_F(Rename, eacces_on_srcdir)
623 const char FULLDST[] = "mountpoint/d/dst";
624 const char RELDST[] = "d/dst";
625 const char FULLSRC[] = "mountpoint/src";
626 const char RELSRC[] = "src";
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)
633 .WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
635 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
636 ASSERT_EQ(EACCES, errno);
639 TEST_F(Rename, eacces_on_dstdir_for_creating)
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;
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)));
654 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
655 ASSERT_EQ(EACCES, errno);
658 TEST_F(Rename, eacces_on_dstdir_for_removing)
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;
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)));
673 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
674 ASSERT_EQ(EACCES, errno);
677 TEST_F(Rename, eperm_on_sticky_srcdir)
679 const char FULLDST[] = "mountpoint/d/dst";
680 const char FULLSRC[] = "mountpoint/src";
681 const char RELSRC[] = "src";
684 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
685 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
687 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
688 ASSERT_EQ(EPERM, errno);
691 TEST_F(Rename, eperm_on_sticky_dstdir)
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;
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;
715 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
716 ASSERT_EQ(EPERM, errno);
719 /* Successfully rename a file, overwriting the destination */
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;
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);
735 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
738 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
740 const char FULLDST[] = "mountpoint/dst";
741 const char RELDST[] = "dst";
742 const char FULLSRC[] = "mountpoint/src";
743 const char RELSRC[] = "src";
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)));
751 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
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;
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);
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;
776 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
779 TEST_F(Setattr, eacces)
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;
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);
796 EXPECT_NE(0, chmod(FULLPATH, newmode));
797 EXPECT_EQ(EPERM, errno);
801 * Setting the sgid bit should fail for an unprivileged user who doesn't belong
802 * to the file's group
804 TEST_F(Setattr, sgid_by_non_group_member)
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();
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);
823 EXPECT_NE(0, chmod(FULLPATH, newmode));
824 EXPECT_EQ(EPERM, errno);
827 /* Only the superuser may set the sticky bit on a non-directory */
828 TEST_F(Setattr, sticky_regular_file)
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;
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);
845 EXPECT_NE(0, chmod(FULLPATH, newmode));
846 EXPECT_EQ(EFTYPE, errno);
849 TEST_F(Setextattr, ok)
851 const char FULLPATH[] = "mountpoint/some_file.txt";
852 const char RELPATH[] = "some_file.txt";
854 const char value[] = "whatever";
855 ssize_t value_len = strlen(value) + 1;
856 int ns = EXTATTR_NAMESPACE_USER;
859 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
860 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
863 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
864 ASSERT_EQ(value_len, r) << strerror(errno);
867 TEST_F(Setextattr, eacces)
869 const char FULLPATH[] = "mountpoint/some_file.txt";
870 const char RELPATH[] = "some_file.txt";
872 const char value[] = "whatever";
873 ssize_t value_len = strlen(value) + 1;
874 int ns = EXTATTR_NAMESPACE_USER;
876 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
877 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
880 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
881 ASSERT_EQ(EACCES, errno);
884 // Setting system attributes requires superuser privileges
885 TEST_F(Setextattr, system)
887 const char FULLPATH[] = "mountpoint/some_file.txt";
888 const char RELPATH[] = "some_file.txt";
890 const char value[] = "whatever";
891 ssize_t value_len = strlen(value) + 1;
892 int ns = EXTATTR_NAMESPACE_SYSTEM;
894 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
895 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
898 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
899 ASSERT_EQ(EPERM, errno);
902 // Setting user attributes merely requires write privileges
903 TEST_F(Setextattr, user)
905 const char FULLPATH[] = "mountpoint/some_file.txt";
906 const char RELPATH[] = "some_file.txt";
908 const char value[] = "whatever";
909 ssize_t value_len = strlen(value) + 1;
910 int ns = EXTATTR_NAMESPACE_USER;
913 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
914 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
917 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
918 ASSERT_EQ(value_len, r) << strerror(errno);
923 const char FULLPATH[] = "mountpoint/some_file.txt";
924 const char RELPATH[] = "some_file.txt";
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);
931 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
935 * Ensure that a cached name doesn't cause unlink to bypass permission checks
938 * This test should pass because lookup(9) purges the namecache entry by doing
939 * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
941 TEST_F(Unlink, cached_unwritable_directory)
943 const char FULLPATH[] = "mountpoint/some_file.txt";
944 const char RELPATH[] = "some_file.txt";
947 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
948 EXPECT_LOOKUP(1, RELPATH)
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;
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);
966 TEST_F(Unlink, unwritable_directory)
968 const char FULLPATH[] = "mountpoint/some_file.txt";
969 const char RELPATH[] = "some_file.txt";
972 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
973 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
975 ASSERT_EQ(-1, unlink(FULLPATH));
976 ASSERT_EQ(EACCES, errno);
979 TEST_F(Unlink, sticky_directory)
981 const char FULLPATH[] = "mountpoint/some_file.txt";
982 const char RELPATH[] = "some_file.txt";
985 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
986 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
988 ASSERT_EQ(-1, unlink(FULLPATH));
989 ASSERT_EQ(EPERM, errno);
992 /* A write by a non-owner should clear a file's SUID bit */
993 TEST_F(Write, clear_suid)
995 const char FULLPATH[] = "mountpoint/some_file.txt";
996 const char RELPATH[] = "some_file.txt";
999 mode_t oldmode = 04777;
1000 mode_t newmode = 0777;
1001 char wbuf[1] = {'x'};
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);
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 */
1018 /* A write by a non-owner should clear a file's SGID bit */
1019 TEST_F(Write, clear_sgid)
1021 const char FULLPATH[] = "mountpoint/some_file.txt";
1022 const char RELPATH[] = "some_file.txt";
1025 mode_t oldmode = 02777;
1026 mode_t newmode = 0777;
1027 char wbuf[1] = {'x'};
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);
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 */