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, uint64_t size = 0)
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.size = size;
86 out.body.attr.attr_valid = UINT64_MAX;
90 void expect_create(const char *relpath, uint64_t ino)
92 EXPECT_CALL(*m_mock, process(
93 ResultOf([=](auto in) {
94 const char *name = (const char*)in.body.bytes +
96 return (in.header.opcode == FUSE_CREATE &&
97 (0 == strcmp(relpath, name)));
100 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
101 SET_OUT_HEADER_LEN(out, create);
102 out.body.create.entry.attr.mode = S_IFREG | 0644;
103 out.body.create.entry.nodeid = ino;
104 out.body.create.entry.entry_valid = UINT64_MAX;
105 out.body.create.entry.attr_valid = UINT64_MAX;
109 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
110 uid_t uid = 0, gid_t gid = 0)
112 EXPECT_CALL(*m_mock, process(
113 ResultOf([=](auto in) {
114 return (in.header.opcode == FUSE_GETATTR &&
115 in.header.nodeid == ino);
119 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
120 SET_OUT_HEADER_LEN(out, attr);
121 out.body.attr.attr.ino = ino; // Must match nodeid
122 out.body.attr.attr.mode = mode;
123 out.body.attr.attr.size = 0;
124 out.body.attr.attr.uid = uid;
125 out.body.attr.attr.uid = gid;
126 out.body.attr.attr_valid = attr_valid;
130 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
131 uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
133 FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
138 class Access: public DefaultPermissions {};
139 class Chown: public DefaultPermissions {};
140 class Chgrp: public DefaultPermissions {};
141 class Lookup: public DefaultPermissions {};
142 class Open: public DefaultPermissions {};
143 class Setattr: public DefaultPermissions {};
144 class Unlink: public DefaultPermissions {};
145 class Utimensat: public DefaultPermissions {};
146 class Write: public DefaultPermissions {};
149 * Test permission handling during create, mkdir, mknod, link, symlink, and
150 * rename vops (they all share a common path for permission checks in
153 class Create: public DefaultPermissions {};
155 class Deleteextattr: public DefaultPermissions {
157 void expect_removexattr()
159 EXPECT_CALL(*m_mock, process(
160 ResultOf([=](auto in) {
161 return (in.header.opcode == FUSE_REMOVEXATTR);
164 ).WillOnce(Invoke(ReturnErrno(0)));
168 class Getextattr: public DefaultPermissions {
170 void expect_getxattr(ProcessMockerT r)
172 EXPECT_CALL(*m_mock, process(
173 ResultOf([=](auto in) {
174 return (in.header.opcode == FUSE_GETXATTR);
177 ).WillOnce(Invoke(r));
181 class Listextattr: public DefaultPermissions {
183 void expect_listxattr()
185 EXPECT_CALL(*m_mock, process(
186 ResultOf([=](auto in) {
187 return (in.header.opcode == FUSE_LISTXATTR);
190 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
191 out.body.listxattr.size = 0;
192 SET_OUT_HEADER_LEN(out, listxattr);
197 class Rename: public DefaultPermissions {
200 * Expect a rename and respond with the given error. Don't both to
201 * validate arguments; the tests in rename.cc do that.
203 void expect_rename(int error)
205 EXPECT_CALL(*m_mock, process(
206 ResultOf([=](auto in) {
207 return (in.header.opcode == FUSE_RENAME);
210 ).WillOnce(Invoke(ReturnErrno(error)));
214 class Setextattr: public DefaultPermissions {
216 void expect_setxattr(int error)
218 EXPECT_CALL(*m_mock, process(
219 ResultOf([=](auto in) {
220 return (in.header.opcode == FUSE_SETXATTR);
223 ).WillOnce(Invoke(ReturnErrno(error)));
227 /* Return a group to which this user does not belong */
228 static gid_t excluded_group()
231 gid_t newgid, groups[ngroups];
233 getgrouplist(getlogin(), getegid(), groups, &ngroups);
234 for (newgid = 0; newgid >= 0; newgid++) {
235 bool belongs = false;
237 for (i = 0; i < ngroups; i++) {
238 if (groups[i] == newgid)
244 /* newgid is now a group to which the current user does not belong */
248 TEST_F(Access, eacces)
250 const char FULLPATH[] = "mountpoint/some_file.txt";
251 const char RELPATH[] = "some_file.txt";
253 mode_t access_mode = X_OK;
255 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
256 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
258 ASSERT_NE(0, access(FULLPATH, access_mode));
259 ASSERT_EQ(EACCES, errno);
262 TEST_F(Access, eacces_no_cached_attrs)
264 const char FULLPATH[] = "mountpoint/some_file.txt";
265 const char RELPATH[] = "some_file.txt";
267 mode_t access_mode = X_OK;
269 expect_getattr(1, S_IFDIR | 0755, 0, 1);
270 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
271 expect_getattr(ino, S_IFREG | 0644, 0, 1);
273 * Once default_permissions is properly implemented, there might be
274 * another FUSE_GETATTR or something in here. But there should not be
278 ASSERT_NE(0, access(FULLPATH, access_mode));
279 ASSERT_EQ(EACCES, errno);
284 const char FULLPATH[] = "mountpoint/some_file.txt";
285 const char RELPATH[] = "some_file.txt";
287 mode_t access_mode = R_OK;
289 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
290 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
292 * Once default_permissions is properly implemented, there might be
293 * another FUSE_GETATTR or something in here.
296 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
299 /* Unprivileged users may chown a file to their own uid */
300 TEST_F(Chown, chown_to_self)
302 const char FULLPATH[] = "mountpoint/some_file.txt";
303 const char RELPATH[] = "some_file.txt";
304 const uint64_t ino = 42;
305 const mode_t mode = 0755;
310 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
311 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
312 /* The OS may optimize chown by omitting the redundant setattr */
313 EXPECT_CALL(*m_mock, process(
314 ResultOf([](auto in) {
315 return (in.header.opcode == FUSE_SETATTR);
318 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
319 SET_OUT_HEADER_LEN(out, attr);
320 out.body.attr.attr.mode = S_IFREG | mode;
321 out.body.attr.attr.uid = uid;
324 EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
328 * A successful chown by a non-privileged non-owner should clear a file's SUID
331 TEST_F(Chown, clear_suid)
333 const char FULLPATH[] = "mountpoint/some_file.txt";
334 const char RELPATH[] = "some_file.txt";
336 const mode_t oldmode = 06755;
337 const mode_t newmode = 0755;
338 uid_t uid = geteuid();
339 uint32_t valid = FATTR_UID | FATTR_MODE;
341 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
342 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
343 EXPECT_CALL(*m_mock, process(
344 ResultOf([=](auto in) {
345 return (in.header.opcode == FUSE_SETATTR &&
346 in.header.nodeid == ino &&
347 in.body.setattr.valid == valid &&
348 in.body.setattr.mode == newmode);
351 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
352 SET_OUT_HEADER_LEN(out, attr);
353 out.body.attr.attr.ino = ino; // Must match nodeid
354 out.body.attr.attr.mode = S_IFREG | newmode;
355 out.body.attr.attr_valid = UINT64_MAX;
358 EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
362 /* Only root may change a file's owner */
365 const char FULLPATH[] = "mountpoint/some_file.txt";
366 const char RELPATH[] = "some_file.txt";
367 const uint64_t ino = 42;
368 const mode_t mode = 0755;
370 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
371 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
372 EXPECT_CALL(*m_mock, process(
373 ResultOf([](auto in) {
374 return (in.header.opcode == FUSE_SETATTR);
379 EXPECT_NE(0, chown(FULLPATH, 0, -1));
380 EXPECT_EQ(EPERM, errno);
384 * A successful chgrp by a non-privileged non-owner should clear a file's SUID
387 TEST_F(Chgrp, clear_suid)
389 const char FULLPATH[] = "mountpoint/some_file.txt";
390 const char RELPATH[] = "some_file.txt";
392 const mode_t oldmode = 06755;
393 const mode_t newmode = 0755;
394 uid_t uid = geteuid();
395 gid_t gid = getegid();
396 uint32_t valid = FATTR_GID | FATTR_MODE;
398 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
399 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
400 EXPECT_CALL(*m_mock, process(
401 ResultOf([=](auto in) {
402 return (in.header.opcode == FUSE_SETATTR &&
403 in.header.nodeid == ino &&
404 in.body.setattr.valid == valid &&
405 in.body.setattr.mode == newmode);
408 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
409 SET_OUT_HEADER_LEN(out, attr);
410 out.body.attr.attr.ino = ino; // Must match nodeid
411 out.body.attr.attr.mode = S_IFREG | newmode;
412 out.body.attr.attr_valid = UINT64_MAX;
415 EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
418 /* non-root users may only chgrp a file to a group they belong to */
421 const char FULLPATH[] = "mountpoint/some_file.txt";
422 const char RELPATH[] = "some_file.txt";
423 const uint64_t ino = 42;
424 const mode_t mode = 0755;
430 newgid = excluded_group();
432 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
433 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
434 EXPECT_CALL(*m_mock, process(
435 ResultOf([](auto in) {
436 return (in.header.opcode == FUSE_SETATTR);
441 EXPECT_NE(0, chown(FULLPATH, -1, newgid));
442 EXPECT_EQ(EPERM, errno);
447 const char FULLPATH[] = "mountpoint/some_file.txt";
448 const char RELPATH[] = "some_file.txt";
449 const uint64_t ino = 42;
450 const mode_t mode = 0755;
458 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
459 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
460 /* The OS may optimize chgrp by omitting the redundant setattr */
461 EXPECT_CALL(*m_mock, process(
462 ResultOf([](auto in) {
463 return (in.header.opcode == FUSE_SETATTR &&
464 in.header.nodeid == ino);
467 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
468 SET_OUT_HEADER_LEN(out, attr);
469 out.body.attr.attr.mode = S_IFREG | mode;
470 out.body.attr.attr.uid = uid;
471 out.body.attr.attr.gid = newgid;
474 EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
479 const char FULLPATH[] = "mountpoint/some_file.txt";
480 const char RELPATH[] = "some_file.txt";
484 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
485 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
486 expect_create(RELPATH, ino);
488 fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
489 EXPECT_LE(0, fd) << strerror(errno);
490 /* Deliberately leak fd. close(2) will be tested in release.cc */
493 TEST_F(Create, eacces)
495 const char FULLPATH[] = "mountpoint/some_file.txt";
496 const char RELPATH[] = "some_file.txt";
498 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
499 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
501 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
502 EXPECT_EQ(EACCES, errno);
505 TEST_F(Deleteextattr, eacces)
507 const char FULLPATH[] = "mountpoint/some_file.txt";
508 const char RELPATH[] = "some_file.txt";
510 int ns = EXTATTR_NAMESPACE_USER;
512 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
513 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
515 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
516 ASSERT_EQ(EACCES, errno);
519 TEST_F(Deleteextattr, ok)
521 const char FULLPATH[] = "mountpoint/some_file.txt";
522 const char RELPATH[] = "some_file.txt";
524 int ns = EXTATTR_NAMESPACE_USER;
526 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
527 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
528 expect_removexattr();
530 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
534 /* Delete system attributes requires superuser privilege */
535 TEST_F(Deleteextattr, system)
537 const char FULLPATH[] = "mountpoint/some_file.txt";
538 const char RELPATH[] = "some_file.txt";
540 int ns = EXTATTR_NAMESPACE_SYSTEM;
542 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
543 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
545 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
546 ASSERT_EQ(EPERM, errno);
549 /* Anybody with write permission can set both timestamps to UTIME_NOW */
550 TEST_F(Utimensat, utime_now)
552 const char FULLPATH[] = "mountpoint/some_file.txt";
553 const char RELPATH[] = "some_file.txt";
554 const uint64_t ino = 42;
555 /* Write permissions for everybody */
556 const mode_t mode = 0666;
558 const timespec times[2] = {
559 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
560 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
563 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
564 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
565 EXPECT_CALL(*m_mock, process(
566 ResultOf([](auto in) {
567 return (in.header.opcode == FUSE_SETATTR &&
568 in.header.nodeid == ino &&
569 in.body.setattr.valid & FATTR_ATIME &&
570 in.body.setattr.valid & FATTR_MTIME);
573 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
574 SET_OUT_HEADER_LEN(out, attr);
575 out.body.attr.attr.mode = S_IFREG | mode;
578 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0))
582 /* Anybody can set both timestamps to UTIME_OMIT */
583 TEST_F(Utimensat, utime_omit)
585 const char FULLPATH[] = "mountpoint/some_file.txt";
586 const char RELPATH[] = "some_file.txt";
587 const uint64_t ino = 42;
588 /* Write permissions for no one */
589 const mode_t mode = 0444;
591 const timespec times[2] = {
592 {.tv_sec = 0, .tv_nsec = UTIME_OMIT},
593 {.tv_sec = 0, .tv_nsec = UTIME_OMIT},
596 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
597 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
599 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0))
603 /* Deleting user attributes merely requires WRITE privilege */
604 TEST_F(Deleteextattr, user)
606 const char FULLPATH[] = "mountpoint/some_file.txt";
607 const char RELPATH[] = "some_file.txt";
609 int ns = EXTATTR_NAMESPACE_USER;
611 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
612 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
613 expect_removexattr();
615 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
619 TEST_F(Getextattr, eacces)
621 const char FULLPATH[] = "mountpoint/some_file.txt";
622 const char RELPATH[] = "some_file.txt";
625 int ns = EXTATTR_NAMESPACE_USER;
627 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
628 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
631 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
632 ASSERT_EQ(EACCES, errno);
635 TEST_F(Getextattr, ok)
637 const char FULLPATH[] = "mountpoint/some_file.txt";
638 const char RELPATH[] = "some_file.txt";
641 const char value[] = "whatever";
642 ssize_t value_len = strlen(value) + 1;
643 int ns = EXTATTR_NAMESPACE_USER;
646 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
647 /* Getting user attributes only requires read access */
648 expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
650 ReturnImmediate([&](auto in __unused, auto& out) {
651 memcpy((void*)out.body.bytes, value, value_len);
652 out.header.len = sizeof(out.header) + value_len;
656 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
657 ASSERT_EQ(value_len, r) << strerror(errno);
658 EXPECT_STREQ(value, data);
661 /* Getting system attributes requires superuser privileges */
662 TEST_F(Getextattr, system)
664 const char FULLPATH[] = "mountpoint/some_file.txt";
665 const char RELPATH[] = "some_file.txt";
668 int ns = EXTATTR_NAMESPACE_SYSTEM;
670 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
671 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
674 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
675 ASSERT_EQ(EPERM, errno);
678 TEST_F(Listextattr, eacces)
680 const char FULLPATH[] = "mountpoint/some_file.txt";
681 const char RELPATH[] = "some_file.txt";
683 int ns = EXTATTR_NAMESPACE_USER;
685 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
686 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
688 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
689 ASSERT_EQ(EACCES, errno);
692 TEST_F(Listextattr, ok)
694 const char FULLPATH[] = "mountpoint/some_file.txt";
695 const char RELPATH[] = "some_file.txt";
697 int ns = EXTATTR_NAMESPACE_USER;
699 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
700 /* Listing user extended attributes merely requires read access */
701 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
704 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
708 /* Listing system xattrs requires superuser privileges */
709 TEST_F(Listextattr, system)
711 const char FULLPATH[] = "mountpoint/some_file.txt";
712 const char RELPATH[] = "some_file.txt";
714 int ns = EXTATTR_NAMESPACE_SYSTEM;
716 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
717 /* Listing user extended attributes merely requires read access */
718 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
720 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
721 ASSERT_EQ(EPERM, errno);
724 /* A component of the search path lacks execute permissions */
725 TEST_F(Lookup, eacces)
727 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
728 const char RELDIRPATH[] = "some_dir";
729 uint64_t dir_ino = 42;
731 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
732 expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
734 EXPECT_EQ(-1, access(FULLPATH, F_OK));
735 EXPECT_EQ(EACCES, errno);
740 const char FULLPATH[] = "mountpoint/some_file.txt";
741 const char RELPATH[] = "some_file.txt";
744 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
745 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
747 EXPECT_NE(0, open(FULLPATH, O_RDWR));
748 EXPECT_EQ(EACCES, errno);
753 const char FULLPATH[] = "mountpoint/some_file.txt";
754 const char RELPATH[] = "some_file.txt";
758 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
759 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
760 expect_open(ino, 0, 1);
762 fd = open(FULLPATH, O_RDONLY);
763 EXPECT_LE(0, fd) << strerror(errno);
764 /* Deliberately leak fd. close(2) will be tested in release.cc */
767 TEST_F(Rename, eacces_on_srcdir)
769 const char FULLDST[] = "mountpoint/d/dst";
770 const char RELDST[] = "d/dst";
771 const char FULLSRC[] = "mountpoint/src";
772 const char RELSRC[] = "src";
775 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
776 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
777 EXPECT_LOOKUP(1, RELDST)
779 .WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
781 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
782 ASSERT_EQ(EACCES, errno);
785 TEST_F(Rename, eacces_on_dstdir_for_creating)
787 const char FULLDST[] = "mountpoint/d/dst";
788 const char RELDSTDIR[] = "d";
789 const char RELDST[] = "dst";
790 const char FULLSRC[] = "mountpoint/src";
791 const char RELSRC[] = "src";
792 uint64_t src_ino = 42;
793 uint64_t dstdir_ino = 43;
795 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
796 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
797 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
798 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
800 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
801 ASSERT_EQ(EACCES, errno);
804 TEST_F(Rename, eacces_on_dstdir_for_removing)
806 const char FULLDST[] = "mountpoint/d/dst";
807 const char RELDSTDIR[] = "d";
808 const char RELDST[] = "dst";
809 const char FULLSRC[] = "mountpoint/src";
810 const char RELSRC[] = "src";
811 uint64_t src_ino = 42;
812 uint64_t dstdir_ino = 43;
814 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
815 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
816 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
817 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
819 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
820 ASSERT_EQ(EACCES, errno);
823 TEST_F(Rename, eperm_on_sticky_srcdir)
825 const char FULLDST[] = "mountpoint/d/dst";
826 const char FULLSRC[] = "mountpoint/src";
827 const char RELSRC[] = "src";
830 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
831 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
833 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
834 ASSERT_EQ(EPERM, errno);
838 * A user cannot move out a subdirectory that he does not own, because that
839 * would require changing the subdirectory's ".." dirent
841 TEST_F(Rename, eperm_for_subdirectory)
843 const char FULLDST[] = "mountpoint/d/dst";
844 const char FULLSRC[] = "mountpoint/src";
845 const char RELDSTDIR[] = "d";
846 const char RELDST[] = "dst";
847 const char RELSRC[] = "src";
849 uint64_t dstdir_ino = 43;
851 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
852 expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
853 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
854 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
856 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
857 ASSERT_EQ(EACCES, errno);
861 * A user _can_ rename a subdirectory to which he lacks write permissions, if
862 * it will keep the same parent
864 TEST_F(Rename, subdirectory_to_same_dir)
866 const char FULLDST[] = "mountpoint/dst";
867 const char FULLSRC[] = "mountpoint/src";
868 const char RELDST[] = "dst";
869 const char RELSRC[] = "src";
872 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
873 expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
874 EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
877 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
880 TEST_F(Rename, eperm_on_sticky_dstdir)
882 const char FULLDST[] = "mountpoint/d/dst";
883 const char RELDSTDIR[] = "d";
884 const char RELDST[] = "dst";
885 const char FULLSRC[] = "mountpoint/src";
886 const char RELSRC[] = "src";
887 uint64_t src_ino = 42;
888 uint64_t dstdir_ino = 43;
889 uint64_t dst_ino = 44;
891 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
892 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
893 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
894 EXPECT_LOOKUP(dstdir_ino, RELDST)
895 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
896 SET_OUT_HEADER_LEN(out, entry);
897 out.body.entry.attr.mode = S_IFREG | 0644;
898 out.body.entry.nodeid = dst_ino;
899 out.body.entry.attr_valid = UINT64_MAX;
900 out.body.entry.entry_valid = UINT64_MAX;
901 out.body.entry.attr.uid = 0;
904 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
905 ASSERT_EQ(EPERM, errno);
908 /* Successfully rename a file, overwriting the destination */
911 const char FULLDST[] = "mountpoint/dst";
912 const char RELDST[] = "dst";
913 const char FULLSRC[] = "mountpoint/src";
914 const char RELSRC[] = "src";
915 // The inode of the already-existing destination file
916 uint64_t dst_ino = 2;
919 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
920 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
921 expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
924 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
927 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
929 const char FULLDST[] = "mountpoint/dst";
930 const char RELDST[] = "dst";
931 const char FULLSRC[] = "mountpoint/src";
932 const char RELSRC[] = "src";
935 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
936 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
937 EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
940 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
945 const char FULLPATH[] = "mountpoint/some_file.txt";
946 const char RELPATH[] = "some_file.txt";
947 const uint64_t ino = 42;
948 const mode_t oldmode = 0755;
949 const mode_t newmode = 0644;
951 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
952 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
953 EXPECT_CALL(*m_mock, process(
954 ResultOf([](auto in) {
955 return (in.header.opcode == FUSE_SETATTR &&
956 in.header.nodeid == ino &&
957 in.body.setattr.mode == newmode);
960 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
961 SET_OUT_HEADER_LEN(out, attr);
962 out.body.attr.attr.mode = S_IFREG | newmode;
965 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
968 TEST_F(Setattr, eacces)
970 const char FULLPATH[] = "mountpoint/some_file.txt";
971 const char RELPATH[] = "some_file.txt";
972 const uint64_t ino = 42;
973 const mode_t oldmode = 0755;
974 const mode_t newmode = 0644;
976 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
977 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
978 EXPECT_CALL(*m_mock, process(
979 ResultOf([](auto in) {
980 return (in.header.opcode == FUSE_SETATTR);
985 EXPECT_NE(0, chmod(FULLPATH, newmode));
986 EXPECT_EQ(EPERM, errno);
990 * ftruncate() of a file without writable permissions should succeed as long as
991 * the file descriptor is writable. This is important when combined with
994 TEST_F(Setattr, ftruncate_of_newly_created_file)
996 const char FULLPATH[] = "mountpoint/some_file.txt";
997 const char RELPATH[] = "some_file.txt";
998 const uint64_t ino = 42;
999 const mode_t mode = 0000;
1002 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
1003 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
1004 expect_create(RELPATH, ino);
1005 EXPECT_CALL(*m_mock, process(
1006 ResultOf([](auto in) {
1007 return (in.header.opcode == FUSE_SETATTR &&
1008 in.header.nodeid == ino &&
1009 (in.body.setattr.valid & FATTR_SIZE));
1012 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1013 SET_OUT_HEADER_LEN(out, attr);
1014 out.body.attr.attr.ino = ino;
1015 out.body.attr.attr.mode = S_IFREG | mode;
1016 out.body.attr.attr_valid = UINT64_MAX;
1019 fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1020 ASSERT_LE(0, fd) << strerror(errno);
1021 ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
1022 /* Deliberately leak fd */
1026 * Setting the sgid bit should fail for an unprivileged user who doesn't belong
1027 * to the file's group
1029 TEST_F(Setattr, sgid_by_non_group_member)
1031 const char FULLPATH[] = "mountpoint/some_file.txt";
1032 const char RELPATH[] = "some_file.txt";
1033 const uint64_t ino = 42;
1034 const mode_t oldmode = 0755;
1035 const mode_t newmode = 02755;
1036 uid_t uid = geteuid();
1037 gid_t gid = excluded_group();
1039 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1040 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
1041 EXPECT_CALL(*m_mock, process(
1042 ResultOf([](auto in) {
1043 return (in.header.opcode == FUSE_SETATTR);
1048 EXPECT_NE(0, chmod(FULLPATH, newmode));
1049 EXPECT_EQ(EPERM, errno);
1052 /* Only the superuser may set the sticky bit on a non-directory */
1053 TEST_F(Setattr, sticky_regular_file)
1055 const char FULLPATH[] = "mountpoint/some_file.txt";
1056 const char RELPATH[] = "some_file.txt";
1057 const uint64_t ino = 42;
1058 const mode_t oldmode = 0644;
1059 const mode_t newmode = 01644;
1061 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1062 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1063 EXPECT_CALL(*m_mock, process(
1064 ResultOf([](auto in) {
1065 return (in.header.opcode == FUSE_SETATTR);
1070 EXPECT_NE(0, chmod(FULLPATH, newmode));
1071 EXPECT_EQ(EFTYPE, errno);
1074 TEST_F(Setextattr, ok)
1076 const char FULLPATH[] = "mountpoint/some_file.txt";
1077 const char RELPATH[] = "some_file.txt";
1079 const char value[] = "whatever";
1080 ssize_t value_len = strlen(value) + 1;
1081 int ns = EXTATTR_NAMESPACE_USER;
1084 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1085 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1088 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1089 ASSERT_EQ(value_len, r) << strerror(errno);
1092 TEST_F(Setextattr, eacces)
1094 const char FULLPATH[] = "mountpoint/some_file.txt";
1095 const char RELPATH[] = "some_file.txt";
1097 const char value[] = "whatever";
1098 ssize_t value_len = strlen(value) + 1;
1099 int ns = EXTATTR_NAMESPACE_USER;
1101 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1102 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1105 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1106 ASSERT_EQ(EACCES, errno);
1109 // Setting system attributes requires superuser privileges
1110 TEST_F(Setextattr, system)
1112 const char FULLPATH[] = "mountpoint/some_file.txt";
1113 const char RELPATH[] = "some_file.txt";
1115 const char value[] = "whatever";
1116 ssize_t value_len = strlen(value) + 1;
1117 int ns = EXTATTR_NAMESPACE_SYSTEM;
1119 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1120 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1123 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1124 ASSERT_EQ(EPERM, errno);
1127 // Setting user attributes merely requires write privileges
1128 TEST_F(Setextattr, user)
1130 const char FULLPATH[] = "mountpoint/some_file.txt";
1131 const char RELPATH[] = "some_file.txt";
1133 const char value[] = "whatever";
1134 ssize_t value_len = strlen(value) + 1;
1135 int ns = EXTATTR_NAMESPACE_USER;
1138 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1139 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1142 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1143 ASSERT_EQ(value_len, r) << strerror(errno);
1148 const char FULLPATH[] = "mountpoint/some_file.txt";
1149 const char RELPATH[] = "some_file.txt";
1152 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
1153 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1154 expect_unlink(1, RELPATH, 0);
1156 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1160 * Ensure that a cached name doesn't cause unlink to bypass permission checks
1163 * This test should pass because lookup(9) purges the namecache entry by doing
1164 * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
1166 TEST_F(Unlink, cached_unwritable_directory)
1168 const char FULLPATH[] = "mountpoint/some_file.txt";
1169 const char RELPATH[] = "some_file.txt";
1172 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1173 EXPECT_LOOKUP(1, RELPATH)
1175 .WillRepeatedly(Invoke(
1176 ReturnImmediate([=](auto i __unused, auto& out) {
1177 SET_OUT_HEADER_LEN(out, entry);
1178 out.body.entry.attr.mode = S_IFREG | 0644;
1179 out.body.entry.nodeid = ino;
1180 out.body.entry.entry_valid = UINT64_MAX;
1184 /* Fill name cache */
1185 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
1186 /* Despite cached name , unlink should fail */
1187 ASSERT_EQ(-1, unlink(FULLPATH));
1188 ASSERT_EQ(EACCES, errno);
1191 TEST_F(Unlink, unwritable_directory)
1193 const char FULLPATH[] = "mountpoint/some_file.txt";
1194 const char RELPATH[] = "some_file.txt";
1197 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1198 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1200 ASSERT_EQ(-1, unlink(FULLPATH));
1201 ASSERT_EQ(EACCES, errno);
1204 TEST_F(Unlink, sticky_directory)
1206 const char FULLPATH[] = "mountpoint/some_file.txt";
1207 const char RELPATH[] = "some_file.txt";
1210 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
1211 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1213 ASSERT_EQ(-1, unlink(FULLPATH));
1214 ASSERT_EQ(EPERM, errno);
1217 /* A write by a non-owner should clear a file's SUID bit */
1218 TEST_F(Write, clear_suid)
1220 const char FULLPATH[] = "mountpoint/some_file.txt";
1221 const char RELPATH[] = "some_file.txt";
1224 mode_t oldmode = 04777;
1225 mode_t newmode = 0777;
1226 char wbuf[1] = {'x'};
1229 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1230 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1231 expect_open(ino, 0, 1);
1232 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1233 expect_chmod(ino, newmode, sizeof(wbuf));
1235 fd = open(FULLPATH, O_WRONLY);
1236 ASSERT_LE(0, fd) << strerror(errno);
1237 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1238 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1239 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1240 /* Deliberately leak fd. close(2) will be tested in release.cc */
1243 /* A write by a non-owner should clear a file's SGID bit */
1244 TEST_F(Write, clear_sgid)
1246 const char FULLPATH[] = "mountpoint/some_file.txt";
1247 const char RELPATH[] = "some_file.txt";
1250 mode_t oldmode = 02777;
1251 mode_t newmode = 0777;
1252 char wbuf[1] = {'x'};
1255 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1256 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1257 expect_open(ino, 0, 1);
1258 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1259 expect_chmod(ino, newmode, sizeof(wbuf));
1261 fd = open(FULLPATH, O_WRONLY);
1262 ASSERT_LE(0, fd) << strerror(errno);
1263 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1264 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1265 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1266 /* Deliberately leak fd. close(2) will be tested in release.cc */
1269 /* Regression test for a specific recurse-of-nonrecursive-lock panic
1271 * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
1272 * may panic. That happens if the FUSE_SETATTR response indicates that the
1273 * file's size has changed since the write.
1275 TEST_F(Write, recursion_panic_while_clearing_suid)
1277 const char FULLPATH[] = "mountpoint/some_file.txt";
1278 const char RELPATH[] = "some_file.txt";
1280 mode_t oldmode = 04777;
1281 mode_t newmode = 0777;
1282 char wbuf[1] = {'x'};
1285 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1286 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1287 expect_open(ino, 0, 1);
1288 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1289 /* XXX Return a smaller file size than what we just wrote! */
1290 expect_chmod(ino, newmode, 0);
1292 fd = open(FULLPATH, O_WRONLY);
1293 ASSERT_LE(0, fd) << strerror(errno);
1294 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1295 /* Deliberately leak fd. close(2) will be tested in release.cc */