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 112*92bbfe1fSAlan Somers void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out, 113*92bbfe1fSAlan Somers uint64_t off_out, uint64_t len) 114*92bbfe1fSAlan Somers { 115*92bbfe1fSAlan Somers EXPECT_CALL(*m_mock, process( 116*92bbfe1fSAlan Somers ResultOf([=](auto in) { 117*92bbfe1fSAlan Somers return (in.header.opcode == FUSE_COPY_FILE_RANGE && 118*92bbfe1fSAlan Somers in.header.nodeid == ino_in && 119*92bbfe1fSAlan Somers in.body.copy_file_range.off_in == off_in && 120*92bbfe1fSAlan Somers in.body.copy_file_range.nodeid_out == ino_out && 121*92bbfe1fSAlan Somers in.body.copy_file_range.off_out == off_out && 122*92bbfe1fSAlan Somers in.body.copy_file_range.len == len); 123*92bbfe1fSAlan Somers }, Eq(true)), 124*92bbfe1fSAlan Somers _) 125*92bbfe1fSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 126*92bbfe1fSAlan Somers SET_OUT_HEADER_LEN(out, write); 127*92bbfe1fSAlan Somers out.body.write.size = len; 128*92bbfe1fSAlan Somers }))); 129*92bbfe1fSAlan Somers } 130*92bbfe1fSAlan 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 {}; 163*92bbfe1fSAlan Somers class CopyFileRange: public DefaultPermissions {}; 164ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1659821f1d3SAlan Somers class Open: public DefaultPermissions {}; 166ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 167ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 168d943c93eSAlan Somers class Utimensat: public DefaultPermissions {}; 169a90e32deSAlan Somers class Write: public DefaultPermissions {}; 1709821f1d3SAlan Somers 171ff4fbdf5SAlan Somers /* 172ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 173ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 174ff4fbdf5SAlan Somers * VOP_LOOKUP) 175ff4fbdf5SAlan Somers */ 1763fa12789SAlan Somers class Create: public DefaultPermissions {}; 177ff4fbdf5SAlan Somers 178ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 179ff4fbdf5SAlan Somers public: 180ff4fbdf5SAlan Somers void expect_removexattr() 181ff4fbdf5SAlan Somers { 182ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 183ff4fbdf5SAlan Somers ResultOf([=](auto in) { 18429edc611SAlan Somers return (in.header.opcode == FUSE_REMOVEXATTR); 185ff4fbdf5SAlan Somers }, Eq(true)), 186ff4fbdf5SAlan Somers _) 187ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 188ff4fbdf5SAlan Somers } 189ff4fbdf5SAlan Somers }; 190ff4fbdf5SAlan Somers 191ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 192ff4fbdf5SAlan Somers public: 193ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 194ff4fbdf5SAlan Somers { 195ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 196ff4fbdf5SAlan Somers ResultOf([=](auto in) { 19729edc611SAlan Somers return (in.header.opcode == FUSE_GETXATTR); 198ff4fbdf5SAlan Somers }, Eq(true)), 199ff4fbdf5SAlan Somers _) 200ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 201ff4fbdf5SAlan Somers } 202ff4fbdf5SAlan Somers }; 203ff4fbdf5SAlan Somers 204ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 205ff4fbdf5SAlan Somers public: 206ff4fbdf5SAlan Somers void expect_listxattr() 207ff4fbdf5SAlan Somers { 208ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 209ff4fbdf5SAlan Somers ResultOf([=](auto in) { 21029edc611SAlan Somers return (in.header.opcode == FUSE_LISTXATTR); 211ff4fbdf5SAlan Somers }, Eq(true)), 212ff4fbdf5SAlan Somers _) 21329edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 21429edc611SAlan Somers out.body.listxattr.size = 0; 215ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 216ff4fbdf5SAlan Somers }))); 217ff4fbdf5SAlan Somers } 218ff4fbdf5SAlan Somers }; 219ff4fbdf5SAlan Somers 220ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 221ff4fbdf5SAlan Somers public: 222ff4fbdf5SAlan Somers /* 223ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 224ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 225ff4fbdf5SAlan Somers */ 226ff4fbdf5SAlan Somers void expect_rename(int error) 227ff4fbdf5SAlan Somers { 228ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 229ff4fbdf5SAlan Somers ResultOf([=](auto in) { 23029edc611SAlan Somers return (in.header.opcode == FUSE_RENAME); 231ff4fbdf5SAlan Somers }, Eq(true)), 232ff4fbdf5SAlan Somers _) 233ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 234ff4fbdf5SAlan Somers } 235ff4fbdf5SAlan Somers }; 236ff4fbdf5SAlan Somers 237ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 238ff4fbdf5SAlan Somers public: 239ff4fbdf5SAlan Somers void expect_setxattr(int error) 240ff4fbdf5SAlan Somers { 241ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 242ff4fbdf5SAlan Somers ResultOf([=](auto in) { 24329edc611SAlan Somers return (in.header.opcode == FUSE_SETXATTR); 244ff4fbdf5SAlan Somers }, Eq(true)), 245ff4fbdf5SAlan Somers _) 246ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 247ff4fbdf5SAlan Somers } 248ff4fbdf5SAlan Somers }; 249ff4fbdf5SAlan Somers 2508cfb4431SAlan Somers /* Return a group to which this user does not belong */ 2518cfb4431SAlan Somers static gid_t excluded_group() 2528cfb4431SAlan Somers { 2538cfb4431SAlan Somers int i, ngroups = 64; 2548cfb4431SAlan Somers gid_t newgid, groups[ngroups]; 2558cfb4431SAlan Somers 2568cfb4431SAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 2575a0b9a27SAlan Somers for (newgid = 0; ; newgid++) { 2588cfb4431SAlan Somers bool belongs = false; 2598cfb4431SAlan Somers 2608cfb4431SAlan Somers for (i = 0; i < ngroups; i++) { 2618cfb4431SAlan Somers if (groups[i] == newgid) 2628cfb4431SAlan Somers belongs = true; 2638cfb4431SAlan Somers } 2648cfb4431SAlan Somers if (!belongs) 2658cfb4431SAlan Somers break; 2668cfb4431SAlan Somers } 2678cfb4431SAlan Somers /* newgid is now a group to which the current user does not belong */ 2688cfb4431SAlan Somers return newgid; 2698cfb4431SAlan Somers } 2708cfb4431SAlan Somers 271ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2729821f1d3SAlan Somers { 2739821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2749821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2759821f1d3SAlan Somers uint64_t ino = 42; 2769821f1d3SAlan Somers mode_t access_mode = X_OK; 2779821f1d3SAlan Somers 278a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 279ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 280ff4fbdf5SAlan Somers 281ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 282ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 283ff4fbdf5SAlan Somers } 284ff4fbdf5SAlan Somers 285ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 286ff4fbdf5SAlan Somers { 287ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 288ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 289ff4fbdf5SAlan Somers uint64_t ino = 42; 290ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 291ff4fbdf5SAlan Somers 292a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1); 293ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 294ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2959821f1d3SAlan Somers /* 2969821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2979821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 2989821f1d3SAlan Somers * a FUSE_ACCESS 2999821f1d3SAlan Somers */ 3009821f1d3SAlan Somers 3019821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 3029821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 3039821f1d3SAlan Somers } 3049821f1d3SAlan Somers 305ff4fbdf5SAlan Somers TEST_F(Access, ok) 3069821f1d3SAlan Somers { 3079821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3089821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 3099821f1d3SAlan Somers uint64_t ino = 42; 3109821f1d3SAlan Somers mode_t access_mode = R_OK; 3119821f1d3SAlan Somers 312a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 313ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 3149821f1d3SAlan Somers /* 3159821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 31691ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 3179821f1d3SAlan Somers */ 3189821f1d3SAlan Somers 3199821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 3209821f1d3SAlan Somers } 3219821f1d3SAlan Somers 3224e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */ 3234e83d655SAlan Somers TEST_F(Chown, chown_to_self) 3244e83d655SAlan Somers { 3254e83d655SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3264e83d655SAlan Somers const char RELPATH[] = "some_file.txt"; 3274e83d655SAlan Somers const uint64_t ino = 42; 3284e83d655SAlan Somers const mode_t mode = 0755; 3294e83d655SAlan Somers uid_t uid; 3304e83d655SAlan Somers 3314e83d655SAlan Somers uid = geteuid(); 3324e83d655SAlan Somers 333a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 3344e83d655SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid); 3354e83d655SAlan Somers /* The OS may optimize chown by omitting the redundant setattr */ 3364e83d655SAlan Somers EXPECT_CALL(*m_mock, process( 3374e83d655SAlan Somers ResultOf([](auto in) { 33829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 3394e83d655SAlan Somers }, Eq(true)), 3404e83d655SAlan Somers _) 34129edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 3424e83d655SAlan Somers SET_OUT_HEADER_LEN(out, attr); 34329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 34429edc611SAlan Somers out.body.attr.attr.uid = uid; 3454e83d655SAlan Somers }))); 3464e83d655SAlan Somers 3474e83d655SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 3484e83d655SAlan Somers } 3494e83d655SAlan Somers 350a2bdd737SAlan Somers /* 351a2bdd737SAlan Somers * A successful chown by a non-privileged non-owner should clear a file's SUID 352a2bdd737SAlan Somers * bit 353a2bdd737SAlan Somers */ 354a2bdd737SAlan Somers TEST_F(Chown, clear_suid) 355a2bdd737SAlan Somers { 356a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 357a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 358a2bdd737SAlan Somers uint64_t ino = 42; 359a2bdd737SAlan Somers const mode_t oldmode = 06755; 360a2bdd737SAlan Somers const mode_t newmode = 0755; 361a2bdd737SAlan Somers uid_t uid = geteuid(); 362a2bdd737SAlan Somers uint32_t valid = FATTR_UID | FATTR_MODE; 363a2bdd737SAlan Somers 364a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 365a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid); 366a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 367a2bdd737SAlan Somers ResultOf([=](auto in) { 36829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 36929edc611SAlan Somers in.header.nodeid == ino && 37029edc611SAlan Somers in.body.setattr.valid == valid && 37129edc611SAlan Somers in.body.setattr.mode == newmode); 372a2bdd737SAlan Somers }, Eq(true)), 373a2bdd737SAlan Somers _) 37429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 375a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 37629edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 37729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 37829edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 379a2bdd737SAlan Somers }))); 380a2bdd737SAlan Somers 381a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 382a2bdd737SAlan Somers } 383a2bdd737SAlan Somers 384a2bdd737SAlan Somers 385474ba6faSAlan Somers /* Only root may change a file's owner */ 386474ba6faSAlan Somers TEST_F(Chown, eperm) 387474ba6faSAlan Somers { 388474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 389474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 390474ba6faSAlan Somers const uint64_t ino = 42; 391474ba6faSAlan Somers const mode_t mode = 0755; 392474ba6faSAlan Somers 393a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 394474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 395474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 396474ba6faSAlan Somers ResultOf([](auto in) { 39729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 398474ba6faSAlan Somers }, Eq(true)), 399474ba6faSAlan Somers _) 400474ba6faSAlan Somers ).Times(0); 401474ba6faSAlan Somers 402474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 403474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 404474ba6faSAlan Somers } 405474ba6faSAlan Somers 406a2bdd737SAlan Somers /* 407a2bdd737SAlan Somers * A successful chgrp by a non-privileged non-owner should clear a file's SUID 408a2bdd737SAlan Somers * bit 409a2bdd737SAlan Somers */ 410a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid) 411a2bdd737SAlan Somers { 412a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 413a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 414a2bdd737SAlan Somers uint64_t ino = 42; 415a2bdd737SAlan Somers const mode_t oldmode = 06755; 416a2bdd737SAlan Somers const mode_t newmode = 0755; 417a2bdd737SAlan Somers uid_t uid = geteuid(); 418a2bdd737SAlan Somers gid_t gid = getegid(); 419a2bdd737SAlan Somers uint32_t valid = FATTR_GID | FATTR_MODE; 420a2bdd737SAlan Somers 421a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 422a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 423a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 424a2bdd737SAlan Somers ResultOf([=](auto in) { 42529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 42629edc611SAlan Somers in.header.nodeid == ino && 42729edc611SAlan Somers in.body.setattr.valid == valid && 42829edc611SAlan Somers in.body.setattr.mode == newmode); 429a2bdd737SAlan Somers }, Eq(true)), 430a2bdd737SAlan Somers _) 43129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 432a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 43329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 43429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 43529edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 436a2bdd737SAlan Somers }))); 437a2bdd737SAlan Somers 438a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno); 439a2bdd737SAlan Somers } 440a2bdd737SAlan Somers 441474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 442474ba6faSAlan Somers TEST_F(Chgrp, eperm) 443474ba6faSAlan Somers { 444474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 445474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 446474ba6faSAlan Somers const uint64_t ino = 42; 447474ba6faSAlan Somers const mode_t mode = 0755; 448474ba6faSAlan Somers uid_t uid; 449474ba6faSAlan Somers gid_t gid, newgid; 450474ba6faSAlan Somers 451474ba6faSAlan Somers uid = geteuid(); 452474ba6faSAlan Somers gid = getegid(); 4538cfb4431SAlan Somers newgid = excluded_group(); 454474ba6faSAlan Somers 455a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 456474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 457474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 458474ba6faSAlan Somers ResultOf([](auto in) { 45929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 460474ba6faSAlan Somers }, Eq(true)), 461474ba6faSAlan Somers _) 462474ba6faSAlan Somers ).Times(0); 463474ba6faSAlan Somers 464474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 465474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 466474ba6faSAlan Somers } 467474ba6faSAlan Somers 468474ba6faSAlan Somers TEST_F(Chgrp, ok) 469474ba6faSAlan Somers { 470474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 471474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 472474ba6faSAlan Somers const uint64_t ino = 42; 473474ba6faSAlan Somers const mode_t mode = 0755; 474474ba6faSAlan Somers uid_t uid; 475474ba6faSAlan Somers gid_t gid, newgid; 476474ba6faSAlan Somers 477474ba6faSAlan Somers uid = geteuid(); 478474ba6faSAlan Somers gid = 0; 479474ba6faSAlan Somers newgid = getegid(); 480474ba6faSAlan Somers 481a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 482474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 4834e83d655SAlan Somers /* The OS may optimize chgrp by omitting the redundant setattr */ 484474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 485474ba6faSAlan Somers ResultOf([](auto in) { 48629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 48729edc611SAlan Somers in.header.nodeid == ino); 488474ba6faSAlan Somers }, Eq(true)), 489474ba6faSAlan Somers _) 49029edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 491474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 49229edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 49329edc611SAlan Somers out.body.attr.attr.uid = uid; 49429edc611SAlan Somers out.body.attr.attr.gid = newgid; 495474ba6faSAlan Somers }))); 496474ba6faSAlan Somers 497474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 498474ba6faSAlan Somers } 499474ba6faSAlan Somers 500*92bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 501*92bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_guid) 502*92bbfe1fSAlan Somers { 503*92bbfe1fSAlan Somers const char FULLPATH_IN[] = "mountpoint/in.txt"; 504*92bbfe1fSAlan Somers const char RELPATH_IN[] = "in.txt"; 505*92bbfe1fSAlan Somers const char FULLPATH_OUT[] = "mountpoint/out.txt"; 506*92bbfe1fSAlan Somers const char RELPATH_OUT[] = "out.txt"; 507*92bbfe1fSAlan Somers struct stat sb; 508*92bbfe1fSAlan Somers uint64_t ino_in = 42; 509*92bbfe1fSAlan Somers uint64_t ino_out = 43; 510*92bbfe1fSAlan Somers mode_t oldmode = 02777; 511*92bbfe1fSAlan Somers mode_t newmode = 0777; 512*92bbfe1fSAlan Somers off_t fsize = 16; 513*92bbfe1fSAlan Somers off_t off_in = 0; 514*92bbfe1fSAlan Somers off_t off_out = 8; 515*92bbfe1fSAlan Somers off_t len = 8; 516*92bbfe1fSAlan Somers int fd_in, fd_out; 517*92bbfe1fSAlan Somers 518*92bbfe1fSAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 519*92bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 520*92bbfe1fSAlan Somers UINT64_MAX, 0, 0); 521*92bbfe1fSAlan Somers expect_open(ino_in, 0, 1); 522*92bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 523*92bbfe1fSAlan Somers 1, UINT64_MAX, 0, 0); 524*92bbfe1fSAlan Somers expect_open(ino_out, 0, 1); 525*92bbfe1fSAlan Somers expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 526*92bbfe1fSAlan Somers expect_chmod(ino_out, newmode, fsize); 527*92bbfe1fSAlan Somers 528*92bbfe1fSAlan Somers fd_in = open(FULLPATH_IN, O_RDONLY); 529*92bbfe1fSAlan Somers ASSERT_LE(0, fd_in) << strerror(errno); 530*92bbfe1fSAlan Somers fd_out = open(FULLPATH_OUT, O_WRONLY); 531*92bbfe1fSAlan Somers ASSERT_LE(0, fd_out) << strerror(errno); 532*92bbfe1fSAlan Somers ASSERT_EQ(len, 533*92bbfe1fSAlan Somers copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 534*92bbfe1fSAlan Somers << strerror(errno); 535*92bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 536*92bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 537*92bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 538*92bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 539*92bbfe1fSAlan Somers 540*92bbfe1fSAlan Somers leak(fd_in); 541*92bbfe1fSAlan Somers leak(fd_out); 542*92bbfe1fSAlan Somers } 543*92bbfe1fSAlan Somers 544*92bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 545*92bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_suid) 546*92bbfe1fSAlan Somers { 547*92bbfe1fSAlan Somers const char FULLPATH_IN[] = "mountpoint/in.txt"; 548*92bbfe1fSAlan Somers const char RELPATH_IN[] = "in.txt"; 549*92bbfe1fSAlan Somers const char FULLPATH_OUT[] = "mountpoint/out.txt"; 550*92bbfe1fSAlan Somers const char RELPATH_OUT[] = "out.txt"; 551*92bbfe1fSAlan Somers struct stat sb; 552*92bbfe1fSAlan Somers uint64_t ino_in = 42; 553*92bbfe1fSAlan Somers uint64_t ino_out = 43; 554*92bbfe1fSAlan Somers mode_t oldmode = 04777; 555*92bbfe1fSAlan Somers mode_t newmode = 0777; 556*92bbfe1fSAlan Somers off_t fsize = 16; 557*92bbfe1fSAlan Somers off_t off_in = 0; 558*92bbfe1fSAlan Somers off_t off_out = 8; 559*92bbfe1fSAlan Somers off_t len = 8; 560*92bbfe1fSAlan Somers int fd_in, fd_out; 561*92bbfe1fSAlan Somers 562*92bbfe1fSAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 563*92bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 564*92bbfe1fSAlan Somers UINT64_MAX, 0, 0); 565*92bbfe1fSAlan Somers expect_open(ino_in, 0, 1); 566*92bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 567*92bbfe1fSAlan Somers 1, UINT64_MAX, 0, 0); 568*92bbfe1fSAlan Somers expect_open(ino_out, 0, 1); 569*92bbfe1fSAlan Somers expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 570*92bbfe1fSAlan Somers expect_chmod(ino_out, newmode, fsize); 571*92bbfe1fSAlan Somers 572*92bbfe1fSAlan Somers fd_in = open(FULLPATH_IN, O_RDONLY); 573*92bbfe1fSAlan Somers ASSERT_LE(0, fd_in) << strerror(errno); 574*92bbfe1fSAlan Somers fd_out = open(FULLPATH_OUT, O_WRONLY); 575*92bbfe1fSAlan Somers ASSERT_LE(0, fd_out) << strerror(errno); 576*92bbfe1fSAlan Somers ASSERT_EQ(len, 577*92bbfe1fSAlan Somers copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 578*92bbfe1fSAlan Somers << strerror(errno); 579*92bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 580*92bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 581*92bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 582*92bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 583*92bbfe1fSAlan Somers 584*92bbfe1fSAlan Somers leak(fd_in); 585*92bbfe1fSAlan Somers leak(fd_out); 586*92bbfe1fSAlan Somers } 587*92bbfe1fSAlan Somers 588ff4fbdf5SAlan Somers TEST_F(Create, ok) 589ff4fbdf5SAlan Somers { 590ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 591ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 592ff4fbdf5SAlan Somers uint64_t ino = 42; 593ff4fbdf5SAlan Somers int fd; 594ff4fbdf5SAlan Somers 595a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 596a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 597a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 598ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 599ff4fbdf5SAlan Somers 600ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 601d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 6027fc0921dSAlan Somers leak(fd); 603ff4fbdf5SAlan Somers } 604ff4fbdf5SAlan Somers 605ff4fbdf5SAlan Somers TEST_F(Create, eacces) 606ff4fbdf5SAlan Somers { 607ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 608ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 609ff4fbdf5SAlan Somers 610a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 611a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 612a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 613ff4fbdf5SAlan Somers 6148e765737SAlan Somers ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 615ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 616ff4fbdf5SAlan Somers } 617ff4fbdf5SAlan Somers 618ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 619ff4fbdf5SAlan Somers { 620ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 621ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 622ff4fbdf5SAlan Somers uint64_t ino = 42; 623ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 624ff4fbdf5SAlan Somers 625a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 626ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 627ff4fbdf5SAlan Somers 628ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 629ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 630ff4fbdf5SAlan Somers } 631ff4fbdf5SAlan Somers 632ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 633ff4fbdf5SAlan Somers { 634ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 635ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 636ff4fbdf5SAlan Somers uint64_t ino = 42; 637ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 638ff4fbdf5SAlan Somers 639a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 640ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 641ff4fbdf5SAlan Somers expect_removexattr(); 642ff4fbdf5SAlan Somers 643ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 644ff4fbdf5SAlan Somers << strerror(errno); 645ff4fbdf5SAlan Somers } 646ff4fbdf5SAlan Somers 647ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 648ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 649ff4fbdf5SAlan Somers { 650ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 651ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 652ff4fbdf5SAlan Somers uint64_t ino = 42; 653ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 654ff4fbdf5SAlan Somers 655a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 656ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 657ff4fbdf5SAlan Somers 658ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 659ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 660ff4fbdf5SAlan Somers } 661ff4fbdf5SAlan Somers 662d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */ 663d943c93eSAlan Somers TEST_F(Utimensat, utime_now) 664d943c93eSAlan Somers { 665d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 666d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 667d943c93eSAlan Somers const uint64_t ino = 42; 668d943c93eSAlan Somers /* Write permissions for everybody */ 669d943c93eSAlan Somers const mode_t mode = 0666; 670d943c93eSAlan Somers uid_t owner = 0; 671d943c93eSAlan Somers const timespec times[2] = { 672d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 673d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 674d943c93eSAlan Somers }; 675d943c93eSAlan Somers 676a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 677d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 678d943c93eSAlan Somers EXPECT_CALL(*m_mock, process( 679d943c93eSAlan Somers ResultOf([](auto in) { 68029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 68129edc611SAlan Somers in.header.nodeid == ino && 68229edc611SAlan Somers in.body.setattr.valid & FATTR_ATIME && 68329edc611SAlan Somers in.body.setattr.valid & FATTR_MTIME); 684d943c93eSAlan Somers }, Eq(true)), 685d943c93eSAlan Somers _) 68629edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 687d943c93eSAlan Somers SET_OUT_HEADER_LEN(out, attr); 68829edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 689d943c93eSAlan Somers }))); 690d943c93eSAlan Somers 691d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 692d943c93eSAlan Somers << strerror(errno); 693d943c93eSAlan Somers } 694d943c93eSAlan Somers 695d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */ 696d943c93eSAlan Somers TEST_F(Utimensat, utime_omit) 697d943c93eSAlan Somers { 698d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 699d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 700d943c93eSAlan Somers const uint64_t ino = 42; 701d943c93eSAlan Somers /* Write permissions for no one */ 702d943c93eSAlan Somers const mode_t mode = 0444; 703d943c93eSAlan Somers uid_t owner = 0; 704d943c93eSAlan Somers const timespec times[2] = { 705d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 706d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 707d943c93eSAlan Somers }; 708d943c93eSAlan Somers 709a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 710d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 711d943c93eSAlan Somers 712d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 713d943c93eSAlan Somers << strerror(errno); 714d943c93eSAlan Somers } 715d943c93eSAlan Somers 716ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 717ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 718ff4fbdf5SAlan Somers { 719ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 720ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 721ff4fbdf5SAlan Somers uint64_t ino = 42; 722ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 723ff4fbdf5SAlan Somers 724a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 725ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 726ff4fbdf5SAlan Somers expect_removexattr(); 727ff4fbdf5SAlan Somers 728ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 729ff4fbdf5SAlan Somers << strerror(errno); 730ff4fbdf5SAlan Somers } 731ff4fbdf5SAlan Somers 732ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 733ff4fbdf5SAlan Somers { 734ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 735ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 736ff4fbdf5SAlan Somers uint64_t ino = 42; 737ff4fbdf5SAlan Somers char data[80]; 738ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 739ff4fbdf5SAlan Somers 740a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 741ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 742ff4fbdf5SAlan Somers 743ff4fbdf5SAlan Somers ASSERT_EQ(-1, 744ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 745ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 746ff4fbdf5SAlan Somers } 747ff4fbdf5SAlan Somers 748ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 749ff4fbdf5SAlan Somers { 750ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 751ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 752ff4fbdf5SAlan Somers uint64_t ino = 42; 753ff4fbdf5SAlan Somers char data[80]; 754ff4fbdf5SAlan Somers const char value[] = "whatever"; 755ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 756ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 757ff4fbdf5SAlan Somers ssize_t r; 758ff4fbdf5SAlan Somers 759a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 760ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 761ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 762ff4fbdf5SAlan Somers expect_getxattr( 76329edc611SAlan Somers ReturnImmediate([&](auto in __unused, auto& out) { 76429edc611SAlan Somers memcpy((void*)out.body.bytes, value, value_len); 76529edc611SAlan Somers out.header.len = sizeof(out.header) + value_len; 766ff4fbdf5SAlan Somers }) 767ff4fbdf5SAlan Somers ); 768ff4fbdf5SAlan Somers 769ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 770ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 771ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 772ff4fbdf5SAlan Somers } 773ff4fbdf5SAlan Somers 774ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 775ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 776ff4fbdf5SAlan Somers { 777ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 778ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 779ff4fbdf5SAlan Somers uint64_t ino = 42; 780ff4fbdf5SAlan Somers char data[80]; 781ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 782ff4fbdf5SAlan Somers 783a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 784ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 785ff4fbdf5SAlan Somers 786ff4fbdf5SAlan Somers ASSERT_EQ(-1, 787ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 788ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 789ff4fbdf5SAlan Somers } 790ff4fbdf5SAlan Somers 791ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 792ff4fbdf5SAlan Somers { 793ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 794ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 795ff4fbdf5SAlan Somers uint64_t ino = 42; 796ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 797ff4fbdf5SAlan Somers 798a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 799ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 800ff4fbdf5SAlan Somers 801ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 802ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 803ff4fbdf5SAlan Somers } 804ff4fbdf5SAlan Somers 805ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 806ff4fbdf5SAlan Somers { 807ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 808ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 809ff4fbdf5SAlan Somers uint64_t ino = 42; 810ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 811ff4fbdf5SAlan Somers 812a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 813ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 814ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 815ff4fbdf5SAlan Somers expect_listxattr(); 816ff4fbdf5SAlan Somers 817ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 818ff4fbdf5SAlan Somers << strerror(errno); 819ff4fbdf5SAlan Somers } 820ff4fbdf5SAlan Somers 821ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 822ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 823ff4fbdf5SAlan Somers { 824ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 825ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 826ff4fbdf5SAlan Somers uint64_t ino = 42; 827ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 828ff4fbdf5SAlan Somers 829a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 830ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 831ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 832ff4fbdf5SAlan Somers 833ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 834ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 835ff4fbdf5SAlan Somers } 836ff4fbdf5SAlan Somers 837ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 838ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 839ff4fbdf5SAlan Somers { 840ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 841ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 842ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 843ff4fbdf5SAlan Somers 844a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 845ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 846ff4fbdf5SAlan Somers 847ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 848ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 849ff4fbdf5SAlan Somers } 850ff4fbdf5SAlan Somers 851ff4fbdf5SAlan Somers TEST_F(Open, eacces) 852ff4fbdf5SAlan Somers { 853ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 854ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 855ff4fbdf5SAlan Somers uint64_t ino = 42; 856ff4fbdf5SAlan Somers 857a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 858ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 859ff4fbdf5SAlan Somers 8604ca1c0b7SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_RDWR)); 861ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 862ff4fbdf5SAlan Somers } 863ff4fbdf5SAlan Somers 8649821f1d3SAlan Somers TEST_F(Open, ok) 8659821f1d3SAlan Somers { 8669821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 8679821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 8689821f1d3SAlan Somers uint64_t ino = 42; 8699821f1d3SAlan Somers int fd; 8709821f1d3SAlan Somers 871a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 872ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 8739821f1d3SAlan Somers expect_open(ino, 0, 1); 8749821f1d3SAlan Somers 8759821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 876d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 8777fc0921dSAlan Somers leak(fd); 8789821f1d3SAlan Somers } 8799821f1d3SAlan Somers 880ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 881ff4fbdf5SAlan Somers { 882ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 883ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 884ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 885ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 886ff4fbdf5SAlan Somers uint64_t ino = 42; 887ff4fbdf5SAlan Somers 888a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0); 889ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 890a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 891ff4fbdf5SAlan Somers .Times(AnyNumber()) 892ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 893ff4fbdf5SAlan Somers 894ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 895ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 896ff4fbdf5SAlan Somers } 897ff4fbdf5SAlan Somers 898ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 899ff4fbdf5SAlan Somers { 900ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 901ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 902ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 903ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 904ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 905ff4fbdf5SAlan Somers uint64_t src_ino = 42; 906ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 907ff4fbdf5SAlan Somers 908a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 909ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 910ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 911ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 912ff4fbdf5SAlan Somers 913ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 914ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 915ff4fbdf5SAlan Somers } 916ff4fbdf5SAlan Somers 917ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 918ff4fbdf5SAlan Somers { 919ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 920ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 921ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 922ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 923ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 924ff4fbdf5SAlan Somers uint64_t src_ino = 42; 925ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 926ff4fbdf5SAlan Somers 927a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 928ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 929ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 930ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 931ff4fbdf5SAlan Somers 932ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 933ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 934ff4fbdf5SAlan Somers } 935ff4fbdf5SAlan Somers 9366124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 937ff4fbdf5SAlan Somers { 938ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 939ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 940ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 941ff4fbdf5SAlan Somers uint64_t ino = 42; 942ff4fbdf5SAlan Somers 943a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 944ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 945ff4fbdf5SAlan Somers 946ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 947ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 948ff4fbdf5SAlan Somers } 949ff4fbdf5SAlan Somers 9508e45ec4eSAlan Somers /* 9518e45ec4eSAlan Somers * A user cannot move out a subdirectory that he does not own, because that 9528e45ec4eSAlan Somers * would require changing the subdirectory's ".." dirent 9538e45ec4eSAlan Somers */ 9548e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory) 9558e45ec4eSAlan Somers { 9568e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 9578e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 9588e45ec4eSAlan Somers const char RELDSTDIR[] = "d"; 9598e45ec4eSAlan Somers const char RELDST[] = "dst"; 9608e45ec4eSAlan Somers const char RELSRC[] = "src"; 9618e45ec4eSAlan Somers uint64_t ino = 42; 9628e45ec4eSAlan Somers uint64_t dstdir_ino = 43; 9638e45ec4eSAlan Somers 964a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 9658e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 9668e45ec4eSAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 9678e45ec4eSAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 9688e45ec4eSAlan Somers 9698e45ec4eSAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 9708e45ec4eSAlan Somers ASSERT_EQ(EACCES, errno); 9718e45ec4eSAlan Somers } 9728e45ec4eSAlan Somers 9738e45ec4eSAlan Somers /* 9748e45ec4eSAlan Somers * A user _can_ rename a subdirectory to which he lacks write permissions, if 9758e45ec4eSAlan Somers * it will keep the same parent 9768e45ec4eSAlan Somers */ 9778e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir) 9788e45ec4eSAlan Somers { 9798e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/dst"; 9808e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 9818e45ec4eSAlan Somers const char RELDST[] = "dst"; 9828e45ec4eSAlan Somers const char RELSRC[] = "src"; 9838e45ec4eSAlan Somers uint64_t ino = 42; 9848e45ec4eSAlan Somers 985a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 9868e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 987a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 988a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 9898e45ec4eSAlan Somers expect_rename(0); 9908e45ec4eSAlan Somers 9918e45ec4eSAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 9928e45ec4eSAlan Somers } 9938e45ec4eSAlan Somers 9946124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 995ff4fbdf5SAlan Somers { 996ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 997ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 9986124fd71SAlan Somers const char RELDST[] = "dst"; 999ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1000ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1001ff4fbdf5SAlan Somers uint64_t src_ino = 42; 1002ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 1003ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 1004ff4fbdf5SAlan Somers 1005a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1006ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1007ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 10086124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 100929edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 10106124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 101129edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 101229edc611SAlan Somers out.body.entry.nodeid = dst_ino; 101329edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 101429edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 101529edc611SAlan Somers out.body.entry.attr.uid = 0; 10166124fd71SAlan Somers }))); 1017ff4fbdf5SAlan Somers 1018ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1019ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1020ff4fbdf5SAlan Somers } 1021ff4fbdf5SAlan Somers 1022ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 1023ff4fbdf5SAlan Somers TEST_F(Rename, ok) 1024ff4fbdf5SAlan Somers { 1025ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 1026ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1027ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1028ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1029ff4fbdf5SAlan Somers // The inode of the already-existing destination file 1030ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 1031ff4fbdf5SAlan Somers uint64_t ino = 42; 1032ff4fbdf5SAlan Somers 1033a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 1034ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1035ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 1036ff4fbdf5SAlan Somers expect_rename(0); 1037ff4fbdf5SAlan Somers 1038ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1039ff4fbdf5SAlan Somers } 1040ff4fbdf5SAlan Somers 1041ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 1042ff4fbdf5SAlan Somers { 1043ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 1044ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1045ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1046ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1047ff4fbdf5SAlan Somers uint64_t ino = 42; 1048ff4fbdf5SAlan Somers 1049a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1050ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1051a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1052a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 1053ff4fbdf5SAlan Somers expect_rename(0); 1054ff4fbdf5SAlan Somers 1055ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1056ff4fbdf5SAlan Somers } 1057ff4fbdf5SAlan Somers 1058ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 1059ff4fbdf5SAlan Somers { 1060ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1061ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1062ff4fbdf5SAlan Somers const uint64_t ino = 42; 1063ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 1064ff4fbdf5SAlan Somers const mode_t newmode = 0644; 1065ff4fbdf5SAlan Somers 1066a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1067ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1068ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 1069ff4fbdf5SAlan Somers ResultOf([](auto in) { 107029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 107129edc611SAlan Somers in.header.nodeid == ino && 107229edc611SAlan Somers in.body.setattr.mode == newmode); 1073ff4fbdf5SAlan Somers }, Eq(true)), 1074ff4fbdf5SAlan Somers _) 107529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 1076ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 107729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 1078ff4fbdf5SAlan Somers }))); 1079ff4fbdf5SAlan Somers 1080ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 1081ff4fbdf5SAlan Somers } 1082ff4fbdf5SAlan Somers 1083ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 1084ff4fbdf5SAlan Somers { 1085ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1086ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1087ff4fbdf5SAlan Somers const uint64_t ino = 42; 1088ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 1089ff4fbdf5SAlan Somers const mode_t newmode = 0644; 1090ff4fbdf5SAlan Somers 1091a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1092ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 1093ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 1094ff4fbdf5SAlan Somers ResultOf([](auto in) { 109529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1096ff4fbdf5SAlan Somers }, Eq(true)), 1097ff4fbdf5SAlan Somers _) 1098ff4fbdf5SAlan Somers ).Times(0); 1099ff4fbdf5SAlan Somers 1100ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1101ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 1102ff4fbdf5SAlan Somers } 1103ff4fbdf5SAlan Somers 11048cfb4431SAlan Somers /* 11053fa12789SAlan Somers * ftruncate() of a file without writable permissions should succeed as long as 11063fa12789SAlan Somers * the file descriptor is writable. This is important when combined with 11073fa12789SAlan Somers * O_CREAT 11083fa12789SAlan Somers */ 11093fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file) 11103fa12789SAlan Somers { 11113fa12789SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11123fa12789SAlan Somers const char RELPATH[] = "some_file.txt"; 11133fa12789SAlan Somers const uint64_t ino = 42; 11143fa12789SAlan Somers const mode_t mode = 0000; 11153fa12789SAlan Somers int fd; 11163fa12789SAlan Somers 1117a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1118a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1119a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 11203fa12789SAlan Somers expect_create(RELPATH, ino); 11213fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 11223fa12789SAlan Somers ResultOf([](auto in) { 112329edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 112429edc611SAlan Somers in.header.nodeid == ino && 112529edc611SAlan Somers (in.body.setattr.valid & FATTR_SIZE)); 11263fa12789SAlan Somers }, Eq(true)), 11273fa12789SAlan Somers _) 112829edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 11293fa12789SAlan Somers SET_OUT_HEADER_LEN(out, attr); 113029edc611SAlan Somers out.body.attr.attr.ino = ino; 113129edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 113229edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 11333fa12789SAlan Somers }))); 11343fa12789SAlan Somers 11353fa12789SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 11363fa12789SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 11373fa12789SAlan Somers ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 11387fc0921dSAlan Somers leak(fd); 11393fa12789SAlan Somers } 11403fa12789SAlan Somers 11413fa12789SAlan Somers /* 11428cfb4431SAlan Somers * Setting the sgid bit should fail for an unprivileged user who doesn't belong 11438cfb4431SAlan Somers * to the file's group 11448cfb4431SAlan Somers */ 11458cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member) 11468cfb4431SAlan Somers { 11478cfb4431SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11488cfb4431SAlan Somers const char RELPATH[] = "some_file.txt"; 11498cfb4431SAlan Somers const uint64_t ino = 42; 11508cfb4431SAlan Somers const mode_t oldmode = 0755; 11518cfb4431SAlan Somers const mode_t newmode = 02755; 11528cfb4431SAlan Somers uid_t uid = geteuid(); 11538cfb4431SAlan Somers gid_t gid = excluded_group(); 11548cfb4431SAlan Somers 1155a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 11568cfb4431SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 11578cfb4431SAlan Somers EXPECT_CALL(*m_mock, process( 11588cfb4431SAlan Somers ResultOf([](auto in) { 115929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 11608cfb4431SAlan Somers }, Eq(true)), 11618cfb4431SAlan Somers _) 11628cfb4431SAlan Somers ).Times(0); 11638cfb4431SAlan Somers 11648cfb4431SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 11658cfb4431SAlan Somers EXPECT_EQ(EPERM, errno); 11668cfb4431SAlan Somers } 11678cfb4431SAlan Somers 1168e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 1169e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 1170e5ff3a7eSAlan Somers { 1171e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1172e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 1173e5ff3a7eSAlan Somers const uint64_t ino = 42; 1174e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 1175e5ff3a7eSAlan Somers const mode_t newmode = 01644; 1176e5ff3a7eSAlan Somers 1177a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1178e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1179e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 1180e5ff3a7eSAlan Somers ResultOf([](auto in) { 118129edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1182e5ff3a7eSAlan Somers }, Eq(true)), 1183e5ff3a7eSAlan Somers _) 1184e5ff3a7eSAlan Somers ).Times(0); 1185e5ff3a7eSAlan Somers 1186e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1187e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 1188e5ff3a7eSAlan Somers } 1189e5ff3a7eSAlan Somers 1190ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 1191ff4fbdf5SAlan Somers { 1192ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1193ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1194ff4fbdf5SAlan Somers uint64_t ino = 42; 1195ff4fbdf5SAlan Somers const char value[] = "whatever"; 1196ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1197ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1198ff4fbdf5SAlan Somers ssize_t r; 1199ff4fbdf5SAlan Somers 1200a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1201ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1202ff4fbdf5SAlan Somers expect_setxattr(0); 1203ff4fbdf5SAlan Somers 12045a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 12055a0b9a27SAlan Somers value_len); 1206ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1207ff4fbdf5SAlan Somers } 1208ff4fbdf5SAlan Somers 1209ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 1210ff4fbdf5SAlan Somers { 1211ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1212ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1213ff4fbdf5SAlan Somers uint64_t ino = 42; 1214ff4fbdf5SAlan Somers const char value[] = "whatever"; 1215ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1216ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1217ff4fbdf5SAlan Somers 1218a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1219ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1220ff4fbdf5SAlan Somers 12215a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 12225a0b9a27SAlan Somers value_len)); 1223ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1224ff4fbdf5SAlan Somers } 1225ff4fbdf5SAlan Somers 1226ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 1227ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 1228ff4fbdf5SAlan Somers { 1229ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1230ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1231ff4fbdf5SAlan Somers uint64_t ino = 42; 1232ff4fbdf5SAlan Somers const char value[] = "whatever"; 1233ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1234ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 1235ff4fbdf5SAlan Somers 1236a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1237ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1238ff4fbdf5SAlan Somers 12395a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 12405a0b9a27SAlan Somers value_len)); 1241ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1242ff4fbdf5SAlan Somers } 1243ff4fbdf5SAlan Somers 1244ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 1245ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 1246ff4fbdf5SAlan Somers { 1247ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1248ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1249ff4fbdf5SAlan Somers uint64_t ino = 42; 1250ff4fbdf5SAlan Somers const char value[] = "whatever"; 1251ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1252ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1253ff4fbdf5SAlan Somers ssize_t r; 1254ff4fbdf5SAlan Somers 1255a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1256ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1257ff4fbdf5SAlan Somers expect_setxattr(0); 1258ff4fbdf5SAlan Somers 12595a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 12605a0b9a27SAlan Somers value_len); 1261ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1262ff4fbdf5SAlan Somers } 1263ff4fbdf5SAlan Somers 1264ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 12659821f1d3SAlan Somers { 12669821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 12679821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 12689821f1d3SAlan Somers uint64_t ino = 42; 1269331884f2SAlan Somers sem_t sem; 1270331884f2SAlan Somers 1271331884f2SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 12729821f1d3SAlan Somers 1273a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1274ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1275a34cdd26SAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, 0); 1276331884f2SAlan Somers expect_forget(ino, 1, &sem); 12779821f1d3SAlan Somers 1278ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1279331884f2SAlan Somers 1280331884f2SAlan Somers sem_wait(&sem); 1281331884f2SAlan Somers sem_destroy(&sem); 1282ff4fbdf5SAlan Somers } 1283ff4fbdf5SAlan Somers 12846124fd71SAlan Somers /* 12856124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 12866124fd71SAlan Somers * in VOP_LOOKUP. 12876124fd71SAlan Somers * 12886124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 12896124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 12906124fd71SAlan Somers */ 12916124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 12926124fd71SAlan Somers { 12936124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 12946124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 12956124fd71SAlan Somers uint64_t ino = 42; 12966124fd71SAlan Somers 1297a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1298a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 12996124fd71SAlan Somers .Times(AnyNumber()) 13006124fd71SAlan Somers .WillRepeatedly(Invoke( 130129edc611SAlan Somers ReturnImmediate([=](auto i __unused, auto& out) { 13026124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 130329edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 130429edc611SAlan Somers out.body.entry.nodeid = ino; 130529edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 13066124fd71SAlan Somers })) 13076124fd71SAlan Somers ); 13086124fd71SAlan Somers 13096124fd71SAlan Somers /* Fill name cache */ 13106124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 13116124fd71SAlan Somers /* Despite cached name , unlink should fail */ 13126124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 13136124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 13146124fd71SAlan Somers } 13156124fd71SAlan Somers 1316ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 1317ff4fbdf5SAlan Somers { 1318ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1319ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1320ff4fbdf5SAlan Somers uint64_t ino = 42; 1321ff4fbdf5SAlan Somers 1322a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1323ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1324ff4fbdf5SAlan Somers 1325ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1326ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1327ff4fbdf5SAlan Somers } 1328ff4fbdf5SAlan Somers 13296124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 1330ff4fbdf5SAlan Somers { 1331ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1332ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1333ff4fbdf5SAlan Somers uint64_t ino = 42; 1334ff4fbdf5SAlan Somers 1335a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1); 1336ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1337ff4fbdf5SAlan Somers 1338ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1339ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 13409821f1d3SAlan Somers } 1341a90e32deSAlan Somers 1342a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1343a90e32deSAlan Somers TEST_F(Write, clear_suid) 1344a90e32deSAlan Somers { 1345a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1346a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1347a90e32deSAlan Somers struct stat sb; 1348a90e32deSAlan Somers uint64_t ino = 42; 1349a90e32deSAlan Somers mode_t oldmode = 04777; 1350a90e32deSAlan Somers mode_t newmode = 0777; 1351a90e32deSAlan Somers char wbuf[1] = {'x'}; 1352a90e32deSAlan Somers int fd; 1353a90e32deSAlan Somers 1354a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1355a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1356a90e32deSAlan Somers expect_open(ino, 0, 1); 1357bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 135818a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1359a90e32deSAlan Somers 1360a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1361a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1362a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1363a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1364a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 13657fc0921dSAlan Somers leak(fd); 1366a90e32deSAlan Somers } 1367a90e32deSAlan Somers 1368a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 1369a90e32deSAlan Somers TEST_F(Write, clear_sgid) 1370a90e32deSAlan Somers { 1371a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1372a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1373a90e32deSAlan Somers struct stat sb; 1374a90e32deSAlan Somers uint64_t ino = 42; 1375a90e32deSAlan Somers mode_t oldmode = 02777; 1376a90e32deSAlan Somers mode_t newmode = 0777; 1377a90e32deSAlan Somers char wbuf[1] = {'x'}; 1378a90e32deSAlan Somers int fd; 1379a90e32deSAlan Somers 1380a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1381a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1382a90e32deSAlan Somers expect_open(ino, 0, 1); 1383bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 138418a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1385a90e32deSAlan Somers 1386a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1387a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1388a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1389a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1390a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 13917fc0921dSAlan Somers leak(fd); 1392a90e32deSAlan Somers } 139318a2264eSAlan Somers 139418a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic 139518a2264eSAlan Somers * 139618a2264eSAlan Somers * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 139718a2264eSAlan Somers * may panic. That happens if the FUSE_SETATTR response indicates that the 139818a2264eSAlan Somers * file's size has changed since the write. 139918a2264eSAlan Somers */ 140018a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid) 140118a2264eSAlan Somers { 140218a2264eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 140318a2264eSAlan Somers const char RELPATH[] = "some_file.txt"; 140418a2264eSAlan Somers uint64_t ino = 42; 140518a2264eSAlan Somers mode_t oldmode = 04777; 140618a2264eSAlan Somers mode_t newmode = 0777; 140718a2264eSAlan Somers char wbuf[1] = {'x'}; 140818a2264eSAlan Somers int fd; 140918a2264eSAlan Somers 1410a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 141118a2264eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 141218a2264eSAlan Somers expect_open(ino, 0, 1); 1413bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 141418a2264eSAlan Somers /* XXX Return a smaller file size than what we just wrote! */ 141518a2264eSAlan Somers expect_chmod(ino, newmode, 0); 141618a2264eSAlan Somers 141718a2264eSAlan Somers fd = open(FULLPATH, O_WRONLY); 141818a2264eSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 141918a2264eSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 14207fc0921dSAlan Somers leak(fd); 142118a2264eSAlan Somers } 1422