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 {}; 164*89d57b94SAlan Somers class Fspacectl: public DefaultPermissions {}; 165ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1669821f1d3SAlan Somers class Open: public DefaultPermissions {}; 167398c88c7SAlan Somers class PosixFallocate: public DefaultPermissions {}; 168ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 169ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 170d943c93eSAlan Somers class Utimensat: public DefaultPermissions {}; 171a90e32deSAlan Somers class Write: public DefaultPermissions {}; 1729821f1d3SAlan Somers 173ff4fbdf5SAlan Somers /* 174ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 175ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 176ff4fbdf5SAlan Somers * VOP_LOOKUP) 177ff4fbdf5SAlan Somers */ 1783fa12789SAlan Somers class Create: public DefaultPermissions {}; 179ff4fbdf5SAlan Somers 180ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 181ff4fbdf5SAlan Somers public: 182ff4fbdf5SAlan Somers void expect_removexattr() 183ff4fbdf5SAlan Somers { 184ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 185ff4fbdf5SAlan Somers ResultOf([=](auto in) { 18629edc611SAlan Somers return (in.header.opcode == FUSE_REMOVEXATTR); 187ff4fbdf5SAlan Somers }, Eq(true)), 188ff4fbdf5SAlan Somers _) 189ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 190ff4fbdf5SAlan Somers } 191ff4fbdf5SAlan Somers }; 192ff4fbdf5SAlan Somers 193ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 194ff4fbdf5SAlan Somers public: 195ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 196ff4fbdf5SAlan Somers { 197ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 198ff4fbdf5SAlan Somers ResultOf([=](auto in) { 19929edc611SAlan Somers return (in.header.opcode == FUSE_GETXATTR); 200ff4fbdf5SAlan Somers }, Eq(true)), 201ff4fbdf5SAlan Somers _) 202ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 203ff4fbdf5SAlan Somers } 204ff4fbdf5SAlan Somers }; 205ff4fbdf5SAlan Somers 206ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 207ff4fbdf5SAlan Somers public: 208ff4fbdf5SAlan Somers void expect_listxattr() 209ff4fbdf5SAlan Somers { 210ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 211ff4fbdf5SAlan Somers ResultOf([=](auto in) { 21229edc611SAlan Somers return (in.header.opcode == FUSE_LISTXATTR); 213ff4fbdf5SAlan Somers }, Eq(true)), 214ff4fbdf5SAlan Somers _) 21529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 21629edc611SAlan Somers out.body.listxattr.size = 0; 217ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 218ff4fbdf5SAlan Somers }))); 219ff4fbdf5SAlan Somers } 220ff4fbdf5SAlan Somers }; 221ff4fbdf5SAlan Somers 222ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 223ff4fbdf5SAlan Somers public: 224ff4fbdf5SAlan Somers /* 225ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 226ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 227ff4fbdf5SAlan Somers */ 228ff4fbdf5SAlan Somers void expect_rename(int error) 229ff4fbdf5SAlan Somers { 230ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 231ff4fbdf5SAlan Somers ResultOf([=](auto in) { 23229edc611SAlan Somers return (in.header.opcode == FUSE_RENAME); 233ff4fbdf5SAlan Somers }, Eq(true)), 234ff4fbdf5SAlan Somers _) 235ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 236ff4fbdf5SAlan Somers } 237ff4fbdf5SAlan Somers }; 238ff4fbdf5SAlan Somers 239ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 240ff4fbdf5SAlan Somers public: 241ff4fbdf5SAlan Somers void expect_setxattr(int error) 242ff4fbdf5SAlan Somers { 243ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 244ff4fbdf5SAlan Somers ResultOf([=](auto in) { 24529edc611SAlan Somers return (in.header.opcode == FUSE_SETXATTR); 246ff4fbdf5SAlan Somers }, Eq(true)), 247ff4fbdf5SAlan Somers _) 248ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 249ff4fbdf5SAlan Somers } 250ff4fbdf5SAlan Somers }; 251ff4fbdf5SAlan Somers 2528cfb4431SAlan Somers /* Return a group to which this user does not belong */ 2538cfb4431SAlan Somers static gid_t excluded_group() 2548cfb4431SAlan Somers { 2558cfb4431SAlan Somers int i, ngroups = 64; 2568cfb4431SAlan Somers gid_t newgid, groups[ngroups]; 2578cfb4431SAlan Somers 2588cfb4431SAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 2595a0b9a27SAlan Somers for (newgid = 0; ; newgid++) { 2608cfb4431SAlan Somers bool belongs = false; 2618cfb4431SAlan Somers 2628cfb4431SAlan Somers for (i = 0; i < ngroups; i++) { 2638cfb4431SAlan Somers if (groups[i] == newgid) 2648cfb4431SAlan Somers belongs = true; 2658cfb4431SAlan Somers } 2668cfb4431SAlan Somers if (!belongs) 2678cfb4431SAlan Somers break; 2688cfb4431SAlan Somers } 2698cfb4431SAlan Somers /* newgid is now a group to which the current user does not belong */ 2708cfb4431SAlan Somers return newgid; 2718cfb4431SAlan Somers } 2728cfb4431SAlan Somers 273ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2749821f1d3SAlan Somers { 2759821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2769821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2779821f1d3SAlan Somers uint64_t ino = 42; 2789821f1d3SAlan Somers mode_t access_mode = X_OK; 2799821f1d3SAlan Somers 280a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 281ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 282ff4fbdf5SAlan Somers 283ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 284ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 285ff4fbdf5SAlan Somers } 286ff4fbdf5SAlan Somers 287ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 288ff4fbdf5SAlan Somers { 289ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 290ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 291ff4fbdf5SAlan Somers uint64_t ino = 42; 292ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 293ff4fbdf5SAlan Somers 294a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1); 295ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 296ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2979821f1d3SAlan Somers /* 2989821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2999821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 3009821f1d3SAlan Somers * a FUSE_ACCESS 3019821f1d3SAlan Somers */ 3029821f1d3SAlan Somers 3039821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 3049821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 3059821f1d3SAlan Somers } 3069821f1d3SAlan Somers 307ff4fbdf5SAlan Somers TEST_F(Access, ok) 3089821f1d3SAlan Somers { 3099821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3109821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 3119821f1d3SAlan Somers uint64_t ino = 42; 3129821f1d3SAlan Somers mode_t access_mode = R_OK; 3139821f1d3SAlan Somers 314a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 315ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 3169821f1d3SAlan Somers /* 3179821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 31891ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 3199821f1d3SAlan Somers */ 3209821f1d3SAlan Somers 3219821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 3229821f1d3SAlan Somers } 3239821f1d3SAlan Somers 3244e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */ 3254e83d655SAlan Somers TEST_F(Chown, chown_to_self) 3264e83d655SAlan Somers { 3274e83d655SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3284e83d655SAlan Somers const char RELPATH[] = "some_file.txt"; 3294e83d655SAlan Somers const uint64_t ino = 42; 3304e83d655SAlan Somers const mode_t mode = 0755; 3314e83d655SAlan Somers uid_t uid; 3324e83d655SAlan Somers 3334e83d655SAlan Somers uid = geteuid(); 3344e83d655SAlan Somers 335a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 3364e83d655SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid); 3374e83d655SAlan Somers /* The OS may optimize chown by omitting the redundant setattr */ 3384e83d655SAlan Somers EXPECT_CALL(*m_mock, process( 3394e83d655SAlan Somers ResultOf([](auto in) { 34029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 3414e83d655SAlan Somers }, Eq(true)), 3424e83d655SAlan Somers _) 34329edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 3444e83d655SAlan Somers SET_OUT_HEADER_LEN(out, attr); 34529edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 34629edc611SAlan Somers out.body.attr.attr.uid = uid; 3474e83d655SAlan Somers }))); 3484e83d655SAlan Somers 3494e83d655SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 3504e83d655SAlan Somers } 3514e83d655SAlan Somers 352a2bdd737SAlan Somers /* 353a2bdd737SAlan Somers * A successful chown by a non-privileged non-owner should clear a file's SUID 354a2bdd737SAlan Somers * bit 355a2bdd737SAlan Somers */ 356a2bdd737SAlan Somers TEST_F(Chown, clear_suid) 357a2bdd737SAlan Somers { 358a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 359a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 360a2bdd737SAlan Somers uint64_t ino = 42; 361a2bdd737SAlan Somers const mode_t oldmode = 06755; 362a2bdd737SAlan Somers const mode_t newmode = 0755; 363a2bdd737SAlan Somers uid_t uid = geteuid(); 364a2bdd737SAlan Somers uint32_t valid = FATTR_UID | FATTR_MODE; 365a2bdd737SAlan Somers 366a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 367a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid); 368a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 369a2bdd737SAlan Somers ResultOf([=](auto in) { 37029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 37129edc611SAlan Somers in.header.nodeid == ino && 37229edc611SAlan Somers in.body.setattr.valid == valid && 37329edc611SAlan Somers in.body.setattr.mode == newmode); 374a2bdd737SAlan Somers }, Eq(true)), 375a2bdd737SAlan Somers _) 37629edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 377a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 37829edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 37929edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 38029edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 381a2bdd737SAlan Somers }))); 382a2bdd737SAlan Somers 383a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 384a2bdd737SAlan Somers } 385a2bdd737SAlan Somers 386a2bdd737SAlan Somers 387474ba6faSAlan Somers /* Only root may change a file's owner */ 388474ba6faSAlan Somers TEST_F(Chown, eperm) 389474ba6faSAlan Somers { 390474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 391474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 392474ba6faSAlan Somers const uint64_t ino = 42; 393474ba6faSAlan Somers const mode_t mode = 0755; 394474ba6faSAlan Somers 395a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 396474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 397474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 398474ba6faSAlan Somers ResultOf([](auto in) { 39929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 400474ba6faSAlan Somers }, Eq(true)), 401474ba6faSAlan Somers _) 402474ba6faSAlan Somers ).Times(0); 403474ba6faSAlan Somers 404474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 405474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 406474ba6faSAlan Somers } 407474ba6faSAlan Somers 408a2bdd737SAlan Somers /* 409a2bdd737SAlan Somers * A successful chgrp by a non-privileged non-owner should clear a file's SUID 410a2bdd737SAlan Somers * bit 411a2bdd737SAlan Somers */ 412a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid) 413a2bdd737SAlan Somers { 414a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 415a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 416a2bdd737SAlan Somers uint64_t ino = 42; 417a2bdd737SAlan Somers const mode_t oldmode = 06755; 418a2bdd737SAlan Somers const mode_t newmode = 0755; 419a2bdd737SAlan Somers uid_t uid = geteuid(); 420a2bdd737SAlan Somers gid_t gid = getegid(); 421a2bdd737SAlan Somers uint32_t valid = FATTR_GID | FATTR_MODE; 422a2bdd737SAlan Somers 423a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 424a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 425a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 426a2bdd737SAlan Somers ResultOf([=](auto in) { 42729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 42829edc611SAlan Somers in.header.nodeid == ino && 42929edc611SAlan Somers in.body.setattr.valid == valid && 43029edc611SAlan Somers in.body.setattr.mode == newmode); 431a2bdd737SAlan Somers }, Eq(true)), 432a2bdd737SAlan Somers _) 43329edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 434a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 43529edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 43629edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 43729edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 438a2bdd737SAlan Somers }))); 439a2bdd737SAlan Somers 440a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno); 441a2bdd737SAlan Somers } 442a2bdd737SAlan Somers 443474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 444474ba6faSAlan Somers TEST_F(Chgrp, eperm) 445474ba6faSAlan Somers { 446474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 447474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 448474ba6faSAlan Somers const uint64_t ino = 42; 449474ba6faSAlan Somers const mode_t mode = 0755; 450474ba6faSAlan Somers uid_t uid; 451474ba6faSAlan Somers gid_t gid, newgid; 452474ba6faSAlan Somers 453474ba6faSAlan Somers uid = geteuid(); 454474ba6faSAlan Somers gid = getegid(); 4558cfb4431SAlan Somers newgid = excluded_group(); 456474ba6faSAlan Somers 457a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 458474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 459474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 460474ba6faSAlan Somers ResultOf([](auto in) { 46129edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 462474ba6faSAlan Somers }, Eq(true)), 463474ba6faSAlan Somers _) 464474ba6faSAlan Somers ).Times(0); 465474ba6faSAlan Somers 466474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 467474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 468474ba6faSAlan Somers } 469474ba6faSAlan Somers 470474ba6faSAlan Somers TEST_F(Chgrp, ok) 471474ba6faSAlan Somers { 472474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 473474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 474474ba6faSAlan Somers const uint64_t ino = 42; 475474ba6faSAlan Somers const mode_t mode = 0755; 476474ba6faSAlan Somers uid_t uid; 477474ba6faSAlan Somers gid_t gid, newgid; 478474ba6faSAlan Somers 479474ba6faSAlan Somers uid = geteuid(); 480474ba6faSAlan Somers gid = 0; 481474ba6faSAlan Somers newgid = getegid(); 482474ba6faSAlan Somers 483a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 484474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 4854e83d655SAlan Somers /* The OS may optimize chgrp by omitting the redundant setattr */ 486474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 487474ba6faSAlan Somers ResultOf([](auto in) { 48829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 48929edc611SAlan Somers in.header.nodeid == ino); 490474ba6faSAlan Somers }, Eq(true)), 491474ba6faSAlan Somers _) 49229edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 493474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 49429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 49529edc611SAlan Somers out.body.attr.attr.uid = uid; 49629edc611SAlan Somers out.body.attr.attr.gid = newgid; 497474ba6faSAlan Somers }))); 498474ba6faSAlan Somers 499474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 500474ba6faSAlan Somers } 501474ba6faSAlan Somers 50292bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 503398c88c7SAlan Somers TEST_F(CopyFileRange, clear_sgid) 50492bbfe1fSAlan Somers { 50592bbfe1fSAlan Somers const char FULLPATH_IN[] = "mountpoint/in.txt"; 50692bbfe1fSAlan Somers const char RELPATH_IN[] = "in.txt"; 50792bbfe1fSAlan Somers const char FULLPATH_OUT[] = "mountpoint/out.txt"; 50892bbfe1fSAlan Somers const char RELPATH_OUT[] = "out.txt"; 50992bbfe1fSAlan Somers struct stat sb; 51092bbfe1fSAlan Somers uint64_t ino_in = 42; 51192bbfe1fSAlan Somers uint64_t ino_out = 43; 51292bbfe1fSAlan Somers mode_t oldmode = 02777; 51392bbfe1fSAlan Somers mode_t newmode = 0777; 51492bbfe1fSAlan Somers off_t fsize = 16; 51592bbfe1fSAlan Somers off_t off_in = 0; 51692bbfe1fSAlan Somers off_t off_out = 8; 51792bbfe1fSAlan Somers off_t len = 8; 51892bbfe1fSAlan Somers int fd_in, fd_out; 51992bbfe1fSAlan Somers 52092bbfe1fSAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 52192bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 52292bbfe1fSAlan Somers UINT64_MAX, 0, 0); 52392bbfe1fSAlan Somers expect_open(ino_in, 0, 1); 52492bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 52592bbfe1fSAlan Somers 1, UINT64_MAX, 0, 0); 52692bbfe1fSAlan Somers expect_open(ino_out, 0, 1); 52792bbfe1fSAlan Somers expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 52892bbfe1fSAlan Somers expect_chmod(ino_out, newmode, fsize); 52992bbfe1fSAlan Somers 53092bbfe1fSAlan Somers fd_in = open(FULLPATH_IN, O_RDONLY); 53192bbfe1fSAlan Somers ASSERT_LE(0, fd_in) << strerror(errno); 53292bbfe1fSAlan Somers fd_out = open(FULLPATH_OUT, O_WRONLY); 53392bbfe1fSAlan Somers ASSERT_LE(0, fd_out) << strerror(errno); 53492bbfe1fSAlan Somers ASSERT_EQ(len, 53592bbfe1fSAlan Somers copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 53692bbfe1fSAlan Somers << strerror(errno); 53792bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 53892bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 53992bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 54092bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 54192bbfe1fSAlan Somers 54292bbfe1fSAlan Somers leak(fd_in); 54392bbfe1fSAlan Somers leak(fd_out); 54492bbfe1fSAlan Somers } 54592bbfe1fSAlan Somers 54692bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 54792bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_suid) 54892bbfe1fSAlan Somers { 54992bbfe1fSAlan Somers const char FULLPATH_IN[] = "mountpoint/in.txt"; 55092bbfe1fSAlan Somers const char RELPATH_IN[] = "in.txt"; 55192bbfe1fSAlan Somers const char FULLPATH_OUT[] = "mountpoint/out.txt"; 55292bbfe1fSAlan Somers const char RELPATH_OUT[] = "out.txt"; 55392bbfe1fSAlan Somers struct stat sb; 55492bbfe1fSAlan Somers uint64_t ino_in = 42; 55592bbfe1fSAlan Somers uint64_t ino_out = 43; 55692bbfe1fSAlan Somers mode_t oldmode = 04777; 55792bbfe1fSAlan Somers mode_t newmode = 0777; 55892bbfe1fSAlan Somers off_t fsize = 16; 55992bbfe1fSAlan Somers off_t off_in = 0; 56092bbfe1fSAlan Somers off_t off_out = 8; 56192bbfe1fSAlan Somers off_t len = 8; 56292bbfe1fSAlan Somers int fd_in, fd_out; 56392bbfe1fSAlan Somers 56492bbfe1fSAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 56592bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 56692bbfe1fSAlan Somers UINT64_MAX, 0, 0); 56792bbfe1fSAlan Somers expect_open(ino_in, 0, 1); 56892bbfe1fSAlan Somers FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 56992bbfe1fSAlan Somers 1, UINT64_MAX, 0, 0); 57092bbfe1fSAlan Somers expect_open(ino_out, 0, 1); 57192bbfe1fSAlan Somers expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 57292bbfe1fSAlan Somers expect_chmod(ino_out, newmode, fsize); 57392bbfe1fSAlan Somers 57492bbfe1fSAlan Somers fd_in = open(FULLPATH_IN, O_RDONLY); 57592bbfe1fSAlan Somers ASSERT_LE(0, fd_in) << strerror(errno); 57692bbfe1fSAlan Somers fd_out = open(FULLPATH_OUT, O_WRONLY); 57792bbfe1fSAlan Somers ASSERT_LE(0, fd_out) << strerror(errno); 57892bbfe1fSAlan Somers ASSERT_EQ(len, 57992bbfe1fSAlan Somers copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 58092bbfe1fSAlan Somers << strerror(errno); 58192bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 58292bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 58392bbfe1fSAlan Somers ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 58492bbfe1fSAlan Somers EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 58592bbfe1fSAlan Somers 58692bbfe1fSAlan Somers leak(fd_in); 58792bbfe1fSAlan Somers leak(fd_out); 58892bbfe1fSAlan Somers } 58992bbfe1fSAlan Somers 590ff4fbdf5SAlan Somers TEST_F(Create, ok) 591ff4fbdf5SAlan Somers { 592ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 593ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 594ff4fbdf5SAlan Somers uint64_t ino = 42; 595ff4fbdf5SAlan Somers int fd; 596ff4fbdf5SAlan Somers 597a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 598a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 599a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 600ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 601ff4fbdf5SAlan Somers 602ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 603d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 6047fc0921dSAlan Somers leak(fd); 605ff4fbdf5SAlan Somers } 606ff4fbdf5SAlan Somers 607ff4fbdf5SAlan Somers TEST_F(Create, eacces) 608ff4fbdf5SAlan Somers { 609ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 610ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 611ff4fbdf5SAlan Somers 612a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 613a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 614a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 615ff4fbdf5SAlan Somers 6168e765737SAlan Somers ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 617ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 618ff4fbdf5SAlan Somers } 619ff4fbdf5SAlan Somers 620ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 621ff4fbdf5SAlan Somers { 622ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 623ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 624ff4fbdf5SAlan Somers uint64_t ino = 42; 625ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 626ff4fbdf5SAlan Somers 627a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 628ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 629ff4fbdf5SAlan Somers 630ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 631ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 632ff4fbdf5SAlan Somers } 633ff4fbdf5SAlan Somers 634ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 635ff4fbdf5SAlan Somers { 636ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 637ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 638ff4fbdf5SAlan Somers uint64_t ino = 42; 639ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 640ff4fbdf5SAlan Somers 641a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 642ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 643ff4fbdf5SAlan Somers expect_removexattr(); 644ff4fbdf5SAlan Somers 645ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 646ff4fbdf5SAlan Somers << strerror(errno); 647ff4fbdf5SAlan Somers } 648ff4fbdf5SAlan Somers 649ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 650ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 651ff4fbdf5SAlan Somers { 652ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 653ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 654ff4fbdf5SAlan Somers uint64_t ino = 42; 655ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 656ff4fbdf5SAlan Somers 657a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 658ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 659ff4fbdf5SAlan Somers 660ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 661ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 662ff4fbdf5SAlan Somers } 663ff4fbdf5SAlan Somers 664d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */ 665d943c93eSAlan Somers TEST_F(Utimensat, utime_now) 666d943c93eSAlan Somers { 667d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 668d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 669d943c93eSAlan Somers const uint64_t ino = 42; 670d943c93eSAlan Somers /* Write permissions for everybody */ 671d943c93eSAlan Somers const mode_t mode = 0666; 672d943c93eSAlan Somers uid_t owner = 0; 673d943c93eSAlan Somers const timespec times[2] = { 674d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 675d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 676d943c93eSAlan Somers }; 677d943c93eSAlan Somers 678a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 679d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 680d943c93eSAlan Somers EXPECT_CALL(*m_mock, process( 681d943c93eSAlan Somers ResultOf([](auto in) { 68229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 68329edc611SAlan Somers in.header.nodeid == ino && 68429edc611SAlan Somers in.body.setattr.valid & FATTR_ATIME && 68529edc611SAlan Somers in.body.setattr.valid & FATTR_MTIME); 686d943c93eSAlan Somers }, Eq(true)), 687d943c93eSAlan Somers _) 68829edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 689d943c93eSAlan Somers SET_OUT_HEADER_LEN(out, attr); 69029edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 691d943c93eSAlan Somers }))); 692d943c93eSAlan Somers 693d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 694d943c93eSAlan Somers << strerror(errno); 695d943c93eSAlan Somers } 696d943c93eSAlan Somers 697d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */ 698d943c93eSAlan Somers TEST_F(Utimensat, utime_omit) 699d943c93eSAlan Somers { 700d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 701d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 702d943c93eSAlan Somers const uint64_t ino = 42; 703d943c93eSAlan Somers /* Write permissions for no one */ 704d943c93eSAlan Somers const mode_t mode = 0444; 705d943c93eSAlan Somers uid_t owner = 0; 706d943c93eSAlan Somers const timespec times[2] = { 707d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 708d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 709d943c93eSAlan Somers }; 710d943c93eSAlan Somers 711a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 712d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 713d943c93eSAlan Somers 714d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 715d943c93eSAlan Somers << strerror(errno); 716d943c93eSAlan Somers } 717d943c93eSAlan Somers 718ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 719ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 720ff4fbdf5SAlan Somers { 721ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 722ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 723ff4fbdf5SAlan Somers uint64_t ino = 42; 724ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 725ff4fbdf5SAlan Somers 726a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 727ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 728ff4fbdf5SAlan Somers expect_removexattr(); 729ff4fbdf5SAlan Somers 730ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 731ff4fbdf5SAlan Somers << strerror(errno); 732ff4fbdf5SAlan Somers } 733ff4fbdf5SAlan Somers 734ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 735ff4fbdf5SAlan Somers { 736ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 737ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 738ff4fbdf5SAlan Somers uint64_t ino = 42; 739ff4fbdf5SAlan Somers char data[80]; 740ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 741ff4fbdf5SAlan Somers 742a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 743ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 744ff4fbdf5SAlan Somers 745ff4fbdf5SAlan Somers ASSERT_EQ(-1, 746ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 747ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 748ff4fbdf5SAlan Somers } 749ff4fbdf5SAlan Somers 750ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 751ff4fbdf5SAlan Somers { 752ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 753ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 754ff4fbdf5SAlan Somers uint64_t ino = 42; 755ff4fbdf5SAlan Somers char data[80]; 756ff4fbdf5SAlan Somers const char value[] = "whatever"; 757ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 758ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 759ff4fbdf5SAlan Somers ssize_t r; 760ff4fbdf5SAlan Somers 761a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 762ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 763ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 764ff4fbdf5SAlan Somers expect_getxattr( 76529edc611SAlan Somers ReturnImmediate([&](auto in __unused, auto& out) { 76629edc611SAlan Somers memcpy((void*)out.body.bytes, value, value_len); 76729edc611SAlan Somers out.header.len = sizeof(out.header) + value_len; 768ff4fbdf5SAlan Somers }) 769ff4fbdf5SAlan Somers ); 770ff4fbdf5SAlan Somers 771ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 772ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 773ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 774ff4fbdf5SAlan Somers } 775ff4fbdf5SAlan Somers 776ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 777ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 778ff4fbdf5SAlan Somers { 779ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 780ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 781ff4fbdf5SAlan Somers uint64_t ino = 42; 782ff4fbdf5SAlan Somers char data[80]; 783ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 784ff4fbdf5SAlan Somers 785a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 786ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 787ff4fbdf5SAlan Somers 788ff4fbdf5SAlan Somers ASSERT_EQ(-1, 789ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 790ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 791ff4fbdf5SAlan Somers } 792ff4fbdf5SAlan Somers 793ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 794ff4fbdf5SAlan Somers { 795ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 796ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 797ff4fbdf5SAlan Somers uint64_t ino = 42; 798ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 799ff4fbdf5SAlan Somers 800a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 801ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 802ff4fbdf5SAlan Somers 803ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 804ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 805ff4fbdf5SAlan Somers } 806ff4fbdf5SAlan Somers 807ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 808ff4fbdf5SAlan Somers { 809ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 810ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 811ff4fbdf5SAlan Somers uint64_t ino = 42; 812ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 813ff4fbdf5SAlan Somers 814a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 815ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 816ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 817ff4fbdf5SAlan Somers expect_listxattr(); 818ff4fbdf5SAlan Somers 819ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 820ff4fbdf5SAlan Somers << strerror(errno); 821ff4fbdf5SAlan Somers } 822ff4fbdf5SAlan Somers 823ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 824ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 825ff4fbdf5SAlan Somers { 826ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 827ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 828ff4fbdf5SAlan Somers uint64_t ino = 42; 829ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 830ff4fbdf5SAlan Somers 831a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 832ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 833ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 834ff4fbdf5SAlan Somers 835ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 836ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 837ff4fbdf5SAlan Somers } 838ff4fbdf5SAlan Somers 839*89d57b94SAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 840*89d57b94SAlan Somers TEST_F(Fspacectl, clear_sgid) 841*89d57b94SAlan Somers { 842*89d57b94SAlan Somers const char FULLPATH[] = "mountpoint/file.txt"; 843*89d57b94SAlan Somers const char RELPATH[] = "file.txt"; 844*89d57b94SAlan Somers struct stat sb; 845*89d57b94SAlan Somers struct spacectl_range rqsr; 846*89d57b94SAlan Somers uint64_t ino = 42; 847*89d57b94SAlan Somers mode_t oldmode = 02777; 848*89d57b94SAlan Somers mode_t newmode = 0777; 849*89d57b94SAlan Somers off_t fsize = 16; 850*89d57b94SAlan Somers off_t off = 8; 851*89d57b94SAlan Somers off_t len = 8; 852*89d57b94SAlan Somers int fd; 853*89d57b94SAlan Somers 854*89d57b94SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 855*89d57b94SAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 856*89d57b94SAlan Somers 1, UINT64_MAX, 0, 0); 857*89d57b94SAlan Somers expect_open(ino, 0, 1); 858*89d57b94SAlan Somers expect_fallocate(ino, off, len, 859*89d57b94SAlan Somers FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 860*89d57b94SAlan Somers expect_chmod(ino, newmode, fsize); 861*89d57b94SAlan Somers 862*89d57b94SAlan Somers fd = open(FULLPATH, O_WRONLY); 863*89d57b94SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 864*89d57b94SAlan Somers rqsr.r_len = len; 865*89d57b94SAlan Somers rqsr.r_offset = off; 866*89d57b94SAlan Somers EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 867*89d57b94SAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 868*89d57b94SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 869*89d57b94SAlan Somers 870*89d57b94SAlan Somers leak(fd); 871*89d57b94SAlan Somers } 872*89d57b94SAlan Somers 873*89d57b94SAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 874*89d57b94SAlan Somers TEST_F(Fspacectl, clear_suid) 875*89d57b94SAlan Somers { 876*89d57b94SAlan Somers const char FULLPATH[] = "mountpoint/file.txt"; 877*89d57b94SAlan Somers const char RELPATH[] = "file.txt"; 878*89d57b94SAlan Somers struct stat sb; 879*89d57b94SAlan Somers struct spacectl_range rqsr; 880*89d57b94SAlan Somers uint64_t ino = 42; 881*89d57b94SAlan Somers mode_t oldmode = 04777; 882*89d57b94SAlan Somers mode_t newmode = 0777; 883*89d57b94SAlan Somers off_t fsize = 16; 884*89d57b94SAlan Somers off_t off = 8; 885*89d57b94SAlan Somers off_t len = 8; 886*89d57b94SAlan Somers int fd; 887*89d57b94SAlan Somers 888*89d57b94SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 889*89d57b94SAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 890*89d57b94SAlan Somers 1, UINT64_MAX, 0, 0); 891*89d57b94SAlan Somers expect_open(ino, 0, 1); 892*89d57b94SAlan Somers expect_fallocate(ino, off, len, 893*89d57b94SAlan Somers FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 894*89d57b94SAlan Somers expect_chmod(ino, newmode, fsize); 895*89d57b94SAlan Somers 896*89d57b94SAlan Somers fd = open(FULLPATH, O_WRONLY); 897*89d57b94SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 898*89d57b94SAlan Somers rqsr.r_len = len; 899*89d57b94SAlan Somers rqsr.r_offset = off; 900*89d57b94SAlan Somers EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 901*89d57b94SAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 902*89d57b94SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 903*89d57b94SAlan Somers 904*89d57b94SAlan Somers leak(fd); 905*89d57b94SAlan Somers } 906*89d57b94SAlan Somers 907*89d57b94SAlan Somers /* 908*89d57b94SAlan Somers * fspacectl() of a file without writable permissions should succeed as 909*89d57b94SAlan Somers * long as the file descriptor is writable. This is important when combined 910*89d57b94SAlan Somers * with O_CREAT 911*89d57b94SAlan Somers */ 912*89d57b94SAlan Somers TEST_F(Fspacectl, posix_fallocate_of_newly_created_file) 913*89d57b94SAlan Somers { 914*89d57b94SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 915*89d57b94SAlan Somers const char RELPATH[] = "some_file.txt"; 916*89d57b94SAlan Somers struct spacectl_range rqsr; 917*89d57b94SAlan Somers const uint64_t ino = 42; 918*89d57b94SAlan Somers off_t off = 8; 919*89d57b94SAlan Somers off_t len = 8; 920*89d57b94SAlan Somers int fd; 921*89d57b94SAlan Somers 922*89d57b94SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 923*89d57b94SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 924*89d57b94SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 925*89d57b94SAlan Somers expect_create(RELPATH, ino); 926*89d57b94SAlan Somers expect_fallocate(ino, off, len, 927*89d57b94SAlan Somers FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 928*89d57b94SAlan Somers 929*89d57b94SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 930*89d57b94SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 931*89d57b94SAlan Somers rqsr.r_len = len; 932*89d57b94SAlan Somers rqsr.r_offset = off; 933*89d57b94SAlan Somers EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 934*89d57b94SAlan Somers leak(fd); 935*89d57b94SAlan Somers } 936*89d57b94SAlan Somers 937ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 938ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 939ff4fbdf5SAlan Somers { 940ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 941ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 942ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 943ff4fbdf5SAlan Somers 944a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 945ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 946ff4fbdf5SAlan Somers 947ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 948ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 949ff4fbdf5SAlan Somers } 950ff4fbdf5SAlan Somers 951ff4fbdf5SAlan Somers TEST_F(Open, eacces) 952ff4fbdf5SAlan Somers { 953ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 954ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 955ff4fbdf5SAlan Somers uint64_t ino = 42; 956ff4fbdf5SAlan Somers 957a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 958ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 959ff4fbdf5SAlan Somers 9604ca1c0b7SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_RDWR)); 961ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 962ff4fbdf5SAlan Somers } 963ff4fbdf5SAlan Somers 9649821f1d3SAlan Somers TEST_F(Open, ok) 9659821f1d3SAlan Somers { 9669821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 9679821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 9689821f1d3SAlan Somers uint64_t ino = 42; 9699821f1d3SAlan Somers int fd; 9709821f1d3SAlan Somers 971a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 972ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 9739821f1d3SAlan Somers expect_open(ino, 0, 1); 9749821f1d3SAlan Somers 9759821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 976d2621689SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 9777fc0921dSAlan Somers leak(fd); 9789821f1d3SAlan Somers } 9799821f1d3SAlan Somers 980398c88c7SAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 981398c88c7SAlan Somers TEST_F(PosixFallocate, clear_sgid) 982398c88c7SAlan Somers { 983398c88c7SAlan Somers const char FULLPATH[] = "mountpoint/file.txt"; 984398c88c7SAlan Somers const char RELPATH[] = "file.txt"; 985398c88c7SAlan Somers struct stat sb; 986398c88c7SAlan Somers uint64_t ino = 42; 987398c88c7SAlan Somers mode_t oldmode = 02777; 988398c88c7SAlan Somers mode_t newmode = 0777; 989398c88c7SAlan Somers off_t fsize = 16; 990398c88c7SAlan Somers off_t off = 8; 991398c88c7SAlan Somers off_t len = 8; 992398c88c7SAlan Somers int fd; 993398c88c7SAlan Somers 994398c88c7SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 995398c88c7SAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 996398c88c7SAlan Somers 1, UINT64_MAX, 0, 0); 997398c88c7SAlan Somers expect_open(ino, 0, 1); 998398c88c7SAlan Somers expect_fallocate(ino, off, len, 0, 0); 999398c88c7SAlan Somers expect_chmod(ino, newmode, fsize); 1000398c88c7SAlan Somers 1001398c88c7SAlan Somers fd = open(FULLPATH, O_WRONLY); 1002398c88c7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1003398c88c7SAlan Somers EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 1004398c88c7SAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1005398c88c7SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1006398c88c7SAlan Somers 1007398c88c7SAlan Somers leak(fd); 1008398c88c7SAlan Somers } 1009398c88c7SAlan Somers 1010398c88c7SAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1011398c88c7SAlan Somers TEST_F(PosixFallocate, clear_suid) 1012398c88c7SAlan Somers { 1013398c88c7SAlan Somers const char FULLPATH[] = "mountpoint/file.txt"; 1014398c88c7SAlan Somers const char RELPATH[] = "file.txt"; 1015398c88c7SAlan Somers struct stat sb; 1016398c88c7SAlan Somers uint64_t ino = 42; 1017398c88c7SAlan Somers mode_t oldmode = 04777; 1018398c88c7SAlan Somers mode_t newmode = 0777; 1019398c88c7SAlan Somers off_t fsize = 16; 1020398c88c7SAlan Somers off_t off = 8; 1021398c88c7SAlan Somers off_t len = 8; 1022398c88c7SAlan Somers int fd; 1023398c88c7SAlan Somers 1024398c88c7SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1025398c88c7SAlan Somers FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 1026398c88c7SAlan Somers 1, UINT64_MAX, 0, 0); 1027398c88c7SAlan Somers expect_open(ino, 0, 1); 1028398c88c7SAlan Somers expect_fallocate(ino, off, len, 0, 0); 1029398c88c7SAlan Somers expect_chmod(ino, newmode, fsize); 1030398c88c7SAlan Somers 1031398c88c7SAlan Somers fd = open(FULLPATH, O_WRONLY); 1032398c88c7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1033398c88c7SAlan Somers EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 1034398c88c7SAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1035398c88c7SAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1036398c88c7SAlan Somers 1037398c88c7SAlan Somers leak(fd); 1038398c88c7SAlan Somers } 1039398c88c7SAlan Somers 1040398c88c7SAlan Somers /* 1041*89d57b94SAlan Somers * posix_fallocate() of a file without writable permissions should succeed as 1042398c88c7SAlan Somers * long as the file descriptor is writable. This is important when combined 1043398c88c7SAlan Somers * with O_CREAT 1044398c88c7SAlan Somers */ 1045398c88c7SAlan Somers TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file) 1046398c88c7SAlan Somers { 1047398c88c7SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1048398c88c7SAlan Somers const char RELPATH[] = "some_file.txt"; 1049398c88c7SAlan Somers const uint64_t ino = 42; 1050398c88c7SAlan Somers off_t off = 8; 1051398c88c7SAlan Somers off_t len = 8; 1052398c88c7SAlan Somers int fd; 1053398c88c7SAlan Somers 1054398c88c7SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1055398c88c7SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1056398c88c7SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 1057398c88c7SAlan Somers expect_create(RELPATH, ino); 1058398c88c7SAlan Somers expect_fallocate(ino, off, len, 0, 0); 1059398c88c7SAlan Somers 1060398c88c7SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 1061398c88c7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1062398c88c7SAlan Somers EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 1063398c88c7SAlan Somers leak(fd); 1064398c88c7SAlan Somers } 1065398c88c7SAlan Somers 1066ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 1067ff4fbdf5SAlan Somers { 1068ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1069ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 1070ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1071ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1072ff4fbdf5SAlan Somers uint64_t ino = 42; 1073ff4fbdf5SAlan Somers 1074a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0); 1075ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1076a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1077ff4fbdf5SAlan Somers .Times(AnyNumber()) 1078ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 1079ff4fbdf5SAlan Somers 1080ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1081ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1082ff4fbdf5SAlan Somers } 1083ff4fbdf5SAlan Somers 1084ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 1085ff4fbdf5SAlan Somers { 1086ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1087ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 1088ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1089ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1090ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1091ff4fbdf5SAlan Somers uint64_t src_ino = 42; 1092ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 1093ff4fbdf5SAlan Somers 1094a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1095ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1096ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 1097ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 1098ff4fbdf5SAlan Somers 1099ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1100ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1101ff4fbdf5SAlan Somers } 1102ff4fbdf5SAlan Somers 1103ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 1104ff4fbdf5SAlan Somers { 1105ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1106ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 1107ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1108ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1109ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1110ff4fbdf5SAlan Somers uint64_t src_ino = 42; 1111ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 1112ff4fbdf5SAlan Somers 1113a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1114ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1115ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 1116ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 1117ff4fbdf5SAlan Somers 1118ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1119ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1120ff4fbdf5SAlan Somers } 1121ff4fbdf5SAlan Somers 11226124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 1123ff4fbdf5SAlan Somers { 1124ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1125ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1126ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1127ff4fbdf5SAlan Somers uint64_t ino = 42; 1128ff4fbdf5SAlan Somers 1129a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1130ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1131ff4fbdf5SAlan Somers 1132ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1133ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1134ff4fbdf5SAlan Somers } 1135ff4fbdf5SAlan Somers 11368e45ec4eSAlan Somers /* 11378e45ec4eSAlan Somers * A user cannot move out a subdirectory that he does not own, because that 11388e45ec4eSAlan Somers * would require changing the subdirectory's ".." dirent 11398e45ec4eSAlan Somers */ 11408e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory) 11418e45ec4eSAlan Somers { 11428e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 11438e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 11448e45ec4eSAlan Somers const char RELDSTDIR[] = "d"; 11458e45ec4eSAlan Somers const char RELDST[] = "dst"; 11468e45ec4eSAlan Somers const char RELSRC[] = "src"; 11478e45ec4eSAlan Somers uint64_t ino = 42; 11488e45ec4eSAlan Somers uint64_t dstdir_ino = 43; 11498e45ec4eSAlan Somers 1150a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 11518e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 11528e45ec4eSAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 11538e45ec4eSAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 11548e45ec4eSAlan Somers 11558e45ec4eSAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 11568e45ec4eSAlan Somers ASSERT_EQ(EACCES, errno); 11578e45ec4eSAlan Somers } 11588e45ec4eSAlan Somers 11598e45ec4eSAlan Somers /* 11608e45ec4eSAlan Somers * A user _can_ rename a subdirectory to which he lacks write permissions, if 11618e45ec4eSAlan Somers * it will keep the same parent 11628e45ec4eSAlan Somers */ 11638e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir) 11648e45ec4eSAlan Somers { 11658e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/dst"; 11668e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 11678e45ec4eSAlan Somers const char RELDST[] = "dst"; 11688e45ec4eSAlan Somers const char RELSRC[] = "src"; 11698e45ec4eSAlan Somers uint64_t ino = 42; 11708e45ec4eSAlan Somers 1171a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 11728e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 1173a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1174a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 11758e45ec4eSAlan Somers expect_rename(0); 11768e45ec4eSAlan Somers 11778e45ec4eSAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 11788e45ec4eSAlan Somers } 11798e45ec4eSAlan Somers 11806124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 1181ff4fbdf5SAlan Somers { 1182ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 1183ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 11846124fd71SAlan Somers const char RELDST[] = "dst"; 1185ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1186ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1187ff4fbdf5SAlan Somers uint64_t src_ino = 42; 1188ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 1189ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 1190ff4fbdf5SAlan Somers 1191a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1192ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1193ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 11946124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 119529edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 11966124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 119729edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 119829edc611SAlan Somers out.body.entry.nodeid = dst_ino; 119929edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 120029edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 120129edc611SAlan Somers out.body.entry.attr.uid = 0; 12026124fd71SAlan Somers }))); 1203ff4fbdf5SAlan Somers 1204ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1205ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1206ff4fbdf5SAlan Somers } 1207ff4fbdf5SAlan Somers 1208ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 1209ff4fbdf5SAlan Somers TEST_F(Rename, ok) 1210ff4fbdf5SAlan Somers { 1211ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 1212ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1213ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1214ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1215ff4fbdf5SAlan Somers // The inode of the already-existing destination file 1216ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 1217ff4fbdf5SAlan Somers uint64_t ino = 42; 1218ff4fbdf5SAlan Somers 1219a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 1220ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1221ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 1222ff4fbdf5SAlan Somers expect_rename(0); 1223ff4fbdf5SAlan Somers 1224ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1225ff4fbdf5SAlan Somers } 1226ff4fbdf5SAlan Somers 1227ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 1228ff4fbdf5SAlan Somers { 1229ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 1230ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 1231ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 1232ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 1233ff4fbdf5SAlan Somers uint64_t ino = 42; 1234ff4fbdf5SAlan Somers 1235a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1236ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1237a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1238a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 1239ff4fbdf5SAlan Somers expect_rename(0); 1240ff4fbdf5SAlan Somers 1241ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1242ff4fbdf5SAlan Somers } 1243ff4fbdf5SAlan Somers 1244ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 1245ff4fbdf5SAlan Somers { 1246ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1247ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1248ff4fbdf5SAlan Somers const uint64_t ino = 42; 1249ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 1250ff4fbdf5SAlan Somers const mode_t newmode = 0644; 1251ff4fbdf5SAlan Somers 1252a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1253ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1254ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 1255ff4fbdf5SAlan Somers ResultOf([](auto in) { 125629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 125729edc611SAlan Somers in.header.nodeid == ino && 125829edc611SAlan Somers in.body.setattr.mode == newmode); 1259ff4fbdf5SAlan Somers }, Eq(true)), 1260ff4fbdf5SAlan Somers _) 126129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 1262ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 126329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 1264ff4fbdf5SAlan Somers }))); 1265ff4fbdf5SAlan Somers 1266ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 1267ff4fbdf5SAlan Somers } 1268ff4fbdf5SAlan Somers 1269ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 1270ff4fbdf5SAlan Somers { 1271ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1272ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1273ff4fbdf5SAlan Somers const uint64_t ino = 42; 1274ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 1275ff4fbdf5SAlan Somers const mode_t newmode = 0644; 1276ff4fbdf5SAlan Somers 1277a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1278ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 1279ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 1280ff4fbdf5SAlan Somers ResultOf([](auto in) { 128129edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1282ff4fbdf5SAlan Somers }, Eq(true)), 1283ff4fbdf5SAlan Somers _) 1284ff4fbdf5SAlan Somers ).Times(0); 1285ff4fbdf5SAlan Somers 1286ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1287ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 1288ff4fbdf5SAlan Somers } 1289ff4fbdf5SAlan Somers 12908cfb4431SAlan Somers /* 12913fa12789SAlan Somers * ftruncate() of a file without writable permissions should succeed as long as 12923fa12789SAlan Somers * the file descriptor is writable. This is important when combined with 12933fa12789SAlan Somers * O_CREAT 12943fa12789SAlan Somers */ 12953fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file) 12963fa12789SAlan Somers { 12973fa12789SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 12983fa12789SAlan Somers const char RELPATH[] = "some_file.txt"; 12993fa12789SAlan Somers const uint64_t ino = 42; 13003fa12789SAlan Somers const mode_t mode = 0000; 13013fa12789SAlan Somers int fd; 13023fa12789SAlan Somers 1303a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1304a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1305a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 13063fa12789SAlan Somers expect_create(RELPATH, ino); 13073fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 13083fa12789SAlan Somers ResultOf([](auto in) { 130929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 131029edc611SAlan Somers in.header.nodeid == ino && 131129edc611SAlan Somers (in.body.setattr.valid & FATTR_SIZE)); 13123fa12789SAlan Somers }, Eq(true)), 13133fa12789SAlan Somers _) 131429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 13153fa12789SAlan Somers SET_OUT_HEADER_LEN(out, attr); 131629edc611SAlan Somers out.body.attr.attr.ino = ino; 131729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 131829edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 13193fa12789SAlan Somers }))); 13203fa12789SAlan Somers 13213fa12789SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 13223fa12789SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 13233fa12789SAlan Somers ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 13247fc0921dSAlan Somers leak(fd); 13253fa12789SAlan Somers } 13263fa12789SAlan Somers 13273fa12789SAlan Somers /* 13288cfb4431SAlan Somers * Setting the sgid bit should fail for an unprivileged user who doesn't belong 13298cfb4431SAlan Somers * to the file's group 13308cfb4431SAlan Somers */ 13318cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member) 13328cfb4431SAlan Somers { 13338cfb4431SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 13348cfb4431SAlan Somers const char RELPATH[] = "some_file.txt"; 13358cfb4431SAlan Somers const uint64_t ino = 42; 13368cfb4431SAlan Somers const mode_t oldmode = 0755; 13378cfb4431SAlan Somers const mode_t newmode = 02755; 13388cfb4431SAlan Somers uid_t uid = geteuid(); 13398cfb4431SAlan Somers gid_t gid = excluded_group(); 13408cfb4431SAlan Somers 1341a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 13428cfb4431SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 13438cfb4431SAlan Somers EXPECT_CALL(*m_mock, process( 13448cfb4431SAlan Somers ResultOf([](auto in) { 134529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 13468cfb4431SAlan Somers }, Eq(true)), 13478cfb4431SAlan Somers _) 13488cfb4431SAlan Somers ).Times(0); 13498cfb4431SAlan Somers 13508cfb4431SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 13518cfb4431SAlan Somers EXPECT_EQ(EPERM, errno); 13528cfb4431SAlan Somers } 13538cfb4431SAlan Somers 1354e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 1355e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 1356e5ff3a7eSAlan Somers { 1357e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1358e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 1359e5ff3a7eSAlan Somers const uint64_t ino = 42; 1360e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 1361e5ff3a7eSAlan Somers const mode_t newmode = 01644; 1362e5ff3a7eSAlan Somers 1363a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1364e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1365e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 1366e5ff3a7eSAlan Somers ResultOf([](auto in) { 136729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1368e5ff3a7eSAlan Somers }, Eq(true)), 1369e5ff3a7eSAlan Somers _) 1370e5ff3a7eSAlan Somers ).Times(0); 1371e5ff3a7eSAlan Somers 1372e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1373e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 1374e5ff3a7eSAlan Somers } 1375e5ff3a7eSAlan Somers 1376ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 1377ff4fbdf5SAlan Somers { 1378ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1379ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1380ff4fbdf5SAlan Somers uint64_t ino = 42; 1381ff4fbdf5SAlan Somers const char value[] = "whatever"; 1382ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1383ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1384ff4fbdf5SAlan Somers ssize_t r; 1385ff4fbdf5SAlan Somers 1386a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1387ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1388ff4fbdf5SAlan Somers expect_setxattr(0); 1389ff4fbdf5SAlan Somers 13905a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 13915a0b9a27SAlan Somers value_len); 1392ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1393ff4fbdf5SAlan Somers } 1394ff4fbdf5SAlan Somers 1395ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 1396ff4fbdf5SAlan Somers { 1397ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1398ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1399ff4fbdf5SAlan Somers uint64_t ino = 42; 1400ff4fbdf5SAlan Somers const char value[] = "whatever"; 1401ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1402ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1403ff4fbdf5SAlan Somers 1404a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1405ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1406ff4fbdf5SAlan Somers 14075a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 14085a0b9a27SAlan Somers value_len)); 1409ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1410ff4fbdf5SAlan Somers } 1411ff4fbdf5SAlan Somers 1412ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 1413ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 1414ff4fbdf5SAlan Somers { 1415ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1416ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1417ff4fbdf5SAlan Somers uint64_t ino = 42; 1418ff4fbdf5SAlan Somers const char value[] = "whatever"; 1419ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1420ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 1421ff4fbdf5SAlan Somers 1422a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1423ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1424ff4fbdf5SAlan Somers 14255a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 14265a0b9a27SAlan Somers value_len)); 1427ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1428ff4fbdf5SAlan Somers } 1429ff4fbdf5SAlan Somers 1430ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 1431ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 1432ff4fbdf5SAlan Somers { 1433ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1434ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1435ff4fbdf5SAlan Somers uint64_t ino = 42; 1436ff4fbdf5SAlan Somers const char value[] = "whatever"; 1437ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1438ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1439ff4fbdf5SAlan Somers ssize_t r; 1440ff4fbdf5SAlan Somers 1441a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1442ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1443ff4fbdf5SAlan Somers expect_setxattr(0); 1444ff4fbdf5SAlan Somers 14455a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 14465a0b9a27SAlan Somers value_len); 1447ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1448ff4fbdf5SAlan Somers } 1449ff4fbdf5SAlan Somers 1450ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 14519821f1d3SAlan Somers { 14529821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 14539821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 14549821f1d3SAlan Somers uint64_t ino = 42; 1455331884f2SAlan Somers sem_t sem; 1456331884f2SAlan Somers 1457331884f2SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 14589821f1d3SAlan Somers 1459a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1460ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1461a34cdd26SAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, 0); 1462331884f2SAlan Somers expect_forget(ino, 1, &sem); 14639821f1d3SAlan Somers 1464ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1465331884f2SAlan Somers 1466331884f2SAlan Somers sem_wait(&sem); 1467331884f2SAlan Somers sem_destroy(&sem); 1468ff4fbdf5SAlan Somers } 1469ff4fbdf5SAlan Somers 14706124fd71SAlan Somers /* 14716124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 14726124fd71SAlan Somers * in VOP_LOOKUP. 14736124fd71SAlan Somers * 14746124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 14756124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 14766124fd71SAlan Somers */ 14776124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 14786124fd71SAlan Somers { 14796124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 14806124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 14816124fd71SAlan Somers uint64_t ino = 42; 14826124fd71SAlan Somers 1483a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1484a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 14856124fd71SAlan Somers .Times(AnyNumber()) 14866124fd71SAlan Somers .WillRepeatedly(Invoke( 148729edc611SAlan Somers ReturnImmediate([=](auto i __unused, auto& out) { 14886124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 148929edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 149029edc611SAlan Somers out.body.entry.nodeid = ino; 149129edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 14926124fd71SAlan Somers })) 14936124fd71SAlan Somers ); 14946124fd71SAlan Somers 14956124fd71SAlan Somers /* Fill name cache */ 14966124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 14976124fd71SAlan Somers /* Despite cached name , unlink should fail */ 14986124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 14996124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 15006124fd71SAlan Somers } 15016124fd71SAlan Somers 1502ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 1503ff4fbdf5SAlan Somers { 1504ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1505ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1506ff4fbdf5SAlan Somers uint64_t ino = 42; 1507ff4fbdf5SAlan Somers 1508a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1509ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1510ff4fbdf5SAlan Somers 1511ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1512ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1513ff4fbdf5SAlan Somers } 1514ff4fbdf5SAlan Somers 15156124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 1516ff4fbdf5SAlan Somers { 1517ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1518ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1519ff4fbdf5SAlan Somers uint64_t ino = 42; 1520ff4fbdf5SAlan Somers 1521a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1); 1522ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1523ff4fbdf5SAlan Somers 1524ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1525ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 15269821f1d3SAlan Somers } 1527a90e32deSAlan Somers 1528a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1529a90e32deSAlan Somers TEST_F(Write, clear_suid) 1530a90e32deSAlan Somers { 1531a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1532a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1533a90e32deSAlan Somers struct stat sb; 1534a90e32deSAlan Somers uint64_t ino = 42; 1535a90e32deSAlan Somers mode_t oldmode = 04777; 1536a90e32deSAlan Somers mode_t newmode = 0777; 1537a90e32deSAlan Somers char wbuf[1] = {'x'}; 1538a90e32deSAlan Somers int fd; 1539a90e32deSAlan Somers 1540a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1541a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1542a90e32deSAlan Somers expect_open(ino, 0, 1); 1543bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 154418a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1545a90e32deSAlan Somers 1546a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1547a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1548a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1549a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1550a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 15517fc0921dSAlan Somers leak(fd); 1552a90e32deSAlan Somers } 1553a90e32deSAlan Somers 1554a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 1555a90e32deSAlan Somers TEST_F(Write, clear_sgid) 1556a90e32deSAlan Somers { 1557a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1558a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1559a90e32deSAlan Somers struct stat sb; 1560a90e32deSAlan Somers uint64_t ino = 42; 1561a90e32deSAlan Somers mode_t oldmode = 02777; 1562a90e32deSAlan Somers mode_t newmode = 0777; 1563a90e32deSAlan Somers char wbuf[1] = {'x'}; 1564a90e32deSAlan Somers int fd; 1565a90e32deSAlan Somers 1566a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1567a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1568a90e32deSAlan Somers expect_open(ino, 0, 1); 1569bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 157018a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1571a90e32deSAlan Somers 1572a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1573a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1574a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1575a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1576a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 15777fc0921dSAlan Somers leak(fd); 1578a90e32deSAlan Somers } 157918a2264eSAlan Somers 158018a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic 158118a2264eSAlan Somers * 158218a2264eSAlan Somers * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 158318a2264eSAlan Somers * may panic. That happens if the FUSE_SETATTR response indicates that the 158418a2264eSAlan Somers * file's size has changed since the write. 158518a2264eSAlan Somers */ 158618a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid) 158718a2264eSAlan Somers { 158818a2264eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 158918a2264eSAlan Somers const char RELPATH[] = "some_file.txt"; 159018a2264eSAlan Somers uint64_t ino = 42; 159118a2264eSAlan Somers mode_t oldmode = 04777; 159218a2264eSAlan Somers mode_t newmode = 0777; 159318a2264eSAlan Somers char wbuf[1] = {'x'}; 159418a2264eSAlan Somers int fd; 159518a2264eSAlan Somers 1596a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 159718a2264eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 159818a2264eSAlan Somers expect_open(ino, 0, 1); 1599bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 160018a2264eSAlan Somers /* XXX Return a smaller file size than what we just wrote! */ 160118a2264eSAlan Somers expect_chmod(ino, newmode, 0); 160218a2264eSAlan Somers 160318a2264eSAlan Somers fd = open(FULLPATH, O_WRONLY); 160418a2264eSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 160518a2264eSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 16067fc0921dSAlan Somers leak(fd); 160718a2264eSAlan Somers } 1608