Index: head/sys/fs/fuse/fuse_internal.c =================================================================== --- head/sys/fs/fuse/fuse_internal.c +++ head/sys/fs/fuse/fuse_internal.c @@ -158,6 +158,7 @@ return 0; } +SDT_PROBE_DEFINE0(fusefs, , internal, access_vadmin); /* Synchronously send a FUSE_ACCESS operation */ int fuse_internal_access(struct vnode *vp, @@ -210,10 +211,18 @@ va.va_gid, mode, cred, NULL); } + if (mode & VADMIN) { + /* + * The FUSE protocol doesn't have an equivalent of VADMIN, so + * it's a bug if we ever reach this point with that bit set. + */ + SDT_PROBE0(fusefs, , internal, access_vadmin); + } + if (!fsess_isimpl(mp, FUSE_ACCESS)) return 0; - if ((mode & (VWRITE | VAPPEND | VADMIN)) != 0) + if ((mode & (VWRITE | VAPPEND)) != 0) mask |= W_OK; if ((mode & VREAD) != 0) mask |= R_OK; Index: head/sys/fs/fuse/fuse_vnops.c =================================================================== --- head/sys/fs/fuse/fuse_vnops.c +++ head/sys/fs/fuse/fuse_vnops.c @@ -235,6 +235,7 @@ { struct mount *mp = vnode_mount(vp); struct fuse_data *data = fuse_get_mpdata(mp); + int default_permissions = data->dataflags & FSESS_DEFAULT_PERMISSIONS; /* * Kernel-invoked always succeeds. @@ -248,12 +249,15 @@ */ switch (ns) { case EXTATTR_NAMESPACE_SYSTEM: - if (data->dataflags & FSESS_DEFAULT_PERMISSIONS) { + if (default_permissions) { return (priv_check_cred(cred, PRIV_VFS_EXTATTR_SYSTEM)); } - /* FALLTHROUGH */ + return (0); case EXTATTR_NAMESPACE_USER: - return (fuse_internal_access(vp, accmode, td, cred)); + if (default_permissions) { + return (fuse_internal_access(vp, accmode, td, cred)); + } + return (0); default: return (EPERM); } @@ -985,6 +989,8 @@ int wantparent = flags & (LOCKPARENT | WANTPARENT); int islastcn = flags & ISLASTCN; struct mount *mp = vnode_mount(dvp); + struct fuse_data *data = fuse_get_mpdata(mp); + int default_permissions = data->dataflags & FSESS_DEFAULT_PERMISSIONS; int err = 0; int lookup_err = 0; @@ -1108,7 +1114,11 @@ if (lookup_err) { /* Entry not found */ if ((nameiop == CREATE || nameiop == RENAME) && islastcn) { - err = fuse_internal_access(dvp, VWRITE, td, cred); + if (default_permissions) + err = fuse_internal_access(dvp, VWRITE, td, + cred); + else + err = 0; if (!err) { /* * Set the SAVENAME flag to hold onto the @@ -1191,7 +1201,7 @@ &fvdat->entry_cache_timeout); if ((nameiop == DELETE || nameiop == RENAME) && - islastcn) + islastcn && default_permissions) { struct vattr dvattr; @@ -1828,7 +1838,11 @@ if (vfs_isrdonly(mp)) return EROFS; - err = fuse_internal_access(vp, accmode, td, cred); + if (checkperm) { + err = fuse_internal_access(vp, accmode, td, cred); + } else { + err = 0; + } if (err) return err; else Index: head/tests/sys/fs/fusefs/access.cc =================================================================== --- head/tests/sys/fs/fusefs/access.cc +++ head/tests/sys/fs/fusefs/access.cc @@ -31,6 +31,9 @@ */ extern "C" { +#include +#include + #include #include } @@ -42,10 +45,33 @@ class Access: public FuseTest { public: +virtual void SetUp() { + FuseTest::SetUp(); + // Clear the default FUSE_ACCESS expectation + Mock::VerifyAndClearExpectations(m_mock); +} + void expect_lookup(const char *relpath, uint64_t ino) { FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); } + +/* + * Expect tha FUSE_ACCESS will never be called for the given inode, with any + * bits in the supplied access_mask set + */ +void expect_noaccess(uint64_t ino, mode_t access_mask) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_ACCESS && + in.header.nodeid == ino && + in.body.access.mask & access_mask); + }, Eq(true)), + _) + ).Times(0); +} + }; class RofsAccess: public Access { @@ -56,6 +82,68 @@ } }; +/* + * Change the mode of a file. + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, chmod) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + const uint64_t ino = 42; + const mode_t newmode = 0644; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_lookup(RELPATH, ino); + expect_noaccess(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([](auto in) { + return (in.header.opcode == FUSE_SETATTR && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = ino; // Must match nodeid + out.body.attr.attr.mode = S_IFREG | newmode; + }))); + + EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); +} + +/* + * Create a new file + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, create) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = S_IFREG | 0755; + uint64_t ino = 42; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_noaccess(FUSE_ROOT_ID, R_OK | W_OK); + EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) + .WillOnce(Invoke(ReturnErrno(ENOENT))); + expect_noaccess(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_CREATE); + }, Eq(true)), + _) + ).WillOnce(ReturnErrno(EPERM)); + + EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); + EXPECT_EQ(EPERM, errno); +} + /* The error case of FUSE_ACCESS. */ TEST_F(Access, eaccess) { @@ -105,6 +193,33 @@ ASSERT_EQ(EROFS, errno); } + +/* + * Lookup an extended attribute + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, Getxattr) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + char data[80]; + int ns = EXTATTR_NAMESPACE_USER; + ssize_t r; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_lookup(RELPATH, ino); + expect_noaccess(ino, 0); + expect_getxattr(ino, "user.foo", ReturnErrno(ENOATTR)); + + r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); + ASSERT_EQ(-1, r); + ASSERT_EQ(ENOATTR, errno); +} + /* The successful case of FUSE_ACCESS. */ TEST_F(Access, ok) { @@ -118,4 +233,71 @@ expect_access(ino, access_mode, 0); ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); +} + +/* + * Unlink a file + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, unlink) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); + expect_noaccess(ino, 0); + expect_lookup(RELPATH, ino); + expect_unlink(1, RELPATH, EPERM); + + ASSERT_NE(0, unlink(FULLPATH)); + ASSERT_EQ(EPERM, errno); +} + +/* + * Unlink a file whose parent diretory's sticky bit is set + * + * There should never be a FUSE_ACCESS sent for this operation, except for + * search permissions on the parent directory. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=245689 + */ +TEST_F(Access, unlink_sticky_directory) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + expect_access(FUSE_ROOT_ID, X_OK, 0); + expect_noaccess(FUSE_ROOT_ID, W_OK | R_OK); + expect_noaccess(ino, 0); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_GETATTR && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) + { + SET_OUT_HEADER_LEN(out, attr); + out.body.attr.attr.ino = FUSE_ROOT_ID; + out.body.attr.attr.mode = S_IFDIR | 01777; + out.body.attr.attr.uid = 0; + out.body.attr.attr_valid = UINT64_MAX; + }))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in.header.opcode == FUSE_ACCESS && + in.header.nodeid == ino); + }, Eq(true)), + _) + ).Times(0); + expect_lookup(RELPATH, ino); + expect_unlink(FUSE_ROOT_ID, RELPATH, EPERM); + + ASSERT_EQ(-1, unlink(FULLPATH)); + ASSERT_EQ(EPERM, errno); } Index: head/tests/sys/fs/fusefs/rename.cc =================================================================== --- head/tests/sys/fs/fusefs/rename.cc +++ head/tests/sys/fs/fusefs/rename.cc @@ -53,24 +53,6 @@ FuseTest::TearDown(); } - - void expect_getattr(uint64_t ino, mode_t mode) - { - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in.header.opcode == FUSE_GETATTR && - in.header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke( - ReturnImmediate([=](auto i __unused, auto& out) { - SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid - out.body.attr.attr.mode = mode; - out.body.attr.attr_valid = UINT64_MAX; - }))); - } - }; // EINVAL, dst is subdir of src @@ -82,7 +64,6 @@ const char RELSRC[] = "src"; uint64_t src_ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, src_ino, S_IFDIR | 0755, 0, 2); EXPECT_LOOKUP(src_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); @@ -123,7 +104,6 @@ */ struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) @@ -158,7 +138,6 @@ uint64_t ino = 42; struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); /* LOOKUP returns a negative cache entry for dst */ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) @@ -196,7 +175,6 @@ tmpfd = mkstemp(tmpfile); ASSERT_LE(0, tmpfd) << strerror(errno); - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELB, b_ino, S_IFREG | 0644, 0, 2); ASSERT_NE(0, rename(tmpfile, FULLB)); @@ -215,7 +193,6 @@ uint64_t dst_dir_ino = FUSE_ROOT_ID; uint64_t ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) .WillOnce(Invoke(ReturnErrno(ENOENT))); @@ -251,7 +228,6 @@ struct stat sb; expect_lookup(RELSRC, ino, S_IFDIR | 0755, 0, 1); - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); EXPECT_LOOKUP(FUSE_ROOT_ID, RELDSTDIR) .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { SET_OUT_HEADER_LEN(out, entry); @@ -303,7 +279,6 @@ uint64_t dst_dir_ino = FUSE_ROOT_ID; uint64_t ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELSRC, ino, S_IFREG | 0644, 0, 1); expect_lookup(RELDST, dst_ino, S_IFREG | 0644, 0, 1); EXPECT_CALL(*m_mock, process( Index: head/tests/sys/fs/fusefs/rmdir.cc =================================================================== --- head/tests/sys/fs/fusefs/rmdir.cc +++ head/tests/sys/fs/fusefs/rmdir.cc @@ -42,22 +42,6 @@ class Rmdir: public FuseTest { public: -void expect_getattr(uint64_t ino, mode_t mode) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in.header.opcode == FUSE_GETATTR && - in.header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { - SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid - out.body.attr.attr.mode = mode; - out.body.attr.attr_valid = UINT64_MAX; - }))); -} - void expect_lookup(const char *relpath, uint64_t ino, int times=1) { EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) @@ -95,25 +79,34 @@ struct stat sb; sem_t sem; uint64_t ino = 42; + Sequence seq; ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); + expect_lookup(RELPATH, ino); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { + return (in.header.opcode == FUSE_RMDIR && + 0 == strcmp(RELPATH, in.body.rmdir) && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).InSequence(seq) + .WillOnce(Invoke(ReturnErrno(0))); + expect_forget(ino, 1, &sem); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { return (in.header.opcode == FUSE_GETATTR && in.header.nodeid == FUSE_ROOT_ID); }, Eq(true)), _) - ).Times(2) + ).InSequence(seq) .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid + out.body.attr.attr.ino = FUSE_ROOT_ID; out.body.attr.attr.mode = S_IFDIR | 0755; out.body.attr.attr_valid = UINT64_MAX; }))); - expect_lookup(RELPATH, ino); - expect_rmdir(FUSE_ROOT_ID, RELPATH, 0); - expect_forget(ino, 1, &sem); ASSERT_EQ(0, rmdir(FULLPATH)) << strerror(errno); EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); @@ -127,7 +120,6 @@ const char RELPATH[] = "some_dir"; uint64_t ino = 42; - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELPATH, ino); expect_rmdir(FUSE_ROOT_ID, RELPATH, ENOTEMPTY); @@ -143,7 +135,6 @@ sem_t sem; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 2); expect_rmdir(FUSE_ROOT_ID, RELPATH, 0); expect_forget(ino, 1, &sem); @@ -163,7 +154,6 @@ ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); - expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755); expect_lookup(RELPATH, ino); expect_rmdir(FUSE_ROOT_ID, RELPATH, 0); expect_forget(ino, 1, &sem); Index: head/tests/sys/fs/fusefs/unlink.cc =================================================================== --- head/tests/sys/fs/fusefs/unlink.cc +++ head/tests/sys/fs/fusefs/unlink.cc @@ -41,22 +41,6 @@ class Unlink: public FuseTest { public: -void expect_getattr(uint64_t ino, mode_t mode) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - return (in.header.opcode == FUSE_GETATTR && - in.header.nodeid == ino); - }, Eq(true)), - _) - ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { - SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid - out.body.attr.attr.mode = mode; - out.body.attr.attr_valid = UINT64_MAX; - }))); -} - void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1) { EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) @@ -89,7 +73,6 @@ struct stat sb_old, sb_new; int fd1; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH0, ino, 1, 2); expect_lookup(RELPATH1, ino, 1, 2); expect_open(ino, 0, 1); @@ -117,23 +100,32 @@ const char RELPATH[] = "some_file.txt"; struct stat sb; uint64_t ino = 42; + Sequence seq; + /* Use nlink=2 so we don't get a FUSE_FORGET */ + expect_lookup(RELPATH, ino, 1, 2); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { + return (in.header.opcode == FUSE_UNLINK && + 0 == strcmp(RELPATH, in.body.unlink) && + in.header.nodeid == FUSE_ROOT_ID); + }, Eq(true)), + _) + ).InSequence(seq) + .WillOnce(Invoke(ReturnErrno(0))); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { return (in.header.opcode == FUSE_GETATTR && in.header.nodeid == FUSE_ROOT_ID); }, Eq(true)), _) - ).Times(2) + ).InSequence(seq) .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { SET_OUT_HEADER_LEN(out, attr); - out.body.attr.attr.ino = ino; // Must match nodeid + out.body.attr.attr.ino = FUSE_ROOT_ID; out.body.attr.attr.mode = S_IFDIR | 0755; out.body.attr.attr_valid = UINT64_MAX; }))); - /* Use nlink=2 so we don't get a FUSE_FORGET */ - expect_lookup(RELPATH, ino, 1, 2); - expect_unlink(1, RELPATH, 0); ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); @@ -145,7 +137,6 @@ const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 1); expect_unlink(1, RELPATH, EPERM); @@ -162,7 +153,6 @@ const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 2, 2); expect_unlink(1, RELPATH, 0); @@ -182,7 +172,6 @@ const char RELPATH1[] = "other_file.txt"; uint64_t ino = 42; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH0, ino, 1, 2); expect_unlink(1, RELPATH0, 0); EXPECT_CALL(*m_mock, process( @@ -213,7 +202,6 @@ ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH, ino, 1); expect_unlink(1, RELPATH, 0); expect_forget(ino, 1, &sem); @@ -233,7 +221,6 @@ uint64_t ino = 42; int fd; - expect_getattr(1, S_IFDIR | 0755); expect_lookup(RELPATH0, ino, 2); expect_open(ino, 0, 1); expect_unlink(1, RELPATH0, 0); Index: head/tests/sys/fs/fusefs/utils.hh =================================================================== --- head/tests/sys/fs/fusefs/utils.hh +++ head/tests/sys/fs/fusefs/utils.hh @@ -133,6 +133,12 @@ void expect_getattr(uint64_t ino, uint64_t size); /* + * Create an expectation that FUSE_GETXATTR will be called once for the + * given inode. + */ + void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r); + + /* * Create an expectation that FUSE_LOOKUP will be called for the given * path exactly times times and cache validity period. It will respond * with inode ino, mode mode, filesize size. Index: head/tests/sys/fs/fusefs/utils.cc =================================================================== --- head/tests/sys/fs/fusefs/utils.cc +++ head/tests/sys/fs/fusefs/utils.cc @@ -273,6 +273,20 @@ }))); } +void FuseTest::expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) +{ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *a = (const char*)in.body.bytes + + sizeof(fuse_getxattr_in); + return (in.header.opcode == FUSE_GETXATTR && + in.header.nodeid == ino && + 0 == strcmp(attr, a)); + }, Eq(true)), + _) + ).WillOnce(Invoke(r)); +} + void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid) { Index: head/tests/sys/fs/fusefs/xattr.cc =================================================================== --- head/tests/sys/fs/fusefs/xattr.cc +++ head/tests/sys/fs/fusefs/xattr.cc @@ -62,20 +62,6 @@ class Xattr: public FuseTest { public: -void expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) -{ - EXPECT_CALL(*m_mock, process( - ResultOf([=](auto in) { - const char *a = (const char*)in.body.bytes + - sizeof(fuse_getxattr_in); - return (in.header.opcode == FUSE_GETXATTR && - in.header.nodeid == ino && - 0 == strcmp(attr, a)); - }, Eq(true)), - _) - ).WillOnce(Invoke(r)); -} - void expect_listxattr(uint64_t ino, uint32_t size, ProcessMockerT r, Sequence *seq = NULL) {