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> 43*331884f2SAlan 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 112ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times, 113474ba6faSAlan Somers uid_t uid = 0, gid_t gid = 0) 1149821f1d3SAlan Somers { 115ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 116ff4fbdf5SAlan Somers ResultOf([=](auto in) { 11729edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR && 11829edc611SAlan Somers in.header.nodeid == ino); 119ff4fbdf5SAlan Somers }, Eq(true)), 120ff4fbdf5SAlan Somers _) 121ff4fbdf5SAlan Somers ).Times(times) 12229edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 123ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 12429edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 12529edc611SAlan Somers out.body.attr.attr.mode = mode; 12629edc611SAlan Somers out.body.attr.attr.size = 0; 12729edc611SAlan Somers out.body.attr.attr.uid = uid; 12829edc611SAlan Somers out.body.attr.attr.uid = gid; 12929edc611SAlan Somers out.body.attr.attr_valid = attr_valid; 130ff4fbdf5SAlan Somers }))); 131ff4fbdf5SAlan Somers } 132ff4fbdf5SAlan Somers 133ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 134474ba6faSAlan Somers uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0) 135ff4fbdf5SAlan Somers { 136474ba6faSAlan Somers FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid); 1379821f1d3SAlan Somers } 1389821f1d3SAlan Somers 1399821f1d3SAlan Somers }; 1409821f1d3SAlan Somers 1419821f1d3SAlan Somers class Access: public DefaultPermissions {}; 142474ba6faSAlan Somers class Chown: public DefaultPermissions {}; 143474ba6faSAlan Somers class Chgrp: public DefaultPermissions {}; 144ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1459821f1d3SAlan Somers class Open: public DefaultPermissions {}; 146ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 147ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 148d943c93eSAlan Somers class Utimensat: public DefaultPermissions {}; 149a90e32deSAlan Somers class Write: public DefaultPermissions {}; 1509821f1d3SAlan Somers 151ff4fbdf5SAlan Somers /* 152ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 153ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 154ff4fbdf5SAlan Somers * VOP_LOOKUP) 155ff4fbdf5SAlan Somers */ 1563fa12789SAlan Somers class Create: public DefaultPermissions {}; 157ff4fbdf5SAlan Somers 158ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 159ff4fbdf5SAlan Somers public: 160ff4fbdf5SAlan Somers void expect_removexattr() 161ff4fbdf5SAlan Somers { 162ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 163ff4fbdf5SAlan Somers ResultOf([=](auto in) { 16429edc611SAlan Somers return (in.header.opcode == FUSE_REMOVEXATTR); 165ff4fbdf5SAlan Somers }, Eq(true)), 166ff4fbdf5SAlan Somers _) 167ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 168ff4fbdf5SAlan Somers } 169ff4fbdf5SAlan Somers }; 170ff4fbdf5SAlan Somers 171ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 172ff4fbdf5SAlan Somers public: 173ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 174ff4fbdf5SAlan Somers { 175ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 176ff4fbdf5SAlan Somers ResultOf([=](auto in) { 17729edc611SAlan Somers return (in.header.opcode == FUSE_GETXATTR); 178ff4fbdf5SAlan Somers }, Eq(true)), 179ff4fbdf5SAlan Somers _) 180ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 181ff4fbdf5SAlan Somers } 182ff4fbdf5SAlan Somers }; 183ff4fbdf5SAlan Somers 184ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 185ff4fbdf5SAlan Somers public: 186ff4fbdf5SAlan Somers void expect_listxattr() 187ff4fbdf5SAlan Somers { 188ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 189ff4fbdf5SAlan Somers ResultOf([=](auto in) { 19029edc611SAlan Somers return (in.header.opcode == FUSE_LISTXATTR); 191ff4fbdf5SAlan Somers }, Eq(true)), 192ff4fbdf5SAlan Somers _) 19329edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 19429edc611SAlan Somers out.body.listxattr.size = 0; 195ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 196ff4fbdf5SAlan Somers }))); 197ff4fbdf5SAlan Somers } 198ff4fbdf5SAlan Somers }; 199ff4fbdf5SAlan Somers 200ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 201ff4fbdf5SAlan Somers public: 202ff4fbdf5SAlan Somers /* 203ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 204ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 205ff4fbdf5SAlan Somers */ 206ff4fbdf5SAlan Somers void expect_rename(int error) 207ff4fbdf5SAlan Somers { 208ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 209ff4fbdf5SAlan Somers ResultOf([=](auto in) { 21029edc611SAlan Somers return (in.header.opcode == FUSE_RENAME); 211ff4fbdf5SAlan Somers }, Eq(true)), 212ff4fbdf5SAlan Somers _) 213ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 214ff4fbdf5SAlan Somers } 215ff4fbdf5SAlan Somers }; 216ff4fbdf5SAlan Somers 217ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 218ff4fbdf5SAlan Somers public: 219ff4fbdf5SAlan Somers void expect_setxattr(int error) 220ff4fbdf5SAlan Somers { 221ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 222ff4fbdf5SAlan Somers ResultOf([=](auto in) { 22329edc611SAlan Somers return (in.header.opcode == FUSE_SETXATTR); 224ff4fbdf5SAlan Somers }, Eq(true)), 225ff4fbdf5SAlan Somers _) 226ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 227ff4fbdf5SAlan Somers } 228ff4fbdf5SAlan Somers }; 229ff4fbdf5SAlan Somers 2308cfb4431SAlan Somers /* Return a group to which this user does not belong */ 2318cfb4431SAlan Somers static gid_t excluded_group() 2328cfb4431SAlan Somers { 2338cfb4431SAlan Somers int i, ngroups = 64; 2348cfb4431SAlan Somers gid_t newgid, groups[ngroups]; 2358cfb4431SAlan Somers 2368cfb4431SAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 2375a0b9a27SAlan Somers for (newgid = 0; ; newgid++) { 2388cfb4431SAlan Somers bool belongs = false; 2398cfb4431SAlan Somers 2408cfb4431SAlan Somers for (i = 0; i < ngroups; i++) { 2418cfb4431SAlan Somers if (groups[i] == newgid) 2428cfb4431SAlan Somers belongs = true; 2438cfb4431SAlan Somers } 2448cfb4431SAlan Somers if (!belongs) 2458cfb4431SAlan Somers break; 2468cfb4431SAlan Somers } 2478cfb4431SAlan Somers /* newgid is now a group to which the current user does not belong */ 2488cfb4431SAlan Somers return newgid; 2498cfb4431SAlan Somers } 2508cfb4431SAlan Somers 251ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2529821f1d3SAlan Somers { 2539821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2549821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2559821f1d3SAlan Somers uint64_t ino = 42; 2569821f1d3SAlan Somers mode_t access_mode = X_OK; 2579821f1d3SAlan Somers 258a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 259ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 260ff4fbdf5SAlan Somers 261ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 262ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 263ff4fbdf5SAlan Somers } 264ff4fbdf5SAlan Somers 265ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 266ff4fbdf5SAlan Somers { 267ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 268ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 269ff4fbdf5SAlan Somers uint64_t ino = 42; 270ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 271ff4fbdf5SAlan Somers 272a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1); 273ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 274ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2759821f1d3SAlan Somers /* 2769821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2779821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 2789821f1d3SAlan Somers * a FUSE_ACCESS 2799821f1d3SAlan Somers */ 2809821f1d3SAlan Somers 2819821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 2829821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 2839821f1d3SAlan Somers } 2849821f1d3SAlan Somers 285ff4fbdf5SAlan Somers TEST_F(Access, ok) 2869821f1d3SAlan Somers { 2879821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2889821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2899821f1d3SAlan Somers uint64_t ino = 42; 2909821f1d3SAlan Somers mode_t access_mode = R_OK; 2919821f1d3SAlan Somers 292a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 293ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 2949821f1d3SAlan Somers /* 2959821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 29691ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 2979821f1d3SAlan Somers */ 2989821f1d3SAlan Somers 2999821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 3009821f1d3SAlan Somers } 3019821f1d3SAlan Somers 3024e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */ 3034e83d655SAlan Somers TEST_F(Chown, chown_to_self) 3044e83d655SAlan Somers { 3054e83d655SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3064e83d655SAlan Somers const char RELPATH[] = "some_file.txt"; 3074e83d655SAlan Somers const uint64_t ino = 42; 3084e83d655SAlan Somers const mode_t mode = 0755; 3094e83d655SAlan Somers uid_t uid; 3104e83d655SAlan Somers 3114e83d655SAlan Somers uid = geteuid(); 3124e83d655SAlan Somers 313a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 3144e83d655SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid); 3154e83d655SAlan Somers /* The OS may optimize chown by omitting the redundant setattr */ 3164e83d655SAlan Somers EXPECT_CALL(*m_mock, process( 3174e83d655SAlan Somers ResultOf([](auto in) { 31829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 3194e83d655SAlan Somers }, Eq(true)), 3204e83d655SAlan Somers _) 32129edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 3224e83d655SAlan Somers SET_OUT_HEADER_LEN(out, attr); 32329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 32429edc611SAlan Somers out.body.attr.attr.uid = uid; 3254e83d655SAlan Somers }))); 3264e83d655SAlan Somers 3274e83d655SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 3284e83d655SAlan Somers } 3294e83d655SAlan Somers 330a2bdd737SAlan Somers /* 331a2bdd737SAlan Somers * A successful chown by a non-privileged non-owner should clear a file's SUID 332a2bdd737SAlan Somers * bit 333a2bdd737SAlan Somers */ 334a2bdd737SAlan Somers TEST_F(Chown, clear_suid) 335a2bdd737SAlan Somers { 336a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 337a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 338a2bdd737SAlan Somers uint64_t ino = 42; 339a2bdd737SAlan Somers const mode_t oldmode = 06755; 340a2bdd737SAlan Somers const mode_t newmode = 0755; 341a2bdd737SAlan Somers uid_t uid = geteuid(); 342a2bdd737SAlan Somers uint32_t valid = FATTR_UID | FATTR_MODE; 343a2bdd737SAlan Somers 344a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 345a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid); 346a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 347a2bdd737SAlan Somers ResultOf([=](auto in) { 34829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 34929edc611SAlan Somers in.header.nodeid == ino && 35029edc611SAlan Somers in.body.setattr.valid == valid && 35129edc611SAlan Somers in.body.setattr.mode == newmode); 352a2bdd737SAlan Somers }, Eq(true)), 353a2bdd737SAlan Somers _) 35429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 355a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 35629edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 35729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 35829edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 359a2bdd737SAlan Somers }))); 360a2bdd737SAlan Somers 361a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 362a2bdd737SAlan Somers } 363a2bdd737SAlan Somers 364a2bdd737SAlan Somers 365474ba6faSAlan Somers /* Only root may change a file's owner */ 366474ba6faSAlan Somers TEST_F(Chown, eperm) 367474ba6faSAlan Somers { 368474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 369474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 370474ba6faSAlan Somers const uint64_t ino = 42; 371474ba6faSAlan Somers const mode_t mode = 0755; 372474ba6faSAlan Somers 373a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 374474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 375474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 376474ba6faSAlan Somers ResultOf([](auto in) { 37729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 378474ba6faSAlan Somers }, Eq(true)), 379474ba6faSAlan Somers _) 380474ba6faSAlan Somers ).Times(0); 381474ba6faSAlan Somers 382474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 383474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 384474ba6faSAlan Somers } 385474ba6faSAlan Somers 386a2bdd737SAlan Somers /* 387a2bdd737SAlan Somers * A successful chgrp by a non-privileged non-owner should clear a file's SUID 388a2bdd737SAlan Somers * bit 389a2bdd737SAlan Somers */ 390a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid) 391a2bdd737SAlan Somers { 392a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 393a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 394a2bdd737SAlan Somers uint64_t ino = 42; 395a2bdd737SAlan Somers const mode_t oldmode = 06755; 396a2bdd737SAlan Somers const mode_t newmode = 0755; 397a2bdd737SAlan Somers uid_t uid = geteuid(); 398a2bdd737SAlan Somers gid_t gid = getegid(); 399a2bdd737SAlan Somers uint32_t valid = FATTR_GID | FATTR_MODE; 400a2bdd737SAlan Somers 401a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 402a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 403a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 404a2bdd737SAlan Somers ResultOf([=](auto in) { 40529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 40629edc611SAlan Somers in.header.nodeid == ino && 40729edc611SAlan Somers in.body.setattr.valid == valid && 40829edc611SAlan Somers in.body.setattr.mode == newmode); 409a2bdd737SAlan Somers }, Eq(true)), 410a2bdd737SAlan Somers _) 41129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 412a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 41329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 41429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 41529edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 416a2bdd737SAlan Somers }))); 417a2bdd737SAlan Somers 418a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno); 419a2bdd737SAlan Somers } 420a2bdd737SAlan Somers 421474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 422474ba6faSAlan Somers TEST_F(Chgrp, eperm) 423474ba6faSAlan Somers { 424474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 425474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 426474ba6faSAlan Somers const uint64_t ino = 42; 427474ba6faSAlan Somers const mode_t mode = 0755; 428474ba6faSAlan Somers uid_t uid; 429474ba6faSAlan Somers gid_t gid, newgid; 430474ba6faSAlan Somers 431474ba6faSAlan Somers uid = geteuid(); 432474ba6faSAlan Somers gid = getegid(); 4338cfb4431SAlan Somers newgid = excluded_group(); 434474ba6faSAlan Somers 435a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 436474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 437474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 438474ba6faSAlan Somers ResultOf([](auto in) { 43929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 440474ba6faSAlan Somers }, Eq(true)), 441474ba6faSAlan Somers _) 442474ba6faSAlan Somers ).Times(0); 443474ba6faSAlan Somers 444474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 445474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 446474ba6faSAlan Somers } 447474ba6faSAlan Somers 448474ba6faSAlan Somers TEST_F(Chgrp, ok) 449474ba6faSAlan Somers { 450474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 451474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 452474ba6faSAlan Somers const uint64_t ino = 42; 453474ba6faSAlan Somers const mode_t mode = 0755; 454474ba6faSAlan Somers uid_t uid; 455474ba6faSAlan Somers gid_t gid, newgid; 456474ba6faSAlan Somers 457474ba6faSAlan Somers uid = geteuid(); 458474ba6faSAlan Somers gid = 0; 459474ba6faSAlan Somers newgid = getegid(); 460474ba6faSAlan Somers 461a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 462474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 4634e83d655SAlan Somers /* The OS may optimize chgrp by omitting the redundant setattr */ 464474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 465474ba6faSAlan Somers ResultOf([](auto in) { 46629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 46729edc611SAlan Somers in.header.nodeid == ino); 468474ba6faSAlan Somers }, Eq(true)), 469474ba6faSAlan Somers _) 47029edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 471474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 47229edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 47329edc611SAlan Somers out.body.attr.attr.uid = uid; 47429edc611SAlan Somers out.body.attr.attr.gid = newgid; 475474ba6faSAlan Somers }))); 476474ba6faSAlan Somers 477474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 478474ba6faSAlan Somers } 479474ba6faSAlan Somers 480ff4fbdf5SAlan Somers TEST_F(Create, ok) 481ff4fbdf5SAlan Somers { 482ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 483ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 484ff4fbdf5SAlan Somers uint64_t ino = 42; 485ff4fbdf5SAlan Somers int fd; 486ff4fbdf5SAlan Somers 487a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 488a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 489a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 490ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 491ff4fbdf5SAlan Somers 492ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 493ff4fbdf5SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 4947fc0921dSAlan Somers leak(fd); 495ff4fbdf5SAlan Somers } 496ff4fbdf5SAlan Somers 497ff4fbdf5SAlan Somers TEST_F(Create, eacces) 498ff4fbdf5SAlan Somers { 499ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 500ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 501ff4fbdf5SAlan Somers 502a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 503a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 504a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 505ff4fbdf5SAlan Somers 506ff4fbdf5SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 507ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 508ff4fbdf5SAlan Somers } 509ff4fbdf5SAlan Somers 510ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 511ff4fbdf5SAlan Somers { 512ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 513ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 514ff4fbdf5SAlan Somers uint64_t ino = 42; 515ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 516ff4fbdf5SAlan Somers 517a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 518ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 519ff4fbdf5SAlan Somers 520ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 521ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 522ff4fbdf5SAlan Somers } 523ff4fbdf5SAlan Somers 524ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 525ff4fbdf5SAlan Somers { 526ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 527ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 528ff4fbdf5SAlan Somers uint64_t ino = 42; 529ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 530ff4fbdf5SAlan Somers 531a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 532ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 533ff4fbdf5SAlan Somers expect_removexattr(); 534ff4fbdf5SAlan Somers 535ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 536ff4fbdf5SAlan Somers << strerror(errno); 537ff4fbdf5SAlan Somers } 538ff4fbdf5SAlan Somers 539ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 540ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 541ff4fbdf5SAlan Somers { 542ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 543ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 544ff4fbdf5SAlan Somers uint64_t ino = 42; 545ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 546ff4fbdf5SAlan Somers 547a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 548ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 549ff4fbdf5SAlan Somers 550ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 551ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 552ff4fbdf5SAlan Somers } 553ff4fbdf5SAlan Somers 554d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */ 555d943c93eSAlan Somers TEST_F(Utimensat, utime_now) 556d943c93eSAlan Somers { 557d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 558d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 559d943c93eSAlan Somers const uint64_t ino = 42; 560d943c93eSAlan Somers /* Write permissions for everybody */ 561d943c93eSAlan Somers const mode_t mode = 0666; 562d943c93eSAlan Somers uid_t owner = 0; 563d943c93eSAlan Somers const timespec times[2] = { 564d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 565d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 566d943c93eSAlan Somers }; 567d943c93eSAlan Somers 568a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 569d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 570d943c93eSAlan Somers EXPECT_CALL(*m_mock, process( 571d943c93eSAlan Somers ResultOf([](auto in) { 57229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 57329edc611SAlan Somers in.header.nodeid == ino && 57429edc611SAlan Somers in.body.setattr.valid & FATTR_ATIME && 57529edc611SAlan Somers in.body.setattr.valid & FATTR_MTIME); 576d943c93eSAlan Somers }, Eq(true)), 577d943c93eSAlan Somers _) 57829edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 579d943c93eSAlan Somers SET_OUT_HEADER_LEN(out, attr); 58029edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 581d943c93eSAlan Somers }))); 582d943c93eSAlan Somers 583d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 584d943c93eSAlan Somers << strerror(errno); 585d943c93eSAlan Somers } 586d943c93eSAlan Somers 587d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */ 588d943c93eSAlan Somers TEST_F(Utimensat, utime_omit) 589d943c93eSAlan Somers { 590d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 591d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 592d943c93eSAlan Somers const uint64_t ino = 42; 593d943c93eSAlan Somers /* Write permissions for no one */ 594d943c93eSAlan Somers const mode_t mode = 0444; 595d943c93eSAlan Somers uid_t owner = 0; 596d943c93eSAlan Somers const timespec times[2] = { 597d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 598d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 599d943c93eSAlan Somers }; 600d943c93eSAlan Somers 601a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 602d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 603d943c93eSAlan Somers 604d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 605d943c93eSAlan Somers << strerror(errno); 606d943c93eSAlan Somers } 607d943c93eSAlan Somers 608ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 609ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 610ff4fbdf5SAlan Somers { 611ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 612ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 613ff4fbdf5SAlan Somers uint64_t ino = 42; 614ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 615ff4fbdf5SAlan Somers 616a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 617ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 618ff4fbdf5SAlan Somers expect_removexattr(); 619ff4fbdf5SAlan Somers 620ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 621ff4fbdf5SAlan Somers << strerror(errno); 622ff4fbdf5SAlan Somers } 623ff4fbdf5SAlan Somers 624ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 625ff4fbdf5SAlan Somers { 626ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 627ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 628ff4fbdf5SAlan Somers uint64_t ino = 42; 629ff4fbdf5SAlan Somers char data[80]; 630ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 631ff4fbdf5SAlan Somers 632a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 633ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 634ff4fbdf5SAlan Somers 635ff4fbdf5SAlan Somers ASSERT_EQ(-1, 636ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 637ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 638ff4fbdf5SAlan Somers } 639ff4fbdf5SAlan Somers 640ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 641ff4fbdf5SAlan Somers { 642ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 643ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 644ff4fbdf5SAlan Somers uint64_t ino = 42; 645ff4fbdf5SAlan Somers char data[80]; 646ff4fbdf5SAlan Somers const char value[] = "whatever"; 647ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 648ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 649ff4fbdf5SAlan Somers ssize_t r; 650ff4fbdf5SAlan Somers 651a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 652ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 653ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 654ff4fbdf5SAlan Somers expect_getxattr( 65529edc611SAlan Somers ReturnImmediate([&](auto in __unused, auto& out) { 65629edc611SAlan Somers memcpy((void*)out.body.bytes, value, value_len); 65729edc611SAlan Somers out.header.len = sizeof(out.header) + value_len; 658ff4fbdf5SAlan Somers }) 659ff4fbdf5SAlan Somers ); 660ff4fbdf5SAlan Somers 661ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 662ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 663ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 664ff4fbdf5SAlan Somers } 665ff4fbdf5SAlan Somers 666ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 667ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 668ff4fbdf5SAlan Somers { 669ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 670ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 671ff4fbdf5SAlan Somers uint64_t ino = 42; 672ff4fbdf5SAlan Somers char data[80]; 673ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 674ff4fbdf5SAlan Somers 675a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 676ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 677ff4fbdf5SAlan Somers 678ff4fbdf5SAlan Somers ASSERT_EQ(-1, 679ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 680ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 681ff4fbdf5SAlan Somers } 682ff4fbdf5SAlan Somers 683ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 684ff4fbdf5SAlan Somers { 685ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 686ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 687ff4fbdf5SAlan Somers uint64_t ino = 42; 688ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 689ff4fbdf5SAlan Somers 690a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 691ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 692ff4fbdf5SAlan Somers 693ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 694ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 695ff4fbdf5SAlan Somers } 696ff4fbdf5SAlan Somers 697ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 698ff4fbdf5SAlan Somers { 699ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 700ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 701ff4fbdf5SAlan Somers uint64_t ino = 42; 702ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 703ff4fbdf5SAlan Somers 704a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 705ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 706ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 707ff4fbdf5SAlan Somers expect_listxattr(); 708ff4fbdf5SAlan Somers 709ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 710ff4fbdf5SAlan Somers << strerror(errno); 711ff4fbdf5SAlan Somers } 712ff4fbdf5SAlan Somers 713ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 714ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 715ff4fbdf5SAlan Somers { 716ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 717ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 718ff4fbdf5SAlan Somers uint64_t ino = 42; 719ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 720ff4fbdf5SAlan Somers 721a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 722ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 723ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 724ff4fbdf5SAlan Somers 725ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 726ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 727ff4fbdf5SAlan Somers } 728ff4fbdf5SAlan Somers 729ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 730ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 731ff4fbdf5SAlan Somers { 732ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 733ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 734ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 735ff4fbdf5SAlan Somers 736a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 737ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 738ff4fbdf5SAlan Somers 739ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 740ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 741ff4fbdf5SAlan Somers } 742ff4fbdf5SAlan Somers 743ff4fbdf5SAlan Somers TEST_F(Open, eacces) 744ff4fbdf5SAlan Somers { 745ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 746ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 747ff4fbdf5SAlan Somers uint64_t ino = 42; 748ff4fbdf5SAlan Somers 749a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 750ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 751ff4fbdf5SAlan Somers 752ff4fbdf5SAlan Somers EXPECT_NE(0, open(FULLPATH, O_RDWR)); 753ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 754ff4fbdf5SAlan Somers } 755ff4fbdf5SAlan Somers 7569821f1d3SAlan Somers TEST_F(Open, ok) 7579821f1d3SAlan Somers { 7589821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 7599821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 7609821f1d3SAlan Somers uint64_t ino = 42; 7619821f1d3SAlan Somers int fd; 7629821f1d3SAlan Somers 763a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 764ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 7659821f1d3SAlan Somers expect_open(ino, 0, 1); 7669821f1d3SAlan Somers 7679821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 7689821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 7697fc0921dSAlan Somers leak(fd); 7709821f1d3SAlan Somers } 7719821f1d3SAlan Somers 772ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 773ff4fbdf5SAlan Somers { 774ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 775ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 776ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 777ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 778ff4fbdf5SAlan Somers uint64_t ino = 42; 779ff4fbdf5SAlan Somers 780a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0); 781ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 782a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 783ff4fbdf5SAlan Somers .Times(AnyNumber()) 784ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 785ff4fbdf5SAlan Somers 786ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 787ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 788ff4fbdf5SAlan Somers } 789ff4fbdf5SAlan Somers 790ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 791ff4fbdf5SAlan Somers { 792ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 793ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 794ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 795ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 796ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 797ff4fbdf5SAlan Somers uint64_t src_ino = 42; 798ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 799ff4fbdf5SAlan Somers 800a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 801ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 802ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 803ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 804ff4fbdf5SAlan Somers 805ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 806ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 807ff4fbdf5SAlan Somers } 808ff4fbdf5SAlan Somers 809ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 810ff4fbdf5SAlan Somers { 811ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 812ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 813ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 814ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 815ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 816ff4fbdf5SAlan Somers uint64_t src_ino = 42; 817ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 818ff4fbdf5SAlan Somers 819a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 820ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 821ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 822ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 823ff4fbdf5SAlan Somers 824ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 825ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 826ff4fbdf5SAlan Somers } 827ff4fbdf5SAlan Somers 8286124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 829ff4fbdf5SAlan Somers { 830ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 831ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 832ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 833ff4fbdf5SAlan Somers uint64_t ino = 42; 834ff4fbdf5SAlan Somers 835a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 836ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 837ff4fbdf5SAlan Somers 838ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 839ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 840ff4fbdf5SAlan Somers } 841ff4fbdf5SAlan Somers 8428e45ec4eSAlan Somers /* 8438e45ec4eSAlan Somers * A user cannot move out a subdirectory that he does not own, because that 8448e45ec4eSAlan Somers * would require changing the subdirectory's ".." dirent 8458e45ec4eSAlan Somers */ 8468e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory) 8478e45ec4eSAlan Somers { 8488e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 8498e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 8508e45ec4eSAlan Somers const char RELDSTDIR[] = "d"; 8518e45ec4eSAlan Somers const char RELDST[] = "dst"; 8528e45ec4eSAlan Somers const char RELSRC[] = "src"; 8538e45ec4eSAlan Somers uint64_t ino = 42; 8548e45ec4eSAlan Somers uint64_t dstdir_ino = 43; 8558e45ec4eSAlan Somers 856a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 8578e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 8588e45ec4eSAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 8598e45ec4eSAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 8608e45ec4eSAlan Somers 8618e45ec4eSAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 8628e45ec4eSAlan Somers ASSERT_EQ(EACCES, errno); 8638e45ec4eSAlan Somers } 8648e45ec4eSAlan Somers 8658e45ec4eSAlan Somers /* 8668e45ec4eSAlan Somers * A user _can_ rename a subdirectory to which he lacks write permissions, if 8678e45ec4eSAlan Somers * it will keep the same parent 8688e45ec4eSAlan Somers */ 8698e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir) 8708e45ec4eSAlan Somers { 8718e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/dst"; 8728e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 8738e45ec4eSAlan Somers const char RELDST[] = "dst"; 8748e45ec4eSAlan Somers const char RELSRC[] = "src"; 8758e45ec4eSAlan Somers uint64_t ino = 42; 8768e45ec4eSAlan Somers 877a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 8788e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 879a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 880a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 8818e45ec4eSAlan Somers expect_rename(0); 8828e45ec4eSAlan Somers 8838e45ec4eSAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 8848e45ec4eSAlan Somers } 8858e45ec4eSAlan Somers 8866124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 887ff4fbdf5SAlan Somers { 888ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 889ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 8906124fd71SAlan Somers const char RELDST[] = "dst"; 891ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 892ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 893ff4fbdf5SAlan Somers uint64_t src_ino = 42; 894ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 895ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 896ff4fbdf5SAlan Somers 897a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 898ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 899ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 9006124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 90129edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 9026124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 90329edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 90429edc611SAlan Somers out.body.entry.nodeid = dst_ino; 90529edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 90629edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 90729edc611SAlan Somers out.body.entry.attr.uid = 0; 9086124fd71SAlan Somers }))); 909ff4fbdf5SAlan Somers 910ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 911ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 912ff4fbdf5SAlan Somers } 913ff4fbdf5SAlan Somers 914ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 915ff4fbdf5SAlan Somers TEST_F(Rename, ok) 916ff4fbdf5SAlan Somers { 917ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 918ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 919ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 920ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 921ff4fbdf5SAlan Somers // The inode of the already-existing destination file 922ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 923ff4fbdf5SAlan Somers uint64_t ino = 42; 924ff4fbdf5SAlan Somers 925a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 926ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 927ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 928ff4fbdf5SAlan Somers expect_rename(0); 929ff4fbdf5SAlan Somers 930ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 931ff4fbdf5SAlan Somers } 932ff4fbdf5SAlan Somers 933ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 934ff4fbdf5SAlan Somers { 935ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 936ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 937ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 938ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 939ff4fbdf5SAlan Somers uint64_t ino = 42; 940ff4fbdf5SAlan Somers 941a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 942ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 943a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 944a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 945ff4fbdf5SAlan Somers expect_rename(0); 946ff4fbdf5SAlan Somers 947ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 948ff4fbdf5SAlan Somers } 949ff4fbdf5SAlan Somers 950ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 951ff4fbdf5SAlan Somers { 952ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 953ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 954ff4fbdf5SAlan Somers const uint64_t ino = 42; 955ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 956ff4fbdf5SAlan Somers const mode_t newmode = 0644; 957ff4fbdf5SAlan Somers 958a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 959ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 960ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 961ff4fbdf5SAlan Somers ResultOf([](auto in) { 96229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 96329edc611SAlan Somers in.header.nodeid == ino && 96429edc611SAlan Somers in.body.setattr.mode == newmode); 965ff4fbdf5SAlan Somers }, Eq(true)), 966ff4fbdf5SAlan Somers _) 96729edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 968ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 96929edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 970ff4fbdf5SAlan Somers }))); 971ff4fbdf5SAlan Somers 972ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 973ff4fbdf5SAlan Somers } 974ff4fbdf5SAlan Somers 975ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 976ff4fbdf5SAlan Somers { 977ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 978ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 979ff4fbdf5SAlan Somers const uint64_t ino = 42; 980ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 981ff4fbdf5SAlan Somers const mode_t newmode = 0644; 982ff4fbdf5SAlan Somers 983a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 984ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 985ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 986ff4fbdf5SAlan Somers ResultOf([](auto in) { 98729edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 988ff4fbdf5SAlan Somers }, Eq(true)), 989ff4fbdf5SAlan Somers _) 990ff4fbdf5SAlan Somers ).Times(0); 991ff4fbdf5SAlan Somers 992ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 993ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 994ff4fbdf5SAlan Somers } 995ff4fbdf5SAlan Somers 9968cfb4431SAlan Somers /* 9973fa12789SAlan Somers * ftruncate() of a file without writable permissions should succeed as long as 9983fa12789SAlan Somers * the file descriptor is writable. This is important when combined with 9993fa12789SAlan Somers * O_CREAT 10003fa12789SAlan Somers */ 10013fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file) 10023fa12789SAlan Somers { 10033fa12789SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 10043fa12789SAlan Somers const char RELPATH[] = "some_file.txt"; 10053fa12789SAlan Somers const uint64_t ino = 42; 10063fa12789SAlan Somers const mode_t mode = 0000; 10073fa12789SAlan Somers int fd; 10083fa12789SAlan Somers 1009a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1010a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1011a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 10123fa12789SAlan Somers expect_create(RELPATH, ino); 10133fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 10143fa12789SAlan Somers ResultOf([](auto in) { 101529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 101629edc611SAlan Somers in.header.nodeid == ino && 101729edc611SAlan Somers (in.body.setattr.valid & FATTR_SIZE)); 10183fa12789SAlan Somers }, Eq(true)), 10193fa12789SAlan Somers _) 102029edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 10213fa12789SAlan Somers SET_OUT_HEADER_LEN(out, attr); 102229edc611SAlan Somers out.body.attr.attr.ino = ino; 102329edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 102429edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 10253fa12789SAlan Somers }))); 10263fa12789SAlan Somers 10273fa12789SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 10283fa12789SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 10293fa12789SAlan Somers ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 10307fc0921dSAlan Somers leak(fd); 10313fa12789SAlan Somers } 10323fa12789SAlan Somers 10333fa12789SAlan Somers /* 10348cfb4431SAlan Somers * Setting the sgid bit should fail for an unprivileged user who doesn't belong 10358cfb4431SAlan Somers * to the file's group 10368cfb4431SAlan Somers */ 10378cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member) 10388cfb4431SAlan Somers { 10398cfb4431SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 10408cfb4431SAlan Somers const char RELPATH[] = "some_file.txt"; 10418cfb4431SAlan Somers const uint64_t ino = 42; 10428cfb4431SAlan Somers const mode_t oldmode = 0755; 10438cfb4431SAlan Somers const mode_t newmode = 02755; 10448cfb4431SAlan Somers uid_t uid = geteuid(); 10458cfb4431SAlan Somers gid_t gid = excluded_group(); 10468cfb4431SAlan Somers 1047a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 10488cfb4431SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 10498cfb4431SAlan Somers EXPECT_CALL(*m_mock, process( 10508cfb4431SAlan Somers ResultOf([](auto in) { 105129edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 10528cfb4431SAlan Somers }, Eq(true)), 10538cfb4431SAlan Somers _) 10548cfb4431SAlan Somers ).Times(0); 10558cfb4431SAlan Somers 10568cfb4431SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 10578cfb4431SAlan Somers EXPECT_EQ(EPERM, errno); 10588cfb4431SAlan Somers } 10598cfb4431SAlan Somers 1060e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 1061e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 1062e5ff3a7eSAlan Somers { 1063e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1064e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 1065e5ff3a7eSAlan Somers const uint64_t ino = 42; 1066e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 1067e5ff3a7eSAlan Somers const mode_t newmode = 01644; 1068e5ff3a7eSAlan Somers 1069a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1070e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1071e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 1072e5ff3a7eSAlan Somers ResultOf([](auto in) { 107329edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1074e5ff3a7eSAlan Somers }, Eq(true)), 1075e5ff3a7eSAlan Somers _) 1076e5ff3a7eSAlan Somers ).Times(0); 1077e5ff3a7eSAlan Somers 1078e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1079e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 1080e5ff3a7eSAlan Somers } 1081e5ff3a7eSAlan Somers 1082ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 1083ff4fbdf5SAlan Somers { 1084ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1085ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1086ff4fbdf5SAlan Somers uint64_t ino = 42; 1087ff4fbdf5SAlan Somers const char value[] = "whatever"; 1088ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1089ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1090ff4fbdf5SAlan Somers ssize_t r; 1091ff4fbdf5SAlan Somers 1092a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1093ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1094ff4fbdf5SAlan Somers expect_setxattr(0); 1095ff4fbdf5SAlan Somers 10965a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 10975a0b9a27SAlan Somers value_len); 1098ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1099ff4fbdf5SAlan Somers } 1100ff4fbdf5SAlan Somers 1101ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 1102ff4fbdf5SAlan Somers { 1103ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1104ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1105ff4fbdf5SAlan Somers uint64_t ino = 42; 1106ff4fbdf5SAlan Somers const char value[] = "whatever"; 1107ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1108ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1109ff4fbdf5SAlan Somers 1110a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1111ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1112ff4fbdf5SAlan Somers 11135a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 11145a0b9a27SAlan Somers value_len)); 1115ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1116ff4fbdf5SAlan Somers } 1117ff4fbdf5SAlan Somers 1118ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 1119ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 1120ff4fbdf5SAlan Somers { 1121ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1122ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1123ff4fbdf5SAlan Somers uint64_t ino = 42; 1124ff4fbdf5SAlan Somers const char value[] = "whatever"; 1125ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1126ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 1127ff4fbdf5SAlan Somers 1128a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1129ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1130ff4fbdf5SAlan Somers 11315a0b9a27SAlan Somers ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 11325a0b9a27SAlan Somers value_len)); 1133ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1134ff4fbdf5SAlan Somers } 1135ff4fbdf5SAlan Somers 1136ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 1137ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 1138ff4fbdf5SAlan Somers { 1139ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1140ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1141ff4fbdf5SAlan Somers uint64_t ino = 42; 1142ff4fbdf5SAlan Somers const char value[] = "whatever"; 1143ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1144ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1145ff4fbdf5SAlan Somers ssize_t r; 1146ff4fbdf5SAlan Somers 1147a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1148ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1149ff4fbdf5SAlan Somers expect_setxattr(0); 1150ff4fbdf5SAlan Somers 11515a0b9a27SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 11525a0b9a27SAlan Somers value_len); 1153ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1154ff4fbdf5SAlan Somers } 1155ff4fbdf5SAlan Somers 1156ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 11579821f1d3SAlan Somers { 11589821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11599821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 11609821f1d3SAlan Somers uint64_t ino = 42; 1161*331884f2SAlan Somers sem_t sem; 1162*331884f2SAlan Somers 1163*331884f2SAlan Somers ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 11649821f1d3SAlan Somers 1165a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1166ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1167a34cdd26SAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, 0); 1168*331884f2SAlan Somers expect_forget(ino, 1, &sem); 11699821f1d3SAlan Somers 1170ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1171*331884f2SAlan Somers 1172*331884f2SAlan Somers sem_wait(&sem); 1173*331884f2SAlan Somers sem_destroy(&sem); 1174ff4fbdf5SAlan Somers } 1175ff4fbdf5SAlan Somers 11766124fd71SAlan Somers /* 11776124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 11786124fd71SAlan Somers * in VOP_LOOKUP. 11796124fd71SAlan Somers * 11806124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 11816124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 11826124fd71SAlan Somers */ 11836124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 11846124fd71SAlan Somers { 11856124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11866124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 11876124fd71SAlan Somers uint64_t ino = 42; 11886124fd71SAlan Somers 1189a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1190a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 11916124fd71SAlan Somers .Times(AnyNumber()) 11926124fd71SAlan Somers .WillRepeatedly(Invoke( 119329edc611SAlan Somers ReturnImmediate([=](auto i __unused, auto& out) { 11946124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 119529edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 119629edc611SAlan Somers out.body.entry.nodeid = ino; 119729edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 11986124fd71SAlan Somers })) 11996124fd71SAlan Somers ); 12006124fd71SAlan Somers 12016124fd71SAlan Somers /* Fill name cache */ 12026124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 12036124fd71SAlan Somers /* Despite cached name , unlink should fail */ 12046124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 12056124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 12066124fd71SAlan Somers } 12076124fd71SAlan Somers 1208ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 1209ff4fbdf5SAlan Somers { 1210ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1211ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1212ff4fbdf5SAlan Somers uint64_t ino = 42; 1213ff4fbdf5SAlan Somers 1214a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1215ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1216ff4fbdf5SAlan Somers 1217ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1218ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1219ff4fbdf5SAlan Somers } 1220ff4fbdf5SAlan Somers 12216124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 1222ff4fbdf5SAlan Somers { 1223ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1224ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1225ff4fbdf5SAlan Somers uint64_t ino = 42; 1226ff4fbdf5SAlan Somers 1227a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1); 1228ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1229ff4fbdf5SAlan Somers 1230ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1231ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 12329821f1d3SAlan Somers } 1233a90e32deSAlan Somers 1234a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1235a90e32deSAlan Somers TEST_F(Write, clear_suid) 1236a90e32deSAlan Somers { 1237a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1238a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1239a90e32deSAlan Somers struct stat sb; 1240a90e32deSAlan Somers uint64_t ino = 42; 1241a90e32deSAlan Somers mode_t oldmode = 04777; 1242a90e32deSAlan Somers mode_t newmode = 0777; 1243a90e32deSAlan Somers char wbuf[1] = {'x'}; 1244a90e32deSAlan Somers int fd; 1245a90e32deSAlan Somers 1246a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1247a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1248a90e32deSAlan Somers expect_open(ino, 0, 1); 1249bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 125018a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1251a90e32deSAlan Somers 1252a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1253a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1254a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1255a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1256a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 12577fc0921dSAlan Somers leak(fd); 1258a90e32deSAlan Somers } 1259a90e32deSAlan Somers 1260a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 1261a90e32deSAlan Somers TEST_F(Write, clear_sgid) 1262a90e32deSAlan Somers { 1263a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1264a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1265a90e32deSAlan Somers struct stat sb; 1266a90e32deSAlan Somers uint64_t ino = 42; 1267a90e32deSAlan Somers mode_t oldmode = 02777; 1268a90e32deSAlan Somers mode_t newmode = 0777; 1269a90e32deSAlan Somers char wbuf[1] = {'x'}; 1270a90e32deSAlan Somers int fd; 1271a90e32deSAlan Somers 1272a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1273a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1274a90e32deSAlan Somers expect_open(ino, 0, 1); 1275bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 127618a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1277a90e32deSAlan Somers 1278a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1279a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1280a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1281a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1282a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 12837fc0921dSAlan Somers leak(fd); 1284a90e32deSAlan Somers } 128518a2264eSAlan Somers 128618a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic 128718a2264eSAlan Somers * 128818a2264eSAlan Somers * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 128918a2264eSAlan Somers * may panic. That happens if the FUSE_SETATTR response indicates that the 129018a2264eSAlan Somers * file's size has changed since the write. 129118a2264eSAlan Somers */ 129218a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid) 129318a2264eSAlan Somers { 129418a2264eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 129518a2264eSAlan Somers const char RELPATH[] = "some_file.txt"; 129618a2264eSAlan Somers uint64_t ino = 42; 129718a2264eSAlan Somers mode_t oldmode = 04777; 129818a2264eSAlan Somers mode_t newmode = 0777; 129918a2264eSAlan Somers char wbuf[1] = {'x'}; 130018a2264eSAlan Somers int fd; 130118a2264eSAlan Somers 1302a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 130318a2264eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 130418a2264eSAlan Somers expect_open(ino, 0, 1); 1305bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 130618a2264eSAlan Somers /* XXX Return a smaller file size than what we just wrote! */ 130718a2264eSAlan Somers expect_chmod(ino, newmode, 0); 130818a2264eSAlan Somers 130918a2264eSAlan Somers fd = open(FULLPATH, O_WRONLY); 131018a2264eSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 131118a2264eSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 13127fc0921dSAlan Somers leak(fd); 131318a2264eSAlan Somers } 131418a2264eSAlan Somers 131518a2264eSAlan Somers 1316