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 /* Unprivileged users may chown a file to their own uid */
298 TEST_F(Chown, chown_to_self)
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;
308 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
309 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
310 /* The OS may optimize chown by omitting the redundant setattr */
311 EXPECT_CALL(*m_mock, process(
312 ResultOf([](auto in) {
313 return (in->header.opcode == FUSE_SETATTR);
316 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out){
317 SET_OUT_HEADER_LEN(out, attr);
318 out->body.attr.attr.mode = S_IFREG | mode;
319 out->body.attr.attr.uid = uid;
322 EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
326 * A successful chown by a non-privileged non-owner should clear a file's SUID
329 TEST_F(Chown, clear_suid)
331 const char FULLPATH[] = "mountpoint/some_file.txt";
332 const char RELPATH[] = "some_file.txt";
334 const mode_t oldmode = 06755;
335 const mode_t newmode = 0755;
336 uid_t uid = geteuid();
337 uint32_t valid = FATTR_UID | FATTR_MODE;
339 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
340 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
341 EXPECT_CALL(*m_mock, process(
342 ResultOf([=](auto in) {
343 return (in->header.opcode == FUSE_SETATTR &&
344 in->header.nodeid == ino &&
345 in->body.setattr.valid == valid &&
346 in->body.setattr.mode == newmode);
349 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
350 SET_OUT_HEADER_LEN(out, attr);
351 out->body.attr.attr.ino = ino; // Must match nodeid
352 out->body.attr.attr.mode = S_IFREG | newmode;
353 out->body.attr.attr_valid = UINT64_MAX;
356 EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
360 /* Only root may change a file's owner */
363 const char FULLPATH[] = "mountpoint/some_file.txt";
364 const char RELPATH[] = "some_file.txt";
365 const uint64_t ino = 42;
366 const mode_t mode = 0755;
368 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
369 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
370 EXPECT_CALL(*m_mock, process(
371 ResultOf([](auto in) {
372 return (in->header.opcode == FUSE_SETATTR);
377 EXPECT_NE(0, chown(FULLPATH, 0, -1));
378 EXPECT_EQ(EPERM, errno);
382 * A successful chgrp by a non-privileged non-owner should clear a file's SUID
385 TEST_F(Chgrp, clear_suid)
387 const char FULLPATH[] = "mountpoint/some_file.txt";
388 const char RELPATH[] = "some_file.txt";
390 const mode_t oldmode = 06755;
391 const mode_t newmode = 0755;
392 uid_t uid = geteuid();
393 gid_t gid = getegid();
394 uint32_t valid = FATTR_GID | FATTR_MODE;
396 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
397 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
398 EXPECT_CALL(*m_mock, process(
399 ResultOf([=](auto in) {
400 return (in->header.opcode == FUSE_SETATTR &&
401 in->header.nodeid == ino &&
402 in->body.setattr.valid == valid &&
403 in->body.setattr.mode == newmode);
406 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
407 SET_OUT_HEADER_LEN(out, attr);
408 out->body.attr.attr.ino = ino; // Must match nodeid
409 out->body.attr.attr.mode = S_IFREG | newmode;
410 out->body.attr.attr_valid = UINT64_MAX;
413 EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
416 /* non-root users may only chgrp a file to a group they belong to */
419 const char FULLPATH[] = "mountpoint/some_file.txt";
420 const char RELPATH[] = "some_file.txt";
421 const uint64_t ino = 42;
422 const mode_t mode = 0755;
428 newgid = excluded_group();
430 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
431 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
432 EXPECT_CALL(*m_mock, process(
433 ResultOf([](auto in) {
434 return (in->header.opcode == FUSE_SETATTR);
439 EXPECT_NE(0, chown(FULLPATH, -1, newgid));
440 EXPECT_EQ(EPERM, errno);
445 const char FULLPATH[] = "mountpoint/some_file.txt";
446 const char RELPATH[] = "some_file.txt";
447 const uint64_t ino = 42;
448 const mode_t mode = 0755;
456 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
457 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
458 /* The OS may optimize chgrp by omitting the redundant setattr */
459 EXPECT_CALL(*m_mock, process(
460 ResultOf([](auto in) {
461 return (in->header.opcode == FUSE_SETATTR &&
462 in->header.nodeid == ino);
465 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out){
466 SET_OUT_HEADER_LEN(out, attr);
467 out->body.attr.attr.mode = S_IFREG | mode;
468 out->body.attr.attr.uid = uid;
469 out->body.attr.attr.gid = newgid;
472 EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
477 const char FULLPATH[] = "mountpoint/some_file.txt";
478 const char RELPATH[] = "some_file.txt";
482 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
483 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
484 expect_create(RELPATH, ino);
486 fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
487 EXPECT_LE(0, fd) << strerror(errno);
488 /* Deliberately leak fd. close(2) will be tested in release.cc */
491 TEST_F(Create, eacces)
493 const char FULLPATH[] = "mountpoint/some_file.txt";
494 const char RELPATH[] = "some_file.txt";
496 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
497 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
499 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
500 EXPECT_EQ(EACCES, errno);
503 TEST_F(Deleteextattr, eacces)
505 const char FULLPATH[] = "mountpoint/some_file.txt";
506 const char RELPATH[] = "some_file.txt";
508 int ns = EXTATTR_NAMESPACE_USER;
510 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
511 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
513 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
514 ASSERT_EQ(EACCES, errno);
517 TEST_F(Deleteextattr, ok)
519 const char FULLPATH[] = "mountpoint/some_file.txt";
520 const char RELPATH[] = "some_file.txt";
522 int ns = EXTATTR_NAMESPACE_USER;
524 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
525 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
526 expect_removexattr();
528 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
532 /* Delete system attributes requires superuser privilege */
533 TEST_F(Deleteextattr, system)
535 const char FULLPATH[] = "mountpoint/some_file.txt";
536 const char RELPATH[] = "some_file.txt";
538 int ns = EXTATTR_NAMESPACE_SYSTEM;
540 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
541 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
543 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
544 ASSERT_EQ(EPERM, errno);
547 /* Deleting user attributes merely requires WRITE privilege */
548 TEST_F(Deleteextattr, user)
550 const char FULLPATH[] = "mountpoint/some_file.txt";
551 const char RELPATH[] = "some_file.txt";
553 int ns = EXTATTR_NAMESPACE_USER;
555 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
556 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
557 expect_removexattr();
559 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
563 TEST_F(Getextattr, eacces)
565 const char FULLPATH[] = "mountpoint/some_file.txt";
566 const char RELPATH[] = "some_file.txt";
569 int ns = EXTATTR_NAMESPACE_USER;
571 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
572 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
575 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
576 ASSERT_EQ(EACCES, errno);
579 TEST_F(Getextattr, ok)
581 const char FULLPATH[] = "mountpoint/some_file.txt";
582 const char RELPATH[] = "some_file.txt";
585 const char value[] = "whatever";
586 ssize_t value_len = strlen(value) + 1;
587 int ns = EXTATTR_NAMESPACE_USER;
590 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
591 /* Getting user attributes only requires read access */
592 expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
594 ReturnImmediate([&](auto in __unused, auto out) {
595 memcpy((void*)out->body.bytes, value, value_len);
596 out->header.len = sizeof(out->header) + value_len;
600 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
601 ASSERT_EQ(value_len, r) << strerror(errno);
602 EXPECT_STREQ(value, data);
605 /* Getting system attributes requires superuser privileges */
606 TEST_F(Getextattr, system)
608 const char FULLPATH[] = "mountpoint/some_file.txt";
609 const char RELPATH[] = "some_file.txt";
612 int ns = EXTATTR_NAMESPACE_SYSTEM;
614 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
615 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
618 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
619 ASSERT_EQ(EPERM, errno);
622 TEST_F(Listextattr, eacces)
624 const char FULLPATH[] = "mountpoint/some_file.txt";
625 const char RELPATH[] = "some_file.txt";
627 int ns = EXTATTR_NAMESPACE_USER;
629 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
630 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
632 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
633 ASSERT_EQ(EACCES, errno);
636 TEST_F(Listextattr, ok)
638 const char FULLPATH[] = "mountpoint/some_file.txt";
639 const char RELPATH[] = "some_file.txt";
641 int ns = EXTATTR_NAMESPACE_USER;
643 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
644 /* Listing user extended attributes merely requires read access */
645 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
648 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
652 /* Listing system xattrs requires superuser privileges */
653 TEST_F(Listextattr, system)
655 const char FULLPATH[] = "mountpoint/some_file.txt";
656 const char RELPATH[] = "some_file.txt";
658 int ns = EXTATTR_NAMESPACE_SYSTEM;
660 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
661 /* Listing user extended attributes merely requires read access */
662 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
664 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
665 ASSERT_EQ(EPERM, errno);
668 /* A component of the search path lacks execute permissions */
669 TEST_F(Lookup, eacces)
671 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
672 const char RELDIRPATH[] = "some_dir";
673 uint64_t dir_ino = 42;
675 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
676 expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
678 EXPECT_EQ(-1, access(FULLPATH, F_OK));
679 EXPECT_EQ(EACCES, errno);
684 const char FULLPATH[] = "mountpoint/some_file.txt";
685 const char RELPATH[] = "some_file.txt";
688 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
689 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
691 EXPECT_NE(0, open(FULLPATH, O_RDWR));
692 EXPECT_EQ(EACCES, errno);
697 const char FULLPATH[] = "mountpoint/some_file.txt";
698 const char RELPATH[] = "some_file.txt";
702 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
703 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
704 expect_open(ino, 0, 1);
706 fd = open(FULLPATH, O_RDONLY);
707 EXPECT_LE(0, fd) << strerror(errno);
708 /* Deliberately leak fd. close(2) will be tested in release.cc */
711 TEST_F(Rename, eacces_on_srcdir)
713 const char FULLDST[] = "mountpoint/d/dst";
714 const char RELDST[] = "d/dst";
715 const char FULLSRC[] = "mountpoint/src";
716 const char RELSRC[] = "src";
719 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
720 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
721 EXPECT_LOOKUP(1, RELDST)
723 .WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
725 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
726 ASSERT_EQ(EACCES, errno);
729 TEST_F(Rename, eacces_on_dstdir_for_creating)
731 const char FULLDST[] = "mountpoint/d/dst";
732 const char RELDSTDIR[] = "d";
733 const char RELDST[] = "dst";
734 const char FULLSRC[] = "mountpoint/src";
735 const char RELSRC[] = "src";
736 uint64_t src_ino = 42;
737 uint64_t dstdir_ino = 43;
739 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
740 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
741 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
742 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
744 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
745 ASSERT_EQ(EACCES, errno);
748 TEST_F(Rename, eacces_on_dstdir_for_removing)
750 const char FULLDST[] = "mountpoint/d/dst";
751 const char RELDSTDIR[] = "d";
752 const char RELDST[] = "dst";
753 const char FULLSRC[] = "mountpoint/src";
754 const char RELSRC[] = "src";
755 uint64_t src_ino = 42;
756 uint64_t dstdir_ino = 43;
758 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
759 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
760 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
761 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
763 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
764 ASSERT_EQ(EACCES, errno);
767 TEST_F(Rename, eperm_on_sticky_srcdir)
769 const char FULLDST[] = "mountpoint/d/dst";
770 const char FULLSRC[] = "mountpoint/src";
771 const char RELSRC[] = "src";
774 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
775 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
777 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
778 ASSERT_EQ(EPERM, errno);
781 TEST_F(Rename, eperm_on_sticky_dstdir)
783 const char FULLDST[] = "mountpoint/d/dst";
784 const char RELDSTDIR[] = "d";
785 const char RELDST[] = "dst";
786 const char FULLSRC[] = "mountpoint/src";
787 const char RELSRC[] = "src";
788 uint64_t src_ino = 42;
789 uint64_t dstdir_ino = 43;
790 uint64_t dst_ino = 44;
792 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
793 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
794 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
795 EXPECT_LOOKUP(dstdir_ino, RELDST)
796 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
797 SET_OUT_HEADER_LEN(out, entry);
798 out->body.entry.attr.mode = S_IFREG | 0644;
799 out->body.entry.nodeid = dst_ino;
800 out->body.entry.attr_valid = UINT64_MAX;
801 out->body.entry.entry_valid = UINT64_MAX;
802 out->body.entry.attr.uid = 0;
805 ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
806 ASSERT_EQ(EPERM, errno);
809 /* Successfully rename a file, overwriting the destination */
812 const char FULLDST[] = "mountpoint/dst";
813 const char RELDST[] = "dst";
814 const char FULLSRC[] = "mountpoint/src";
815 const char RELSRC[] = "src";
816 // The inode of the already-existing destination file
817 uint64_t dst_ino = 2;
820 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
821 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
822 expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
825 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
828 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
830 const char FULLDST[] = "mountpoint/dst";
831 const char RELDST[] = "dst";
832 const char FULLSRC[] = "mountpoint/src";
833 const char RELSRC[] = "src";
836 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
837 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
838 EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
841 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
846 const char FULLPATH[] = "mountpoint/some_file.txt";
847 const char RELPATH[] = "some_file.txt";
848 const uint64_t ino = 42;
849 const mode_t oldmode = 0755;
850 const mode_t newmode = 0644;
852 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
853 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
854 EXPECT_CALL(*m_mock, process(
855 ResultOf([](auto in) {
856 return (in->header.opcode == FUSE_SETATTR &&
857 in->header.nodeid == ino &&
858 in->body.setattr.mode == newmode);
861 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
862 SET_OUT_HEADER_LEN(out, attr);
863 out->body.attr.attr.mode = S_IFREG | newmode;
866 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
869 TEST_F(Setattr, eacces)
871 const char FULLPATH[] = "mountpoint/some_file.txt";
872 const char RELPATH[] = "some_file.txt";
873 const uint64_t ino = 42;
874 const mode_t oldmode = 0755;
875 const mode_t newmode = 0644;
877 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
878 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
879 EXPECT_CALL(*m_mock, process(
880 ResultOf([](auto in) {
881 return (in->header.opcode == FUSE_SETATTR);
886 EXPECT_NE(0, chmod(FULLPATH, newmode));
887 EXPECT_EQ(EPERM, errno);
891 * ftruncate() of a file without writable permissions should succeed as long as
892 * the file descriptor is writable. This is important when combined with
895 TEST_F(Setattr, ftruncate_of_newly_created_file)
897 const char FULLPATH[] = "mountpoint/some_file.txt";
898 const char RELPATH[] = "some_file.txt";
899 const uint64_t ino = 42;
900 const mode_t mode = 0000;
903 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
904 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
905 expect_create(RELPATH, ino);
906 EXPECT_CALL(*m_mock, process(
907 ResultOf([](auto in) {
908 return (in->header.opcode == FUSE_SETATTR &&
909 in->header.nodeid == ino &&
910 (in->body.setattr.valid & FATTR_SIZE));
913 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
914 SET_OUT_HEADER_LEN(out, attr);
915 out->body.attr.attr.ino = ino;
916 out->body.attr.attr.mode = S_IFREG | mode;
917 out->body.attr.attr_valid = UINT64_MAX;
920 fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
921 ASSERT_LE(0, fd) << strerror(errno);
922 ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
923 /* Deliberately leak fd */
927 * Setting the sgid bit should fail for an unprivileged user who doesn't belong
928 * to the file's group
930 TEST_F(Setattr, sgid_by_non_group_member)
932 const char FULLPATH[] = "mountpoint/some_file.txt";
933 const char RELPATH[] = "some_file.txt";
934 const uint64_t ino = 42;
935 const mode_t oldmode = 0755;
936 const mode_t newmode = 02755;
937 uid_t uid = geteuid();
938 gid_t gid = excluded_group();
940 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
941 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
942 EXPECT_CALL(*m_mock, process(
943 ResultOf([](auto in) {
944 return (in->header.opcode == FUSE_SETATTR);
949 EXPECT_NE(0, chmod(FULLPATH, newmode));
950 EXPECT_EQ(EPERM, errno);
953 /* Only the superuser may set the sticky bit on a non-directory */
954 TEST_F(Setattr, sticky_regular_file)
956 const char FULLPATH[] = "mountpoint/some_file.txt";
957 const char RELPATH[] = "some_file.txt";
958 const uint64_t ino = 42;
959 const mode_t oldmode = 0644;
960 const mode_t newmode = 01644;
962 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
963 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
964 EXPECT_CALL(*m_mock, process(
965 ResultOf([](auto in) {
966 return (in->header.opcode == FUSE_SETATTR);
971 EXPECT_NE(0, chmod(FULLPATH, newmode));
972 EXPECT_EQ(EFTYPE, errno);
975 TEST_F(Setextattr, ok)
977 const char FULLPATH[] = "mountpoint/some_file.txt";
978 const char RELPATH[] = "some_file.txt";
980 const char value[] = "whatever";
981 ssize_t value_len = strlen(value) + 1;
982 int ns = EXTATTR_NAMESPACE_USER;
985 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
986 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
989 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
990 ASSERT_EQ(value_len, r) << strerror(errno);
993 TEST_F(Setextattr, eacces)
995 const char FULLPATH[] = "mountpoint/some_file.txt";
996 const char RELPATH[] = "some_file.txt";
998 const char value[] = "whatever";
999 ssize_t value_len = strlen(value) + 1;
1000 int ns = EXTATTR_NAMESPACE_USER;
1002 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1003 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1006 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1007 ASSERT_EQ(EACCES, errno);
1010 // Setting system attributes requires superuser privileges
1011 TEST_F(Setextattr, system)
1013 const char FULLPATH[] = "mountpoint/some_file.txt";
1014 const char RELPATH[] = "some_file.txt";
1016 const char value[] = "whatever";
1017 ssize_t value_len = strlen(value) + 1;
1018 int ns = EXTATTR_NAMESPACE_SYSTEM;
1020 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1021 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1024 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1025 ASSERT_EQ(EPERM, errno);
1028 // Setting user attributes merely requires write privileges
1029 TEST_F(Setextattr, user)
1031 const char FULLPATH[] = "mountpoint/some_file.txt";
1032 const char RELPATH[] = "some_file.txt";
1034 const char value[] = "whatever";
1035 ssize_t value_len = strlen(value) + 1;
1036 int ns = EXTATTR_NAMESPACE_USER;
1039 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1040 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1043 r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1044 ASSERT_EQ(value_len, r) << strerror(errno);
1049 const char FULLPATH[] = "mountpoint/some_file.txt";
1050 const char RELPATH[] = "some_file.txt";
1053 expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
1054 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1055 expect_unlink(1, RELPATH, 0);
1057 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1061 * Ensure that a cached name doesn't cause unlink to bypass permission checks
1064 * This test should pass because lookup(9) purges the namecache entry by doing
1065 * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
1067 TEST_F(Unlink, cached_unwritable_directory)
1069 const char FULLPATH[] = "mountpoint/some_file.txt";
1070 const char RELPATH[] = "some_file.txt";
1073 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1074 EXPECT_LOOKUP(1, RELPATH)
1076 .WillRepeatedly(Invoke(
1077 ReturnImmediate([=](auto i __unused, auto out) {
1078 SET_OUT_HEADER_LEN(out, entry);
1079 out->body.entry.attr.mode = S_IFREG | 0644;
1080 out->body.entry.nodeid = ino;
1081 out->body.entry.entry_valid = UINT64_MAX;
1085 /* Fill name cache */
1086 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
1087 /* Despite cached name , unlink should fail */
1088 ASSERT_EQ(-1, unlink(FULLPATH));
1089 ASSERT_EQ(EACCES, errno);
1092 TEST_F(Unlink, unwritable_directory)
1094 const char FULLPATH[] = "mountpoint/some_file.txt";
1095 const char RELPATH[] = "some_file.txt";
1098 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1099 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1101 ASSERT_EQ(-1, unlink(FULLPATH));
1102 ASSERT_EQ(EACCES, errno);
1105 TEST_F(Unlink, sticky_directory)
1107 const char FULLPATH[] = "mountpoint/some_file.txt";
1108 const char RELPATH[] = "some_file.txt";
1111 expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
1112 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1114 ASSERT_EQ(-1, unlink(FULLPATH));
1115 ASSERT_EQ(EPERM, errno);
1118 /* A write by a non-owner should clear a file's SUID bit */
1119 TEST_F(Write, clear_suid)
1121 const char FULLPATH[] = "mountpoint/some_file.txt";
1122 const char RELPATH[] = "some_file.txt";
1125 mode_t oldmode = 04777;
1126 mode_t newmode = 0777;
1127 char wbuf[1] = {'x'};
1130 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1131 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1132 expect_open(ino, 0, 1);
1133 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1134 expect_chmod(ino, newmode);
1136 fd = open(FULLPATH, O_WRONLY);
1137 ASSERT_LE(0, fd) << strerror(errno);
1138 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1139 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1140 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1141 /* Deliberately leak fd. close(2) will be tested in release.cc */
1144 /* A write by a non-owner should clear a file's SGID bit */
1145 TEST_F(Write, clear_sgid)
1147 const char FULLPATH[] = "mountpoint/some_file.txt";
1148 const char RELPATH[] = "some_file.txt";
1151 mode_t oldmode = 02777;
1152 mode_t newmode = 0777;
1153 char wbuf[1] = {'x'};
1156 expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1157 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1158 expect_open(ino, 0, 1);
1159 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf);
1160 expect_chmod(ino, newmode);
1162 fd = open(FULLPATH, O_WRONLY);
1163 ASSERT_LE(0, fd) << strerror(errno);
1164 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1165 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1166 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1167 /* Deliberately leak fd. close(2) will be tested in release.cc */