19821f1d3SAlan Somers /*- 29821f1d3SAlan Somers * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 39821f1d3SAlan Somers * 49821f1d3SAlan Somers * Copyright (c) 2019 The FreeBSD Foundation 59821f1d3SAlan Somers * 69821f1d3SAlan Somers * This software was developed by BFF Storage Systems, LLC under sponsorship 79821f1d3SAlan Somers * from the FreeBSD Foundation. 89821f1d3SAlan Somers * 99821f1d3SAlan Somers * Redistribution and use in source and binary forms, with or without 109821f1d3SAlan Somers * modification, are permitted provided that the following conditions 119821f1d3SAlan Somers * are met: 129821f1d3SAlan Somers * 1. Redistributions of source code must retain the above copyright 139821f1d3SAlan Somers * notice, this list of conditions and the following disclaimer. 149821f1d3SAlan Somers * 2. Redistributions in binary form must reproduce the above copyright 159821f1d3SAlan Somers * notice, this list of conditions and the following disclaimer in the 169821f1d3SAlan Somers * documentation and/or other materials provided with the distribution. 179821f1d3SAlan Somers * 189821f1d3SAlan Somers * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 199821f1d3SAlan Somers * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 209821f1d3SAlan Somers * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 219821f1d3SAlan Somers * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 229821f1d3SAlan Somers * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 239821f1d3SAlan Somers * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 249821f1d3SAlan Somers * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 259821f1d3SAlan Somers * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 269821f1d3SAlan Somers * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 279821f1d3SAlan Somers * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 289821f1d3SAlan Somers * SUCH DAMAGE. 291fa8ebfbSAlan Somers * 301fa8ebfbSAlan Somers * $FreeBSD$ 319821f1d3SAlan Somers */ 329821f1d3SAlan Somers 339821f1d3SAlan Somers /* 349821f1d3SAlan Somers * Tests for the "default_permissions" mount option. They must be in their own 359821f1d3SAlan Somers * file so they can be run as an unprivileged user 369821f1d3SAlan Somers */ 379821f1d3SAlan Somers 389821f1d3SAlan Somers extern "C" { 39ff4fbdf5SAlan Somers #include <sys/types.h> 40ff4fbdf5SAlan Somers #include <sys/extattr.h> 41ff4fbdf5SAlan Somers 429821f1d3SAlan Somers #include <fcntl.h> 43331884f2SAlan Somers #include <semaphore.h> 449821f1d3SAlan Somers #include <unistd.h> 459821f1d3SAlan Somers } 469821f1d3SAlan Somers 479821f1d3SAlan Somers #include "mockfs.hh" 489821f1d3SAlan Somers #include "utils.hh" 499821f1d3SAlan Somers 509821f1d3SAlan Somers using namespace testing; 519821f1d3SAlan Somers 529821f1d3SAlan Somers class DefaultPermissions: public FuseTest { 539821f1d3SAlan Somers 549821f1d3SAlan Somers virtual void SetUp() { 55ff4fbdf5SAlan Somers m_default_permissions = true; 569821f1d3SAlan Somers FuseTest::SetUp(); 57ff4fbdf5SAlan Somers if (HasFatalFailure() || IsSkipped()) 58ff4fbdf5SAlan Somers return; 599821f1d3SAlan Somers 609821f1d3SAlan Somers if (geteuid() == 0) { 619821f1d3SAlan Somers GTEST_SKIP() << "This test requires an unprivileged user"; 629821f1d3SAlan Somers } 63ff4fbdf5SAlan Somers 64ff4fbdf5SAlan Somers /* With -o default_permissions, FUSE_ACCESS should never be called */ 65ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 66ff4fbdf5SAlan Somers ResultOf([=](auto in) { 6729edc611SAlan Somers return (in.header.opcode == FUSE_ACCESS); 68ff4fbdf5SAlan Somers }, Eq(true)), 69ff4fbdf5SAlan Somers _) 70ff4fbdf5SAlan Somers ).Times(0); 719821f1d3SAlan Somers } 729821f1d3SAlan Somers 739821f1d3SAlan Somers public: 7418a2264eSAlan Somers void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0) 75a90e32deSAlan Somers { 76a90e32deSAlan Somers EXPECT_CALL(*m_mock, process( 77a90e32deSAlan Somers ResultOf([=](auto in) { 7829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 7929edc611SAlan Somers in.header.nodeid == ino && 8029edc611SAlan Somers in.body.setattr.valid == FATTR_MODE && 8129edc611SAlan Somers in.body.setattr.mode == mode); 82a90e32deSAlan Somers }, Eq(true)), 83a90e32deSAlan Somers _) 8429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 85a90e32deSAlan Somers SET_OUT_HEADER_LEN(out, attr); 8629edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 8729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 8829edc611SAlan Somers out.body.attr.attr.size = size; 8929edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 90a90e32deSAlan Somers }))); 91a90e32deSAlan Somers } 92a90e32deSAlan Somers 933fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino) 943fa12789SAlan Somers { 953fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 963fa12789SAlan Somers ResultOf([=](auto in) { 9729edc611SAlan Somers const char *name = (const char*)in.body.bytes + 98a4856c96SAlan Somers sizeof(fuse_create_in); 9929edc611SAlan Somers return (in.header.opcode == FUSE_CREATE && 1003fa12789SAlan Somers (0 == strcmp(relpath, name))); 1013fa12789SAlan Somers }, Eq(true)), 1023fa12789SAlan Somers _) 10329edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 1043fa12789SAlan Somers SET_OUT_HEADER_LEN(out, create); 10529edc611SAlan Somers out.body.create.entry.attr.mode = S_IFREG | 0644; 10629edc611SAlan Somers out.body.create.entry.nodeid = ino; 10729edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 10829edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 1093fa12789SAlan Somers }))); 1103fa12789SAlan Somers } 1113fa12789SAlan Somers 11292bbfe1fSAlan Somers void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out, 11392bbfe1fSAlan Somers uint64_t off_out, uint64_t len) 11492bbfe1fSAlan Somers { 11592bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 11692bbfe1fSAlan Somers ResultOf([=](auto in) { 11792bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 11892bbfe1fSAlan Somers in.header.nodeid == ino_in && 11992bbfe1fSAlan Somers in.body.copy_file_range.off_in == off_in && 12092bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino_out && 12192bbfe1fSAlan Somers in.body.copy_file_range.off_out == off_out && 12292bbfe1fSAlan Somers in.body.copy_file_range.len == len); 12392bbfe1fSAlan Somers }, Eq(true)), 12492bbfe1fSAlan Somers _) 12592bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 12692bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 12792bbfe1fSAlan Somers out.body.write.size = len; 12892bbfe1fSAlan Somers }))); 12992bbfe1fSAlan Somers } 13092bbfe1fSAlan Somers 131ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times, 132474ba6faSAlan Somers uid_t uid = 0, gid_t gid = 0) 1339821f1d3SAlan Somers { 134ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 135ff4fbdf5SAlan Somers ResultOf([=](auto in) { 13629edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR && 13729edc611SAlan Somers in.header.nodeid == ino); 138ff4fbdf5SAlan Somers }, Eq(true)), 139ff4fbdf5SAlan Somers _) 140ff4fbdf5SAlan Somers ).Times(times) 14129edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 142ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 14329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 14429edc611SAlan Somers out.body.attr.attr.mode = mode; 14529edc611SAlan Somers out.body.attr.attr.size = 0; 14629edc611SAlan Somers out.body.attr.attr.uid = uid; 147a22a7807SAlan Somers out.body.attr.attr.gid = gid; 14829edc611SAlan Somers out.body.attr.attr_valid = attr_valid; 149ff4fbdf5SAlan Somers }))); 150ff4fbdf5SAlan Somers } 151ff4fbdf5SAlan Somers 152ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 153474ba6faSAlan Somers uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0) 154ff4fbdf5SAlan Somers { 155474ba6faSAlan Somers FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid); 1569821f1d3SAlan Somers } 1579821f1d3SAlan Somers 1589821f1d3SAlan Somers }; 1599821f1d3SAlan Somers 1609821f1d3SAlan Somers class Access: public DefaultPermissions {}; 161474ba6faSAlan Somers class Chown: public DefaultPermissions {}; 162474ba6faSAlan Somers class Chgrp: public DefaultPermissions {}; 16392bbfe1fSAlan Somers class CopyFileRange: public DefaultPermissions {}; 164ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1659821f1d3SAlan Somers class Open: public DefaultPermissions {}; 166*398c88c7SAlan Somers class PosixFallocate: public DefaultPermissions {}; 167ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 168ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 169d943c93eSAlan Somers class Utimensat: public DefaultPermissions {}; 170a90e32deSAlan Somers class Write: public DefaultPermissions {}; 1719821f1d3SAlan Somers 172ff4fbdf5SAlan Somers /* 173ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 174ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 175ff4fbdf5SAlan Somers * VOP_LOOKUP) 176ff4fbdf5SAlan Somers */ 1773fa12789SAlan Somers class Create: public DefaultPermissions {}; 178ff4fbdf5SAlan Somers 179ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 180ff4fbdf5SAlan Somers public: 181ff4fbdf5SAlan Somers void expect_removexattr() 182ff4fbdf5SAlan Somers { 183ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 184ff4fbdf5SAlan Somers ResultOf([=](auto in) { 18529edc611SAlan Somers return (in.header.opcode == FUSE_REMOVEXATTR); 186ff4fbdf5SAlan Somers }, Eq(true)), 187ff4fbdf5SAlan Somers _) 188ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 189ff4fbdf5SAlan Somers } 190ff4fbdf5SAlan Somers }; 191ff4fbdf5SAlan Somers 192ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 193ff4fbdf5SAlan Somers public: 194ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 195ff4fbdf5SAlan Somers { 196ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 197ff4fbdf5SAlan Somers ResultOf([=](auto in) { 19829edc611SAlan Somers return (in.header.opcode == FUSE_GETXATTR); 199ff4fbdf5SAlan Somers }, Eq(true)), 200ff4fbdf5SAlan Somers _) 201ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 202ff4fbdf5SAlan Somers } 203ff4fbdf5SAlan Somers }; 204ff4fbdf5SAlan Somers 205ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 206ff4fbdf5SAlan Somers public: 207ff4fbdf5SAlan Somers void expect_listxattr() 208ff4fbdf5SAlan Somers { 209ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 210ff4fbdf5SAlan Somers ResultOf([=](auto in) { 21129edc611SAlan Somers return (in.header.opcode == FUSE_LISTXATTR); 212ff4fbdf5SAlan Somers }, Eq(true)), 213ff4fbdf5SAlan Somers _) 21429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 21529edc611SAlan Somers out.body.listxattr.size = 0; 216ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 217ff4fbdf5SAlan Somers }))); 218ff4fbdf5SAlan Somers } 219ff4fbdf5SAlan Somers }; 220ff4fbdf5SAlan Somers 221ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 222ff4fbdf5SAlan Somers public: 223ff4fbdf5SAlan Somers /* 224ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 225ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 226ff4fbdf5SAlan Somers */ 227ff4fbdf5SAlan Somers void expect_rename(int error) 228ff4fbdf5SAlan Somers { 229ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 230ff4fbdf5SAlan Somers ResultOf([=](auto in) { 23129edc611SAlan Somers return (in.header.opcode == FUSE_RENAME); 232ff4fbdf5SAlan Somers }, Eq(true)), 233ff4fbdf5SAlan Somers _) 234ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 235ff4fbdf5SAlan Somers } 236ff4fbdf5SAlan Somers }; 237ff4fbdf5SAlan Somers 238ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 239ff4fbdf5SAlan Somers public: 240ff4fbdf5SAlan Somers void expect_setxattr(int error) 241ff4fbdf5SAlan Somers { 242ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 243ff4fbdf5SAlan Somers ResultOf([=](auto in) { 24429edc611SAlan Somers return (in.header.opcode == FUSE_SETXATTR); 245ff4fbdf5SAlan Somers }, Eq(true)), 246ff4fbdf5SAlan Somers _) 247ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 248ff4fbdf5SAlan Somers } 249ff4fbdf5SAlan Somers }; 250ff4fbdf5SAlan Somers 2518cfb4431SAlan Somers /* Return a group to which this user does not belong */ 2528cfb4431SAlan Somers static gid_t excluded_group() 2538cfb4431SAlan Somers { 2548cfb4431SAlan Somers int i, ngroups = 64; 2558cfb4431SAlan Somers gid_t newgid, groups[ngroups]; 2568cfb4431SAlan Somers 2578cfb4431SAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 2585a0b9a27SAlan Somers for (newgid = 0; ; newgid++) { 2598cfb4431SAlan Somers bool belongs = false; 2608cfb4431SAlan Somers 2618cfb4431SAlan Somers for (i = 0; i < ngroups; i++) { 2628cfb4431SAlan Somers if (groups[i] == newgid) 2638cfb4431SAlan Somers belongs = true; 2648cfb4431SAlan Somers } 2658cfb4431SAlan Somers if (!belongs) 2668cfb4431SAlan Somers break; 2678cfb4431SAlan Somers } 2688cfb4431SAlan Somers /* newgid is now a group to which the current user does not belong */ 2698cfb4431SAlan Somers return newgid; 2708cfb4431SAlan Somers } 2718cfb4431SAlan Somers 272ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2739821f1d3SAlan Somers { 2749821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2759821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2769821f1d3SAlan Somers uint64_t ino = 42; 2779821f1d3SAlan Somers mode_t access_mode = X_OK; 2789821f1d3SAlan Somers 279a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 280ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 281ff4fbdf5SAlan Somers 282ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 283ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 284ff4fbdf5SAlan Somers } 285ff4fbdf5SAlan Somers 286ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 287ff4fbdf5SAlan Somers { 288ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 289ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 290ff4fbdf5SAlan Somers uint64_t ino = 42; 291ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 292ff4fbdf5SAlan Somers 293a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1); 294ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 295ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2969821f1d3SAlan Somers /* 2979821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2989821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 2999821f1d3SAlan Somers * a FUSE_ACCESS 3009821f1d3SAlan Somers */ 3019821f1d3SAlan Somers 3029821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 3039821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 3049821f1d3SAlan Somers } 3059821f1d3SAlan Somers 306ff4fbdf5SAlan Somers TEST_F(Access, ok) 3079821f1d3SAlan Somers { 3089821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3099821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 3109821f1d3SAlan Somers uint64_t ino = 42; 3119821f1d3SAlan Somers mode_t access_mode = R_OK; 3129821f1d3SAlan Somers 313a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 314ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 3159821f1d3SAlan Somers /* 3169821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 31791ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 3189821f1d3SAlan Somers */ 3199821f1d3SAlan Somers 3209821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 3219821f1d3SAlan Somers } 3229821f1d3SAlan Somers 3234e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */ 3244e83d655SAlan Somers TEST_F(Chown, chown_to_self) 3254e83d655SAlan Somers { 3264e83d655SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3274e83d655SAlan Somers const char RELPATH[] = "some_file.txt"; 3284e83d655SAlan Somers const uint64_t ino = 42; 3294e83d655SAlan Somers const mode_t mode = 0755; 3304e83d655SAlan Somers uid_t uid; 3314e83d655SAlan Somers 3324e83d655SAlan Somers uid = geteuid(); 3334e83d655SAlan Somers 334a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 3354e83d655SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid); 3364e83d655SAlan Somers /* The OS may optimize chown by omitting the redundant setattr */ 3374e83d655SAlan Somers EXPECT_CALL(*m_mock, process( 3384e83d655SAlan Somers ResultOf([](auto in) { 33929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 3404e83d655SAlan Somers }, Eq(true)), 3414e83d655SAlan Somers _) 34229edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 3434e83d655SAlan Somers SET_OUT_HEADER_LEN(out, attr); 34429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 34529edc611SAlan Somers out.body.attr.attr.uid = uid; 3464e83d655SAlan Somers }))); 3474e83d655SAlan Somers 3484e83d655SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 3494e83d655SAlan Somers } 3504e83d655SAlan Somers 351a2bdd737SAlan Somers /* 352a2bdd737SAlan Somers * A successful chown by a non-privileged non-owner should clear a file's SUID 353a2bdd737SAlan Somers * bit 354a2bdd737SAlan Somers */ 355a2bdd737SAlan Somers TEST_F(Chown, clear_suid) 356a2bdd737SAlan Somers { 357a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 358a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 359a2bdd737SAlan Somers uint64_t ino = 42; 360a2bdd737SAlan Somers const mode_t oldmode = 06755; 361a2bdd737SAlan Somers const mode_t newmode = 0755; 362a2bdd737SAlan Somers uid_t uid = geteuid(); 363a2bdd737SAlan Somers uint32_t valid = FATTR_UID | FATTR_MODE; 364a2bdd737SAlan Somers 365a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 366a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid); 367a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 368a2bdd737SAlan Somers ResultOf([=](auto in) { 36929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 37029edc611SAlan Somers in.header.nodeid == ino && 37129edc611SAlan Somers in.body.setattr.valid == valid && 37229edc611SAlan Somers in.body.setattr.mode == newmode); 373a2bdd737SAlan Somers }, Eq(true)), 374a2bdd737SAlan Somers _) 37529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 376a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 37729edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 37829edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 37929edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 380a2bdd737SAlan Somers }))); 381a2bdd737SAlan Somers 382a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 383a2bdd737SAlan Somers } 384a2bdd737SAlan Somers 385a2bdd737SAlan Somers 386474ba6faSAlan Somers /* Only root may change a file's owner */ 387474ba6faSAlan Somers TEST_F(Chown, eperm) 388474ba6faSAlan Somers { 389474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 390474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 391474ba6faSAlan Somers const uint64_t ino = 42; 392474ba6faSAlan Somers const mode_t mode = 0755; 393474ba6faSAlan Somers 394a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 395474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 396474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 397474ba6faSAlan Somers ResultOf([](auto in) { 39829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 399474ba6faSAlan Somers }, Eq(true)), 400474ba6faSAlan Somers _) 401474ba6faSAlan Somers ).Times(0); 402474ba6faSAlan Somers 403474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 404474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 405474ba6faSAlan Somers } 406474ba6faSAlan Somers 407a2bdd737SAlan Somers /* 408a2bdd737SAlan Somers * A successful chgrp by a non-privileged non-owner should clear a file's SUID 409a2bdd737SAlan Somers * bit 410a2bdd737SAlan Somers */ 411a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid) 412a2bdd737SAlan Somers { 413a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 414a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 415a2bdd737SAlan Somers uint64_t ino = 42; 416a2bdd737SAlan Somers const mode_t oldmode = 06755; 417a2bdd737SAlan Somers const mode_t newmode = 0755; 418a2bdd737SAlan Somers uid_t uid = geteuid(); 419a2bdd737SAlan Somers gid_t gid = getegid(); 420a2bdd737SAlan Somers uint32_t valid = FATTR_GID | FATTR_MODE; 421a2bdd737SAlan Somers 422a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 423a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 424a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 425a2bdd737SAlan Somers ResultOf([=](auto in) { 42629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 42729edc611SAlan Somers in.header.nodeid == ino && 42829edc611SAlan Somers in.body.setattr.valid == valid && 42929edc611SAlan Somers in.body.setattr.mode == newmode); 430a2bdd737SAlan Somers }, Eq(true)), 431a2bdd737SAlan Somers _) 43229edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 433a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 43429edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 43529edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 43629edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 437a2bdd737SAlan Somers }))); 438a2bdd737SAlan Somers 439a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno); 440a2bdd737SAlan Somers } 441a2bdd737SAlan Somers 442474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 443474ba6faSAlan Somers TEST_F(Chgrp, eperm) 444474ba6faSAlan Somers { 445474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 446474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 447474ba6faSAlan Somers const uint64_t ino = 42; 448474ba6faSAlan Somers const mode_t mode = 0755; 449474ba6faSAlan Somers uid_t uid; 450474ba6faSAlan Somers gid_t gid, newgid; 451474ba6faSAlan Somers 452474ba6faSAlan Somers uid = geteuid(); 453474ba6faSAlan Somers gid = getegid(); 4548cfb4431SAlan Somers newgid = excluded_group(); 455474ba6faSAlan Somers 456a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 457474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 458474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 459474ba6faSAlan Somers ResultOf([](auto in) { 46029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 461474ba6faSAlan Somers }, Eq(true)), 462474ba6faSAlan Somers _) 463474ba6faSAlan Somers ).Times(0); 464474ba6faSAlan Somers 465474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 466474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 467474ba6faSAlan Somers } 468474ba6faSAlan Somers 469474ba6faSAlan Somers TEST_F(Chgrp, ok) 470474ba6faSAlan Somers { 471474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 472474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 473474ba6faSAlan Somers const uint64_t ino = 42; 474474ba6faSAlan Somers const mode_t mode = 0755; 475474ba6faSAlan Somers uid_t uid; 476474ba6faSAlan Somers gid_t gid, newgid; 477474ba6faSAlan Somers 478474ba6faSAlan Somers uid = geteuid(); 479474ba6faSAlan Somers gid = 0; 480474ba6faSAlan Somers newgid = getegid(); 481474ba6faSAlan Somers 482a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 483474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 4844e83d655SAlan Somers /* The OS may optimize chgrp by omitting the redundant setattr */ 485474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 486474ba6faSAlan Somers ResultOf([](auto in) { 48729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 48829edc611SAlan Somers in.header.nodeid == ino); 489474ba6faSAlan Somers }, Eq(true)), 490474ba6faSAlan Somers _) 49129edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 492474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 49329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 49429edc611SAlan Somers out.body.attr.attr.uid = uid; 49529edc611SAlan Somers out.body.attr.attr.gid = newgid; 496474ba6faSAlan Somers }))); 497474ba6faSAlan Somers 498474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 499474ba6faSAlan Somers } 500474ba6faSAlan Somers 50192bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 502*398c88c7SAlan Somers TEST_F(CopyFileRange, clear_sgid) 50392bbfe1fSAlan Somers { 50492bbfe1fSAlan Somers const char FULLPATH_IN[] = "mountpoint/in.txt"; 50592bbfe1fSAlan Somers const char RELPATH_IN[] = "in.txt"; 50692bbfe1fSAlan Somers const char FULLPATH_OUT[] = "mountpoint/out.txt"; 50792bbfe1fSAlan Somers const char RELPATH_OUT[] = "out.txt"; 50892bbfe1fSAlan Somers struct stat sb; 50992bbfe1fSAlan Somers uint64_t ino_in = 42; 51092bbfe1fSAlan Somers uint64_t ino_out = 43; 51192bbfe1fSAlan Somers mode_t oldmode = 02777; 51292bbfe1fSAlan Somers mode_t newmode = 0777; 51392bbfe1fSAlan Somers off_t fsize = 16; 51492bbfe1fSAlan Somers off_t off_in = 0; 51592bbfe1fSAlan Somers off_t off_out = 8; 51692bbfe1fSAlan Somers off_t len = 8; 51792bbfe1fSAlan Somers int fd_in, fd_out; 51892bbfe1fSAlan Somers 51992bbfe1fSAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 52092bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 52192bbfe1fSAlan Somers UINT64_MAX, 0, 0); 52292bbfe1fSAlan Somers expect_open(ino_in, 0, 1); 52392bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 52492bbfe1fSAlan Somers 1, UINT64_MAX, 0, 0); 52592bbfe1fSAlan Somers expect_open(ino_out, 0, 1); 52692bbfe1fSAlan Somers expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 52792bbfe1fSAlan Somers expect_chmod(ino_out, newmode, fsize); 52892bbfe1fSAlan Somers 52992bbfe1fSAlan Somers fd_in = open(FULLPATH_IN, O_RDONLY); 53092bbfe1fSAlan Somers ASSERT_LE(0, fd_in) << strerror(errno); 53192bbfe1fSAlan Somers fd_out = open(FULLPATH_OUT, O_WRONLY); 53292bbfe1fSAlan Somers ASSERT_LE(0, fd_out) << strerror(errno); 53392bbfe1fSAlan Somers ASSERT_EQ(len, 53492bbfe1fSAlan Somers copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 53592bbfe1fSAlan Somers << strerror(errno); 53692bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 53792bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 53892bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 53992bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 54092bbfe1fSAlan Somers 54192bbfe1fSAlan Somers leak(fd_in); 54292bbfe1fSAlan Somers leak(fd_out); 54392bbfe1fSAlan Somers } 54492bbfe1fSAlan Somers 54592bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 54692bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_suid) 54792bbfe1fSAlan Somers { 54892bbfe1fSAlan Somers const char FULLPATH_IN[] = "mountpoint/in.txt"; 54992bbfe1fSAlan Somers const char RELPATH_IN[] = "in.txt"; 55092bbfe1fSAlan Somers const char FULLPATH_OUT[] = "mountpoint/out.txt"; 55192bbfe1fSAlan Somers const char RELPATH_OUT[] = "out.txt"; 55292bbfe1fSAlan Somers struct stat sb; 55392bbfe1fSAlan Somers uint64_t ino_in = 42; 55492bbfe1fSAlan Somers uint64_t ino_out = 43; 55592bbfe1fSAlan Somers mode_t oldmode = 04777; 55692bbfe1fSAlan Somers mode_t newmode = 0777; 55792bbfe1fSAlan Somers off_t fsize = 16; 55892bbfe1fSAlan Somers off_t off_in = 0; 55992bbfe1fSAlan Somers off_t off_out = 8; 56092bbfe1fSAlan Somers off_t len = 8; 56192bbfe1fSAlan Somers int fd_in, fd_out; 56292bbfe1fSAlan Somers 56392bbfe1fSAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 56492bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 56592bbfe1fSAlan Somers UINT64_MAX, 0, 0); 56692bbfe1fSAlan Somers expect_open(ino_in, 0, 1); 56792bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 56892bbfe1fSAlan Somers 1, UINT64_MAX, 0, 0); 56992bbfe1fSAlan Somers expect_open(ino_out, 0, 1); 57092bbfe1fSAlan Somers expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 57192bbfe1fSAlan Somers expect_chmod(ino_out, newmode, fsize); 57292bbfe1fSAlan Somers 57392bbfe1fSAlan Somers fd_in = open(FULLPATH_IN, O_RDONLY); 57492bbfe1fSAlan Somers ASSERT_LE(0, fd_in) << strerror(errno); 57592bbfe1fSAlan Somers fd_out = open(FULLPATH_OUT, O_WRONLY); 57692bbfe1fSAlan Somers ASSERT_LE(0, fd_out) << strerror(errno); 57792bbfe1fSAlan Somers ASSERT_EQ(len, 57892bbfe1fSAlan Somers copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 57992bbfe1fSAlan Somers << strerror(errno); 58092bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 58192bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 58292bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 58392bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 58492bbfe1fSAlan Somers 58592bbfe1fSAlan Somers leak(fd_in); 58692bbfe1fSAlan Somers leak(fd_out); 58792bbfe1fSAlan Somers } 58892bbfe1fSAlan Somers 589ff4fbdf5SAlan Somers TEST_F(Create, ok) 590ff4fbdf5SAlan Somers { 591ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 592ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 593ff4fbdf5SAlan Somers uint64_t ino = 42; 594ff4fbdf5SAlan Somers int fd; 595ff4fbdf5SAlan Somers 596a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 597a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 598a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 599ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 600ff4fbdf5SAlan Somers 601ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 602d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 6037fc0921dSAlan Somers leak(fd); 604ff4fbdf5SAlan Somers } 605ff4fbdf5SAlan Somers 606ff4fbdf5SAlan Somers TEST_F(Create, eacces) 607ff4fbdf5SAlan Somers { 608ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 609ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 610ff4fbdf5SAlan Somers 611a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 612a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 613a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 614ff4fbdf5SAlan Somers 6158e765737SAlan Somers ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 616ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 617ff4fbdf5SAlan Somers } 618ff4fbdf5SAlan Somers 619ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 620ff4fbdf5SAlan Somers { 621ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 622ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 623ff4fbdf5SAlan Somers uint64_t ino = 42; 624ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 625ff4fbdf5SAlan Somers 626a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 627ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 628ff4fbdf5SAlan Somers 629ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 630ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 631ff4fbdf5SAlan Somers } 632ff4fbdf5SAlan Somers 633ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 634ff4fbdf5SAlan Somers { 635ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 636ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 637ff4fbdf5SAlan Somers uint64_t ino = 42; 638ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 639ff4fbdf5SAlan Somers 640a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 641ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 642ff4fbdf5SAlan Somers expect_removexattr(); 643ff4fbdf5SAlan Somers 644ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 645ff4fbdf5SAlan Somers << strerror(errno); 646ff4fbdf5SAlan Somers } 647ff4fbdf5SAlan Somers 648ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 649ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 650ff4fbdf5SAlan Somers { 651ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 652ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 653ff4fbdf5SAlan Somers uint64_t ino = 42; 654ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 655ff4fbdf5SAlan Somers 656a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 657ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 658ff4fbdf5SAlan Somers 659ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 660ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 661ff4fbdf5SAlan Somers } 662ff4fbdf5SAlan Somers 663d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */ 664d943c93eSAlan Somers TEST_F(Utimensat, utime_now) 665d943c93eSAlan Somers { 666d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 667d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 668d943c93eSAlan Somers const uint64_t ino = 42; 669d943c93eSAlan Somers /* Write permissions for everybody */ 670d943c93eSAlan Somers const mode_t mode = 0666; 671d943c93eSAlan Somers uid_t owner = 0; 672d943c93eSAlan Somers const timespec times[2] = { 673d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 674d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 675d943c93eSAlan Somers }; 676d943c93eSAlan Somers 677a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 678d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 679d943c93eSAlan Somers EXPECT_CALL(*m_mock, process( 680d943c93eSAlan Somers ResultOf([](auto in) { 68129edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 68229edc611SAlan Somers in.header.nodeid == ino && 68329edc611SAlan Somers in.body.setattr.valid & FATTR_ATIME && 68429edc611SAlan Somers in.body.setattr.valid & FATTR_MTIME); 685d943c93eSAlan Somers }, Eq(true)), 686d943c93eSAlan Somers _) 68729edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 688d943c93eSAlan Somers SET_OUT_HEADER_LEN(out, attr); 68929edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 690d943c93eSAlan Somers }))); 691d943c93eSAlan Somers 692d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 693d943c93eSAlan Somers << strerror(errno); 694d943c93eSAlan Somers } 695d943c93eSAlan Somers 696d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */ 697d943c93eSAlan Somers TEST_F(Utimensat, utime_omit) 698d943c93eSAlan Somers { 699d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 700d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 701d943c93eSAlan Somers const uint64_t ino = 42; 702d943c93eSAlan Somers /* Write permissions for no one */ 703d943c93eSAlan Somers const mode_t mode = 0444; 704d943c93eSAlan Somers uid_t owner = 0; 705d943c93eSAlan Somers const timespec times[2] = { 706d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 707d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 708d943c93eSAlan Somers }; 709d943c93eSAlan Somers 710a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 711d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 712d943c93eSAlan Somers 713d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 714d943c93eSAlan Somers << strerror(errno); 715d943c93eSAlan Somers } 716d943c93eSAlan Somers 717ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 718ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 719ff4fbdf5SAlan Somers { 720ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 721ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 722ff4fbdf5SAlan Somers uint64_t ino = 42; 723ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 724ff4fbdf5SAlan Somers 725a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 726ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 727ff4fbdf5SAlan Somers expect_removexattr(); 728ff4fbdf5SAlan Somers 729ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 730ff4fbdf5SAlan Somers << strerror(errno); 731ff4fbdf5SAlan Somers } 732ff4fbdf5SAlan Somers 733ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 734ff4fbdf5SAlan Somers { 735ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 736ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 737ff4fbdf5SAlan Somers uint64_t ino = 42; 738ff4fbdf5SAlan Somers char data[80]; 739ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 740ff4fbdf5SAlan Somers 741a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 742ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 743ff4fbdf5SAlan Somers 744ff4fbdf5SAlan Somers ASSERT_EQ(-1, 745ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 746ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 747ff4fbdf5SAlan Somers } 748ff4fbdf5SAlan Somers 749ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 750ff4fbdf5SAlan Somers { 751ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 752ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 753ff4fbdf5SAlan Somers uint64_t ino = 42; 754ff4fbdf5SAlan Somers char data[80]; 755ff4fbdf5SAlan Somers const char value[] = "whatever"; 756ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 757ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 758ff4fbdf5SAlan Somers ssize_t r; 759ff4fbdf5SAlan Somers 760a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 761ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 762ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 763ff4fbdf5SAlan Somers expect_getxattr( 76429edc611SAlan Somers ReturnImmediate([&](auto in __unused, auto& out) { 76529edc611SAlan Somers memcpy((void*)out.body.bytes, value, value_len); 76629edc611SAlan Somers out.header.len = sizeof(out.header) + value_len; 767ff4fbdf5SAlan Somers }) 768ff4fbdf5SAlan Somers ); 769ff4fbdf5SAlan Somers 770ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 771ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 772ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 773ff4fbdf5SAlan Somers } 774ff4fbdf5SAlan Somers 775ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 776ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 777ff4fbdf5SAlan Somers { 778ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 779ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 780ff4fbdf5SAlan Somers uint64_t ino = 42; 781ff4fbdf5SAlan Somers char data[80]; 782ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 783ff4fbdf5SAlan Somers 784a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 785ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 786ff4fbdf5SAlan Somers 787ff4fbdf5SAlan Somers ASSERT_EQ(-1, 788ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 789ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 790ff4fbdf5SAlan Somers } 791ff4fbdf5SAlan Somers 792ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 793ff4fbdf5SAlan Somers { 794ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 795ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 796ff4fbdf5SAlan Somers uint64_t ino = 42; 797ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 798ff4fbdf5SAlan Somers 799a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 800ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 801ff4fbdf5SAlan Somers 802ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 803ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 804ff4fbdf5SAlan Somers } 805ff4fbdf5SAlan Somers 806ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 807ff4fbdf5SAlan Somers { 808ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 809ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 810ff4fbdf5SAlan Somers uint64_t ino = 42; 811ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 812ff4fbdf5SAlan Somers 813a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 814ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 815ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 816ff4fbdf5SAlan Somers expect_listxattr(); 817ff4fbdf5SAlan Somers 818ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 819ff4fbdf5SAlan Somers << strerror(errno); 820ff4fbdf5SAlan Somers } 821ff4fbdf5SAlan Somers 822ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 823ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 824ff4fbdf5SAlan Somers { 825ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 826ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 827ff4fbdf5SAlan Somers uint64_t ino = 42; 828ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 829ff4fbdf5SAlan Somers 830a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 831ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 832ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 833ff4fbdf5SAlan Somers 834ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 835ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 836ff4fbdf5SAlan Somers } 837ff4fbdf5SAlan Somers 838ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 839ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 840ff4fbdf5SAlan Somers { 841ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 842ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 843ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 844ff4fbdf5SAlan Somers 845a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 846ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 847ff4fbdf5SAlan Somers 848ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 849ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 850ff4fbdf5SAlan Somers } 851ff4fbdf5SAlan Somers 852ff4fbdf5SAlan Somers TEST_F(Open, eacces) 853ff4fbdf5SAlan Somers { 854ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 855ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 856ff4fbdf5SAlan Somers uint64_t ino = 42; 857ff4fbdf5SAlan Somers 858a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 859ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 860ff4fbdf5SAlan Somers 8614ca1c0b7SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_RDWR)); 862ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 863ff4fbdf5SAlan Somers } 864ff4fbdf5SAlan Somers 8659821f1d3SAlan Somers TEST_F(Open, ok) 8669821f1d3SAlan Somers { 8679821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 8689821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 8699821f1d3SAlan Somers uint64_t ino = 42; 8709821f1d3SAlan Somers int fd; 8719821f1d3SAlan Somers 872a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 873ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 8749821f1d3SAlan Somers expect_open(ino, 0, 1); 8759821f1d3SAlan Somers 8769821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 877d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 8787fc0921dSAlan Somers leak(fd); 8799821f1d3SAlan Somers } 8809821f1d3SAlan Somers 881*398c88c7SAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 882*398c88c7SAlan Somers TEST_F(PosixFallocate, clear_sgid) 883*398c88c7SAlan Somers { 884*398c88c7SAlan Somers const char FULLPATH[] = "mountpoint/file.txt"; 885*398c88c7SAlan Somers const char RELPATH[] = "file.txt"; 886*398c88c7SAlan Somers struct stat sb; 887*398c88c7SAlan Somers uint64_t ino = 42; 888*398c88c7SAlan Somers mode_t oldmode = 02777; 889*398c88c7SAlan Somers mode_t newmode = 0777; 890*398c88c7SAlan Somers off_t fsize = 16; 891*398c88c7SAlan Somers off_t off = 8; 892*398c88c7SAlan Somers off_t len = 8; 893*398c88c7SAlan Somers int fd; 894*398c88c7SAlan Somers 895*398c88c7SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 896*398c88c7SAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 897*398c88c7SAlan Somers 1, UINT64_MAX, 0, 0); 898*398c88c7SAlan Somers expect_open(ino, 0, 1); 899*398c88c7SAlan Somers expect_fallocate(ino, off, len, 0, 0); 900*398c88c7SAlan Somers expect_chmod(ino, newmode, fsize); 901*398c88c7SAlan Somers 902*398c88c7SAlan Somers fd = open(FULLPATH, O_WRONLY); 903*398c88c7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 904*398c88c7SAlan Somers EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 905*398c88c7SAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 906*398c88c7SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 907*398c88c7SAlan Somers 908*398c88c7SAlan Somers leak(fd); 909*398c88c7SAlan Somers } 910*398c88c7SAlan Somers 911*398c88c7SAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 912*398c88c7SAlan Somers TEST_F(PosixFallocate, clear_suid) 913*398c88c7SAlan Somers { 914*398c88c7SAlan Somers const char FULLPATH[] = "mountpoint/file.txt"; 915*398c88c7SAlan Somers const char RELPATH[] = "file.txt"; 916*398c88c7SAlan Somers struct stat sb; 917*398c88c7SAlan Somers uint64_t ino = 42; 918*398c88c7SAlan Somers mode_t oldmode = 04777; 919*398c88c7SAlan Somers mode_t newmode = 0777; 920*398c88c7SAlan Somers off_t fsize = 16; 921*398c88c7SAlan Somers off_t off = 8; 922*398c88c7SAlan Somers off_t len = 8; 923*398c88c7SAlan Somers int fd; 924*398c88c7SAlan Somers 925*398c88c7SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 926*398c88c7SAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 927*398c88c7SAlan Somers 1, UINT64_MAX, 0, 0); 928*398c88c7SAlan Somers expect_open(ino, 0, 1); 929*398c88c7SAlan Somers expect_fallocate(ino, off, len, 0, 0); 930*398c88c7SAlan Somers expect_chmod(ino, newmode, fsize); 931*398c88c7SAlan Somers 932*398c88c7SAlan Somers fd = open(FULLPATH, O_WRONLY); 933*398c88c7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 934*398c88c7SAlan Somers EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 935*398c88c7SAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 936*398c88c7SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 937*398c88c7SAlan Somers 938*398c88c7SAlan Somers leak(fd); 939*398c88c7SAlan Somers } 940*398c88c7SAlan Somers 941*398c88c7SAlan Somers /* 942*398c88c7SAlan Somers * posix_fallcoate() of a file without writable permissions should succeed as 943*398c88c7SAlan Somers * long as the file descriptor is writable. This is important when combined 944*398c88c7SAlan Somers * with O_CREAT 945*398c88c7SAlan Somers */ 946*398c88c7SAlan Somers TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file) 947*398c88c7SAlan Somers { 948*398c88c7SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 949*398c88c7SAlan Somers const char RELPATH[] = "some_file.txt"; 950*398c88c7SAlan Somers const uint64_t ino = 42; 951*398c88c7SAlan Somers off_t off = 8; 952*398c88c7SAlan Somers off_t len = 8; 953*398c88c7SAlan Somers int fd; 954*398c88c7SAlan Somers 955*398c88c7SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 956*398c88c7SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 957*398c88c7SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 958*398c88c7SAlan Somers expect_create(RELPATH, ino); 959*398c88c7SAlan Somers expect_fallocate(ino, off, len, 0, 0); 960*398c88c7SAlan Somers 961*398c88c7SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 962*398c88c7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 963*398c88c7SAlan Somers EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 964*398c88c7SAlan Somers leak(fd); 965*398c88c7SAlan Somers } 966*398c88c7SAlan Somers 967ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 968ff4fbdf5SAlan Somers { 969ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 970ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 971ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 972ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 973ff4fbdf5SAlan Somers uint64_t ino = 42; 974ff4fbdf5SAlan Somers 975a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0); 976ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 977a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 978ff4fbdf5SAlan Somers .Times(AnyNumber()) 979ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 980ff4fbdf5SAlan Somers 981ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 982ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 983ff4fbdf5SAlan Somers } 984ff4fbdf5SAlan Somers 985ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 986ff4fbdf5SAlan Somers { 987ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 988ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 989ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 990ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 991ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 992ff4fbdf5SAlan Somers uint64_t src_ino = 42; 993ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 994ff4fbdf5SAlan Somers 995a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 996ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 997ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 998ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 999ff4fbdf5SAlan Somers 1000ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1001ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1002ff4fbdf5SAlan Somers } 1003ff4fbdf5SAlan Somers 1004ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 1005ff4fbdf5SAlan Somers { 1006ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1007ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 1008ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1009ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1010ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1011ff4fbdf5SAlan Somers uint64_t src_ino = 42; 1012ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 1013ff4fbdf5SAlan Somers 1014a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1015ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1016ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 1017ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 1018ff4fbdf5SAlan Somers 1019ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1020ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1021ff4fbdf5SAlan Somers } 1022ff4fbdf5SAlan Somers 10236124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 1024ff4fbdf5SAlan Somers { 1025ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1026ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1027ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1028ff4fbdf5SAlan Somers uint64_t ino = 42; 1029ff4fbdf5SAlan Somers 1030a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1031ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1032ff4fbdf5SAlan Somers 1033ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1034ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1035ff4fbdf5SAlan Somers } 1036ff4fbdf5SAlan Somers 10378e45ec4eSAlan Somers /* 10388e45ec4eSAlan Somers * A user cannot move out a subdirectory that he does not own, because that 10398e45ec4eSAlan Somers * would require changing the subdirectory's ".." dirent 10408e45ec4eSAlan Somers */ 10418e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory) 10428e45ec4eSAlan Somers { 10438e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 10448e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 10458e45ec4eSAlan Somers const char RELDSTDIR[] = "d"; 10468e45ec4eSAlan Somers const char RELDST[] = "dst"; 10478e45ec4eSAlan Somers const char RELSRC[] = "src"; 10488e45ec4eSAlan Somers uint64_t ino = 42; 10498e45ec4eSAlan Somers uint64_t dstdir_ino = 43; 10508e45ec4eSAlan Somers 1051a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 10528e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 10538e45ec4eSAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 10548e45ec4eSAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 10558e45ec4eSAlan Somers 10568e45ec4eSAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 10578e45ec4eSAlan Somers ASSERT_EQ(EACCES, errno); 10588e45ec4eSAlan Somers } 10598e45ec4eSAlan Somers 10608e45ec4eSAlan Somers /* 10618e45ec4eSAlan Somers * A user _can_ rename a subdirectory to which he lacks write permissions, if 10628e45ec4eSAlan Somers * it will keep the same parent 10638e45ec4eSAlan Somers */ 10648e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir) 10658e45ec4eSAlan Somers { 10668e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/dst"; 10678e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 10688e45ec4eSAlan Somers const char RELDST[] = "dst"; 10698e45ec4eSAlan Somers const char RELSRC[] = "src"; 10708e45ec4eSAlan Somers uint64_t ino = 42; 10718e45ec4eSAlan Somers 1072a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 10738e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 1074a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1075a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 10768e45ec4eSAlan Somers expect_rename(0); 10778e45ec4eSAlan Somers 10788e45ec4eSAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 10798e45ec4eSAlan Somers } 10808e45ec4eSAlan Somers 10816124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 1082ff4fbdf5SAlan Somers { 1083ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1084ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 10856124fd71SAlan Somers const char RELDST[] = "dst"; 1086ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1087ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1088ff4fbdf5SAlan Somers uint64_t src_ino = 42; 1089ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 1090ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 1091ff4fbdf5SAlan Somers 1092a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1093ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1094ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 10956124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 109629edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 10976124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 109829edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 109929edc611SAlan Somers out.body.entry.nodeid = dst_ino; 110029edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 110129edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 110229edc611SAlan Somers out.body.entry.attr.uid = 0; 11036124fd71SAlan Somers }))); 1104ff4fbdf5SAlan Somers 1105ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1106ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1107ff4fbdf5SAlan Somers } 1108ff4fbdf5SAlan Somers 1109ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 1110ff4fbdf5SAlan Somers TEST_F(Rename, ok) 1111ff4fbdf5SAlan Somers { 1112ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 1113ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1114ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1115ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1116ff4fbdf5SAlan Somers // The inode of the already-existing destination file 1117ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 1118ff4fbdf5SAlan Somers uint64_t ino = 42; 1119ff4fbdf5SAlan Somers 1120a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 1121ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1122ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 1123ff4fbdf5SAlan Somers expect_rename(0); 1124ff4fbdf5SAlan Somers 1125ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1126ff4fbdf5SAlan Somers } 1127ff4fbdf5SAlan Somers 1128ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 1129ff4fbdf5SAlan Somers { 1130ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 1131ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1132ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1133ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1134ff4fbdf5SAlan Somers uint64_t ino = 42; 1135ff4fbdf5SAlan Somers 1136a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1137ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1138a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1139a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 1140ff4fbdf5SAlan Somers expect_rename(0); 1141ff4fbdf5SAlan Somers 1142ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1143ff4fbdf5SAlan Somers } 1144ff4fbdf5SAlan Somers 1145ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 1146ff4fbdf5SAlan Somers { 1147ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1148ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1149ff4fbdf5SAlan Somers const uint64_t ino = 42; 1150ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 1151ff4fbdf5SAlan Somers const mode_t newmode = 0644; 1152ff4fbdf5SAlan Somers 1153a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1154ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1155ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 1156ff4fbdf5SAlan Somers ResultOf([](auto in) { 115729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 115829edc611SAlan Somers in.header.nodeid == ino && 115929edc611SAlan Somers in.body.setattr.mode == newmode); 1160ff4fbdf5SAlan Somers }, Eq(true)), 1161ff4fbdf5SAlan Somers _) 116229edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 1163ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 116429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 1165ff4fbdf5SAlan Somers }))); 1166ff4fbdf5SAlan Somers 1167ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 1168ff4fbdf5SAlan Somers } 1169ff4fbdf5SAlan Somers 1170ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 1171ff4fbdf5SAlan Somers { 1172ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1173ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1174ff4fbdf5SAlan Somers const uint64_t ino = 42; 1175ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 1176ff4fbdf5SAlan Somers const mode_t newmode = 0644; 1177ff4fbdf5SAlan Somers 1178a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1179ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 1180ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 1181ff4fbdf5SAlan Somers ResultOf([](auto in) { 118229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1183ff4fbdf5SAlan Somers }, Eq(true)), 1184ff4fbdf5SAlan Somers _) 1185ff4fbdf5SAlan Somers ).Times(0); 1186ff4fbdf5SAlan Somers 1187ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1188ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 1189ff4fbdf5SAlan Somers } 1190ff4fbdf5SAlan Somers 11918cfb4431SAlan Somers /* 11923fa12789SAlan Somers * ftruncate() of a file without writable permissions should succeed as long as 11933fa12789SAlan Somers * the file descriptor is writable. This is important when combined with 11943fa12789SAlan Somers * O_CREAT 11953fa12789SAlan Somers */ 11963fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file) 11973fa12789SAlan Somers { 11983fa12789SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11993fa12789SAlan Somers const char RELPATH[] = "some_file.txt"; 12003fa12789SAlan Somers const uint64_t ino = 42; 12013fa12789SAlan Somers const mode_t mode = 0000; 12023fa12789SAlan Somers int fd; 12033fa12789SAlan Somers 1204a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1205a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1206a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 12073fa12789SAlan Somers expect_create(RELPATH, ino); 12083fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 12093fa12789SAlan Somers ResultOf([](auto in) { 121029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 121129edc611SAlan Somers in.header.nodeid == ino && 121229edc611SAlan Somers (in.body.setattr.valid & FATTR_SIZE)); 12133fa12789SAlan Somers }, Eq(true)), 12143fa12789SAlan Somers _) 121529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 12163fa12789SAlan Somers SET_OUT_HEADER_LEN(out, attr); 121729edc611SAlan Somers out.body.attr.attr.ino = ino; 121829edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 121929edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 12203fa12789SAlan Somers }))); 12213fa12789SAlan Somers 12223fa12789SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 12233fa12789SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 12243fa12789SAlan Somers ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 12257fc0921dSAlan Somers leak(fd); 12263fa12789SAlan Somers } 12273fa12789SAlan Somers 12283fa12789SAlan Somers /* 12298cfb4431SAlan Somers * Setting the sgid bit should fail for an unprivileged user who doesn't belong 12308cfb4431SAlan Somers * to the file's group 12318cfb4431SAlan Somers */ 12328cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member) 12338cfb4431SAlan Somers { 12348cfb4431SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 12358cfb4431SAlan Somers const char RELPATH[] = "some_file.txt"; 12368cfb4431SAlan Somers const uint64_t ino = 42; 12378cfb4431SAlan Somers const mode_t oldmode = 0755; 12388cfb4431SAlan Somers const mode_t newmode = 02755; 12398cfb4431SAlan Somers uid_t uid = geteuid(); 12408cfb4431SAlan Somers gid_t gid = excluded_group(); 12418cfb4431SAlan Somers 1242a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 12438cfb4431SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 12448cfb4431SAlan Somers EXPECT_CALL(*m_mock, process( 12458cfb4431SAlan Somers ResultOf([](auto in) { 124629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 12478cfb4431SAlan Somers }, Eq(true)), 12488cfb4431SAlan Somers _) 12498cfb4431SAlan Somers ).Times(0); 12508cfb4431SAlan Somers 12518cfb4431SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 12528cfb4431SAlan Somers EXPECT_EQ(EPERM, errno); 12538cfb4431SAlan Somers } 12548cfb4431SAlan Somers 1255e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 1256e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 1257e5ff3a7eSAlan Somers { 1258e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1259e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 1260e5ff3a7eSAlan Somers const uint64_t ino = 42; 1261e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 1262e5ff3a7eSAlan Somers const mode_t newmode = 01644; 1263e5ff3a7eSAlan Somers 1264a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1265e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1266e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 1267e5ff3a7eSAlan Somers ResultOf([](auto in) { 126829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1269e5ff3a7eSAlan Somers }, Eq(true)), 1270e5ff3a7eSAlan Somers _) 1271e5ff3a7eSAlan Somers ).Times(0); 1272e5ff3a7eSAlan Somers 1273e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1274e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 1275e5ff3a7eSAlan Somers } 1276e5ff3a7eSAlan Somers 1277ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 1278ff4fbdf5SAlan Somers { 1279ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1280ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1281ff4fbdf5SAlan Somers uint64_t ino = 42; 1282ff4fbdf5SAlan Somers const char value[] = "whatever"; 1283ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1284ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1285ff4fbdf5SAlan Somers ssize_t r; 1286ff4fbdf5SAlan Somers 1287a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1288ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1289ff4fbdf5SAlan Somers expect_setxattr(0); 1290ff4fbdf5SAlan Somers 12915a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 12925a0b9a27SAlan Somers value_len); 1293ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1294ff4fbdf5SAlan Somers } 1295ff4fbdf5SAlan Somers 1296ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 1297ff4fbdf5SAlan Somers { 1298ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1299ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1300ff4fbdf5SAlan Somers uint64_t ino = 42; 1301ff4fbdf5SAlan Somers const char value[] = "whatever"; 1302ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1303ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1304ff4fbdf5SAlan Somers 1305a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1306ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1307ff4fbdf5SAlan Somers 13085a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 13095a0b9a27SAlan Somers value_len)); 1310ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1311ff4fbdf5SAlan Somers } 1312ff4fbdf5SAlan Somers 1313ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 1314ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 1315ff4fbdf5SAlan Somers { 1316ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1317ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1318ff4fbdf5SAlan Somers uint64_t ino = 42; 1319ff4fbdf5SAlan Somers const char value[] = "whatever"; 1320ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1321ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 1322ff4fbdf5SAlan Somers 1323a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1324ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1325ff4fbdf5SAlan Somers 13265a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 13275a0b9a27SAlan Somers value_len)); 1328ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1329ff4fbdf5SAlan Somers } 1330ff4fbdf5SAlan Somers 1331ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 1332ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 1333ff4fbdf5SAlan Somers { 1334ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1335ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1336ff4fbdf5SAlan Somers uint64_t ino = 42; 1337ff4fbdf5SAlan Somers const char value[] = "whatever"; 1338ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1339ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1340ff4fbdf5SAlan Somers ssize_t r; 1341ff4fbdf5SAlan Somers 1342a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1343ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1344ff4fbdf5SAlan Somers expect_setxattr(0); 1345ff4fbdf5SAlan Somers 13465a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 13475a0b9a27SAlan Somers value_len); 1348ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1349ff4fbdf5SAlan Somers } 1350ff4fbdf5SAlan Somers 1351ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 13529821f1d3SAlan Somers { 13539821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 13549821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 13559821f1d3SAlan Somers uint64_t ino = 42; 1356331884f2SAlan Somers sem_t sem; 1357331884f2SAlan Somers 1358331884f2SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 13599821f1d3SAlan Somers 1360a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1361ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1362a34cdd26SAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, 0); 1363331884f2SAlan Somers expect_forget(ino, 1, &sem); 13649821f1d3SAlan Somers 1365ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1366331884f2SAlan Somers 1367331884f2SAlan Somers sem_wait(&sem); 1368331884f2SAlan Somers sem_destroy(&sem); 1369ff4fbdf5SAlan Somers } 1370ff4fbdf5SAlan Somers 13716124fd71SAlan Somers /* 13726124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 13736124fd71SAlan Somers * in VOP_LOOKUP. 13746124fd71SAlan Somers * 13756124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 13766124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 13776124fd71SAlan Somers */ 13786124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 13796124fd71SAlan Somers { 13806124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 13816124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 13826124fd71SAlan Somers uint64_t ino = 42; 13836124fd71SAlan Somers 1384a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1385a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 13866124fd71SAlan Somers .Times(AnyNumber()) 13876124fd71SAlan Somers .WillRepeatedly(Invoke( 138829edc611SAlan Somers ReturnImmediate([=](auto i __unused, auto& out) { 13896124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 139029edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 139129edc611SAlan Somers out.body.entry.nodeid = ino; 139229edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 13936124fd71SAlan Somers })) 13946124fd71SAlan Somers ); 13956124fd71SAlan Somers 13966124fd71SAlan Somers /* Fill name cache */ 13976124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 13986124fd71SAlan Somers /* Despite cached name , unlink should fail */ 13996124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 14006124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 14016124fd71SAlan Somers } 14026124fd71SAlan Somers 1403ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 1404ff4fbdf5SAlan Somers { 1405ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1406ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1407ff4fbdf5SAlan Somers uint64_t ino = 42; 1408ff4fbdf5SAlan Somers 1409a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1410ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1411ff4fbdf5SAlan Somers 1412ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1413ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1414ff4fbdf5SAlan Somers } 1415ff4fbdf5SAlan Somers 14166124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 1417ff4fbdf5SAlan Somers { 1418ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1419ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1420ff4fbdf5SAlan Somers uint64_t ino = 42; 1421ff4fbdf5SAlan Somers 1422a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1); 1423ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1424ff4fbdf5SAlan Somers 1425ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1426ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 14279821f1d3SAlan Somers } 1428a90e32deSAlan Somers 1429a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1430a90e32deSAlan Somers TEST_F(Write, clear_suid) 1431a90e32deSAlan Somers { 1432a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1433a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1434a90e32deSAlan Somers struct stat sb; 1435a90e32deSAlan Somers uint64_t ino = 42; 1436a90e32deSAlan Somers mode_t oldmode = 04777; 1437a90e32deSAlan Somers mode_t newmode = 0777; 1438a90e32deSAlan Somers char wbuf[1] = {'x'}; 1439a90e32deSAlan Somers int fd; 1440a90e32deSAlan Somers 1441a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1442a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1443a90e32deSAlan Somers expect_open(ino, 0, 1); 1444bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 144518a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1446a90e32deSAlan Somers 1447a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1448a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1449a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1450a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1451a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 14527fc0921dSAlan Somers leak(fd); 1453a90e32deSAlan Somers } 1454a90e32deSAlan Somers 1455a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 1456a90e32deSAlan Somers TEST_F(Write, clear_sgid) 1457a90e32deSAlan Somers { 1458a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1459a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1460a90e32deSAlan Somers struct stat sb; 1461a90e32deSAlan Somers uint64_t ino = 42; 1462a90e32deSAlan Somers mode_t oldmode = 02777; 1463a90e32deSAlan Somers mode_t newmode = 0777; 1464a90e32deSAlan Somers char wbuf[1] = {'x'}; 1465a90e32deSAlan Somers int fd; 1466a90e32deSAlan Somers 1467a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1468a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1469a90e32deSAlan Somers expect_open(ino, 0, 1); 1470bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 147118a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1472a90e32deSAlan Somers 1473a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1474a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1475a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1476a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1477a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 14787fc0921dSAlan Somers leak(fd); 1479a90e32deSAlan Somers } 148018a2264eSAlan Somers 148118a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic 148218a2264eSAlan Somers * 148318a2264eSAlan Somers * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 148418a2264eSAlan Somers * may panic. That happens if the FUSE_SETATTR response indicates that the 148518a2264eSAlan Somers * file's size has changed since the write. 148618a2264eSAlan Somers */ 148718a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid) 148818a2264eSAlan Somers { 148918a2264eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 149018a2264eSAlan Somers const char RELPATH[] = "some_file.txt"; 149118a2264eSAlan Somers uint64_t ino = 42; 149218a2264eSAlan Somers mode_t oldmode = 04777; 149318a2264eSAlan Somers mode_t newmode = 0777; 149418a2264eSAlan Somers char wbuf[1] = {'x'}; 149518a2264eSAlan Somers int fd; 149618a2264eSAlan Somers 1497a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 149818a2264eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 149918a2264eSAlan Somers expect_open(ino, 0, 1); 1500bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 150118a2264eSAlan Somers /* XXX Return a smaller file size than what we just wrote! */ 150218a2264eSAlan Somers expect_chmod(ino, newmode, 0); 150318a2264eSAlan Somers 150418a2264eSAlan Somers fd = open(FULLPATH, O_WRONLY); 150518a2264eSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 150618a2264eSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 15077fc0921dSAlan Somers leak(fd); 150818a2264eSAlan Somers } 1509