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. 299821f1d3SAlan Somers */ 309821f1d3SAlan Somers 319821f1d3SAlan Somers /* 329821f1d3SAlan Somers * Tests for the "default_permissions" mount option. They must be in their own 339821f1d3SAlan Somers * file so they can be run as an unprivileged user 349821f1d3SAlan Somers */ 359821f1d3SAlan Somers 369821f1d3SAlan Somers extern "C" { 37ff4fbdf5SAlan Somers #include <sys/types.h> 38ff4fbdf5SAlan Somers #include <sys/extattr.h> 39ff4fbdf5SAlan Somers 409821f1d3SAlan Somers #include <fcntl.h> 419821f1d3SAlan Somers #include <unistd.h> 429821f1d3SAlan Somers } 439821f1d3SAlan Somers 449821f1d3SAlan Somers #include "mockfs.hh" 459821f1d3SAlan Somers #include "utils.hh" 469821f1d3SAlan Somers 479821f1d3SAlan Somers using namespace testing; 489821f1d3SAlan Somers 499821f1d3SAlan Somers class DefaultPermissions: public FuseTest { 509821f1d3SAlan Somers 519821f1d3SAlan Somers virtual void SetUp() { 52ff4fbdf5SAlan Somers m_default_permissions = true; 539821f1d3SAlan Somers FuseTest::SetUp(); 54ff4fbdf5SAlan Somers if (HasFatalFailure() || IsSkipped()) 55ff4fbdf5SAlan Somers return; 569821f1d3SAlan Somers 579821f1d3SAlan Somers if (geteuid() == 0) { 589821f1d3SAlan Somers GTEST_SKIP() << "This test requires an unprivileged user"; 599821f1d3SAlan Somers } 60ff4fbdf5SAlan Somers 61ff4fbdf5SAlan Somers /* With -o default_permissions, FUSE_ACCESS should never be called */ 62ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 63ff4fbdf5SAlan Somers ResultOf([=](auto in) { 6429edc611SAlan Somers return (in.header.opcode == FUSE_ACCESS); 65ff4fbdf5SAlan Somers }, Eq(true)), 66ff4fbdf5SAlan Somers _) 67ff4fbdf5SAlan Somers ).Times(0); 689821f1d3SAlan Somers } 699821f1d3SAlan Somers 709821f1d3SAlan Somers public: 7118a2264eSAlan Somers void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0) 72a90e32deSAlan Somers { 73a90e32deSAlan Somers EXPECT_CALL(*m_mock, process( 74a90e32deSAlan Somers ResultOf([=](auto in) { 7529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 7629edc611SAlan Somers in.header.nodeid == ino && 7729edc611SAlan Somers in.body.setattr.valid == FATTR_MODE && 7829edc611SAlan Somers in.body.setattr.mode == mode); 79a90e32deSAlan Somers }, Eq(true)), 80a90e32deSAlan Somers _) 8129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 82a90e32deSAlan Somers SET_OUT_HEADER_LEN(out, attr); 8329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 8429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 8529edc611SAlan Somers out.body.attr.attr.size = size; 8629edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 87a90e32deSAlan Somers }))); 88a90e32deSAlan Somers } 89a90e32deSAlan Somers 903fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino) 913fa12789SAlan Somers { 923fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 933fa12789SAlan Somers ResultOf([=](auto in) { 9429edc611SAlan Somers const char *name = (const char*)in.body.bytes + 95a4856c96SAlan Somers sizeof(fuse_create_in); 9629edc611SAlan Somers return (in.header.opcode == FUSE_CREATE && 973fa12789SAlan Somers (0 == strcmp(relpath, name))); 983fa12789SAlan Somers }, Eq(true)), 993fa12789SAlan Somers _) 10029edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 1013fa12789SAlan Somers SET_OUT_HEADER_LEN(out, create); 10229edc611SAlan Somers out.body.create.entry.attr.mode = S_IFREG | 0644; 10329edc611SAlan Somers out.body.create.entry.nodeid = ino; 10429edc611SAlan Somers out.body.create.entry.entry_valid = UINT64_MAX; 10529edc611SAlan Somers out.body.create.entry.attr_valid = UINT64_MAX; 1063fa12789SAlan Somers }))); 1073fa12789SAlan Somers } 1083fa12789SAlan Somers 109ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times, 110474ba6faSAlan Somers uid_t uid = 0, gid_t gid = 0) 1119821f1d3SAlan Somers { 112ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 113ff4fbdf5SAlan Somers ResultOf([=](auto in) { 11429edc611SAlan Somers return (in.header.opcode == FUSE_GETATTR && 11529edc611SAlan Somers in.header.nodeid == ino); 116ff4fbdf5SAlan Somers }, Eq(true)), 117ff4fbdf5SAlan Somers _) 118ff4fbdf5SAlan Somers ).Times(times) 11929edc611SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 120ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 12129edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 12229edc611SAlan Somers out.body.attr.attr.mode = mode; 12329edc611SAlan Somers out.body.attr.attr.size = 0; 12429edc611SAlan Somers out.body.attr.attr.uid = uid; 12529edc611SAlan Somers out.body.attr.attr.uid = gid; 12629edc611SAlan Somers out.body.attr.attr_valid = attr_valid; 127ff4fbdf5SAlan Somers }))); 128ff4fbdf5SAlan Somers } 129ff4fbdf5SAlan Somers 130ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 131474ba6faSAlan Somers uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0) 132ff4fbdf5SAlan Somers { 133474ba6faSAlan Somers FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid); 1349821f1d3SAlan Somers } 1359821f1d3SAlan Somers 1369821f1d3SAlan Somers }; 1379821f1d3SAlan Somers 1389821f1d3SAlan Somers class Access: public DefaultPermissions {}; 139474ba6faSAlan Somers class Chown: public DefaultPermissions {}; 140474ba6faSAlan Somers class Chgrp: public DefaultPermissions {}; 141ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1429821f1d3SAlan Somers class Open: public DefaultPermissions {}; 143ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 144ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 145d943c93eSAlan Somers class Utimensat: public DefaultPermissions {}; 146a90e32deSAlan Somers class Write: public DefaultPermissions {}; 1479821f1d3SAlan Somers 148ff4fbdf5SAlan Somers /* 149ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 150ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 151ff4fbdf5SAlan Somers * VOP_LOOKUP) 152ff4fbdf5SAlan Somers */ 1533fa12789SAlan Somers class Create: public DefaultPermissions {}; 154ff4fbdf5SAlan Somers 155ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 156ff4fbdf5SAlan Somers public: 157ff4fbdf5SAlan Somers void expect_removexattr() 158ff4fbdf5SAlan Somers { 159ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 160ff4fbdf5SAlan Somers ResultOf([=](auto in) { 16129edc611SAlan Somers return (in.header.opcode == FUSE_REMOVEXATTR); 162ff4fbdf5SAlan Somers }, Eq(true)), 163ff4fbdf5SAlan Somers _) 164ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 165ff4fbdf5SAlan Somers } 166ff4fbdf5SAlan Somers }; 167ff4fbdf5SAlan Somers 168ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 169ff4fbdf5SAlan Somers public: 170ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 171ff4fbdf5SAlan Somers { 172ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 173ff4fbdf5SAlan Somers ResultOf([=](auto in) { 17429edc611SAlan Somers return (in.header.opcode == FUSE_GETXATTR); 175ff4fbdf5SAlan Somers }, Eq(true)), 176ff4fbdf5SAlan Somers _) 177ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 178ff4fbdf5SAlan Somers } 179ff4fbdf5SAlan Somers }; 180ff4fbdf5SAlan Somers 181ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 182ff4fbdf5SAlan Somers public: 183ff4fbdf5SAlan Somers void expect_listxattr() 184ff4fbdf5SAlan Somers { 185ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 186ff4fbdf5SAlan Somers ResultOf([=](auto in) { 18729edc611SAlan Somers return (in.header.opcode == FUSE_LISTXATTR); 188ff4fbdf5SAlan Somers }, Eq(true)), 189ff4fbdf5SAlan Somers _) 19029edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 19129edc611SAlan Somers out.body.listxattr.size = 0; 192ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 193ff4fbdf5SAlan Somers }))); 194ff4fbdf5SAlan Somers } 195ff4fbdf5SAlan Somers }; 196ff4fbdf5SAlan Somers 197ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 198ff4fbdf5SAlan Somers public: 199ff4fbdf5SAlan Somers /* 200ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 201ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 202ff4fbdf5SAlan Somers */ 203ff4fbdf5SAlan Somers void expect_rename(int error) 204ff4fbdf5SAlan Somers { 205ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 206ff4fbdf5SAlan Somers ResultOf([=](auto in) { 20729edc611SAlan Somers return (in.header.opcode == FUSE_RENAME); 208ff4fbdf5SAlan Somers }, Eq(true)), 209ff4fbdf5SAlan Somers _) 210ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 211ff4fbdf5SAlan Somers } 212ff4fbdf5SAlan Somers }; 213ff4fbdf5SAlan Somers 214ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 215ff4fbdf5SAlan Somers public: 216ff4fbdf5SAlan Somers void expect_setxattr(int error) 217ff4fbdf5SAlan Somers { 218ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 219ff4fbdf5SAlan Somers ResultOf([=](auto in) { 22029edc611SAlan Somers return (in.header.opcode == FUSE_SETXATTR); 221ff4fbdf5SAlan Somers }, Eq(true)), 222ff4fbdf5SAlan Somers _) 223ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 224ff4fbdf5SAlan Somers } 225ff4fbdf5SAlan Somers }; 226ff4fbdf5SAlan Somers 2278cfb4431SAlan Somers /* Return a group to which this user does not belong */ 2288cfb4431SAlan Somers static gid_t excluded_group() 2298cfb4431SAlan Somers { 2308cfb4431SAlan Somers int i, ngroups = 64; 2318cfb4431SAlan Somers gid_t newgid, groups[ngroups]; 2328cfb4431SAlan Somers 2338cfb4431SAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 2348cfb4431SAlan Somers for (newgid = 0; newgid >= 0; newgid++) { 2358cfb4431SAlan Somers bool belongs = false; 2368cfb4431SAlan Somers 2378cfb4431SAlan Somers for (i = 0; i < ngroups; i++) { 2388cfb4431SAlan Somers if (groups[i] == newgid) 2398cfb4431SAlan Somers belongs = true; 2408cfb4431SAlan Somers } 2418cfb4431SAlan Somers if (!belongs) 2428cfb4431SAlan Somers break; 2438cfb4431SAlan Somers } 2448cfb4431SAlan Somers /* newgid is now a group to which the current user does not belong */ 2458cfb4431SAlan Somers return newgid; 2468cfb4431SAlan Somers } 2478cfb4431SAlan Somers 248ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2499821f1d3SAlan Somers { 2509821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2519821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2529821f1d3SAlan Somers uint64_t ino = 42; 2539821f1d3SAlan Somers mode_t access_mode = X_OK; 2549821f1d3SAlan Somers 255a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 256ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 257ff4fbdf5SAlan Somers 258ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 259ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 260ff4fbdf5SAlan Somers } 261ff4fbdf5SAlan Somers 262ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 263ff4fbdf5SAlan Somers { 264ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 265ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 266ff4fbdf5SAlan Somers uint64_t ino = 42; 267ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 268ff4fbdf5SAlan Somers 269a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1); 270ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 271ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2729821f1d3SAlan Somers /* 2739821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2749821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 2759821f1d3SAlan Somers * a FUSE_ACCESS 2769821f1d3SAlan Somers */ 2779821f1d3SAlan Somers 2789821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 2799821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 2809821f1d3SAlan Somers } 2819821f1d3SAlan Somers 282ff4fbdf5SAlan Somers TEST_F(Access, ok) 2839821f1d3SAlan Somers { 2849821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2859821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2869821f1d3SAlan Somers uint64_t ino = 42; 2879821f1d3SAlan Somers mode_t access_mode = R_OK; 2889821f1d3SAlan Somers 289a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 290ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 2919821f1d3SAlan Somers /* 2929821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 29391ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 2949821f1d3SAlan Somers */ 2959821f1d3SAlan Somers 2969821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 2979821f1d3SAlan Somers } 2989821f1d3SAlan Somers 2994e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */ 3004e83d655SAlan Somers TEST_F(Chown, chown_to_self) 3014e83d655SAlan Somers { 3024e83d655SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 3034e83d655SAlan Somers const char RELPATH[] = "some_file.txt"; 3044e83d655SAlan Somers const uint64_t ino = 42; 3054e83d655SAlan Somers const mode_t mode = 0755; 3064e83d655SAlan Somers uid_t uid; 3074e83d655SAlan Somers 3084e83d655SAlan Somers uid = geteuid(); 3094e83d655SAlan Somers 310a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 3114e83d655SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid); 3124e83d655SAlan Somers /* The OS may optimize chown by omitting the redundant setattr */ 3134e83d655SAlan Somers EXPECT_CALL(*m_mock, process( 3144e83d655SAlan Somers ResultOf([](auto in) { 31529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 3164e83d655SAlan Somers }, Eq(true)), 3174e83d655SAlan Somers _) 31829edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 3194e83d655SAlan Somers SET_OUT_HEADER_LEN(out, attr); 32029edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 32129edc611SAlan Somers out.body.attr.attr.uid = uid; 3224e83d655SAlan Somers }))); 3234e83d655SAlan Somers 3244e83d655SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 3254e83d655SAlan Somers } 3264e83d655SAlan Somers 327a2bdd737SAlan Somers /* 328a2bdd737SAlan Somers * A successful chown by a non-privileged non-owner should clear a file's SUID 329a2bdd737SAlan Somers * bit 330a2bdd737SAlan Somers */ 331a2bdd737SAlan Somers TEST_F(Chown, clear_suid) 332a2bdd737SAlan Somers { 333a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 334a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 335a2bdd737SAlan Somers uint64_t ino = 42; 336a2bdd737SAlan Somers const mode_t oldmode = 06755; 337a2bdd737SAlan Somers const mode_t newmode = 0755; 338a2bdd737SAlan Somers uid_t uid = geteuid(); 339a2bdd737SAlan Somers uint32_t valid = FATTR_UID | FATTR_MODE; 340a2bdd737SAlan Somers 341a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 342a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid); 343a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 344a2bdd737SAlan Somers ResultOf([=](auto in) { 34529edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 34629edc611SAlan Somers in.header.nodeid == ino && 34729edc611SAlan Somers in.body.setattr.valid == valid && 34829edc611SAlan Somers in.body.setattr.mode == newmode); 349a2bdd737SAlan Somers }, Eq(true)), 350a2bdd737SAlan Somers _) 35129edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 352a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 35329edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 35429edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 35529edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 356a2bdd737SAlan Somers }))); 357a2bdd737SAlan Somers 358a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 359a2bdd737SAlan Somers } 360a2bdd737SAlan Somers 361a2bdd737SAlan Somers 362474ba6faSAlan Somers /* Only root may change a file's owner */ 363474ba6faSAlan Somers TEST_F(Chown, eperm) 364474ba6faSAlan Somers { 365474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 366474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 367474ba6faSAlan Somers const uint64_t ino = 42; 368474ba6faSAlan Somers const mode_t mode = 0755; 369474ba6faSAlan Somers 370a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 371474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 372474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 373474ba6faSAlan Somers ResultOf([](auto in) { 37429edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 375474ba6faSAlan Somers }, Eq(true)), 376474ba6faSAlan Somers _) 377474ba6faSAlan Somers ).Times(0); 378474ba6faSAlan Somers 379474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 380474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 381474ba6faSAlan Somers } 382474ba6faSAlan Somers 383a2bdd737SAlan Somers /* 384a2bdd737SAlan Somers * A successful chgrp by a non-privileged non-owner should clear a file's SUID 385a2bdd737SAlan Somers * bit 386a2bdd737SAlan Somers */ 387a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid) 388a2bdd737SAlan Somers { 389a2bdd737SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 390a2bdd737SAlan Somers const char RELPATH[] = "some_file.txt"; 391a2bdd737SAlan Somers uint64_t ino = 42; 392a2bdd737SAlan Somers const mode_t oldmode = 06755; 393a2bdd737SAlan Somers const mode_t newmode = 0755; 394a2bdd737SAlan Somers uid_t uid = geteuid(); 395a2bdd737SAlan Somers gid_t gid = getegid(); 396a2bdd737SAlan Somers uint32_t valid = FATTR_GID | FATTR_MODE; 397a2bdd737SAlan Somers 398a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 399a2bdd737SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 400a2bdd737SAlan Somers EXPECT_CALL(*m_mock, process( 401a2bdd737SAlan Somers ResultOf([=](auto in) { 40229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 40329edc611SAlan Somers in.header.nodeid == ino && 40429edc611SAlan Somers in.body.setattr.valid == valid && 40529edc611SAlan Somers in.body.setattr.mode == newmode); 406a2bdd737SAlan Somers }, Eq(true)), 407a2bdd737SAlan Somers _) 40829edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 409a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 41029edc611SAlan Somers out.body.attr.attr.ino = ino; // Must match nodeid 41129edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 41229edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 413a2bdd737SAlan Somers }))); 414a2bdd737SAlan Somers 415a2bdd737SAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno); 416a2bdd737SAlan Somers } 417a2bdd737SAlan Somers 418474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 419474ba6faSAlan Somers TEST_F(Chgrp, eperm) 420474ba6faSAlan Somers { 421474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 422474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 423474ba6faSAlan Somers const uint64_t ino = 42; 424474ba6faSAlan Somers const mode_t mode = 0755; 425474ba6faSAlan Somers uid_t uid; 426474ba6faSAlan Somers gid_t gid, newgid; 427474ba6faSAlan Somers 428474ba6faSAlan Somers uid = geteuid(); 429474ba6faSAlan Somers gid = getegid(); 4308cfb4431SAlan Somers newgid = excluded_group(); 431474ba6faSAlan Somers 432a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 433474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 434474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 435474ba6faSAlan Somers ResultOf([](auto in) { 43629edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 437474ba6faSAlan Somers }, Eq(true)), 438474ba6faSAlan Somers _) 439474ba6faSAlan Somers ).Times(0); 440474ba6faSAlan Somers 441474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 442474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 443474ba6faSAlan Somers } 444474ba6faSAlan Somers 445474ba6faSAlan Somers TEST_F(Chgrp, ok) 446474ba6faSAlan Somers { 447474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 448474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 449474ba6faSAlan Somers const uint64_t ino = 42; 450474ba6faSAlan Somers const mode_t mode = 0755; 451474ba6faSAlan Somers uid_t uid; 452474ba6faSAlan Somers gid_t gid, newgid; 453474ba6faSAlan Somers 454474ba6faSAlan Somers uid = geteuid(); 455474ba6faSAlan Somers gid = 0; 456474ba6faSAlan Somers newgid = getegid(); 457474ba6faSAlan Somers 458a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 459474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 4604e83d655SAlan Somers /* The OS may optimize chgrp by omitting the redundant setattr */ 461474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 462474ba6faSAlan Somers ResultOf([](auto in) { 46329edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 46429edc611SAlan Somers in.header.nodeid == ino); 465474ba6faSAlan Somers }, Eq(true)), 466474ba6faSAlan Somers _) 46729edc611SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 468474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 46929edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 47029edc611SAlan Somers out.body.attr.attr.uid = uid; 47129edc611SAlan Somers out.body.attr.attr.gid = newgid; 472474ba6faSAlan Somers }))); 473474ba6faSAlan Somers 474474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 475474ba6faSAlan Somers } 476474ba6faSAlan Somers 477ff4fbdf5SAlan Somers TEST_F(Create, ok) 478ff4fbdf5SAlan Somers { 479ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 480ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 481ff4fbdf5SAlan Somers uint64_t ino = 42; 482ff4fbdf5SAlan Somers int fd; 483ff4fbdf5SAlan Somers 484a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 485a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 486a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 487ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 488ff4fbdf5SAlan Somers 489ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 490ff4fbdf5SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 491*7fc0921dSAlan Somers leak(fd); 492ff4fbdf5SAlan Somers } 493ff4fbdf5SAlan Somers 494ff4fbdf5SAlan Somers TEST_F(Create, eacces) 495ff4fbdf5SAlan Somers { 496ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 497ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 498ff4fbdf5SAlan Somers 499a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 500a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 501a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 502ff4fbdf5SAlan Somers 503ff4fbdf5SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 504ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 505ff4fbdf5SAlan Somers } 506ff4fbdf5SAlan Somers 507ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 508ff4fbdf5SAlan Somers { 509ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 510ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 511ff4fbdf5SAlan Somers uint64_t ino = 42; 512ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 513ff4fbdf5SAlan Somers 514a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 515ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 516ff4fbdf5SAlan Somers 517ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 518ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 519ff4fbdf5SAlan Somers } 520ff4fbdf5SAlan Somers 521ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 522ff4fbdf5SAlan Somers { 523ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 524ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 525ff4fbdf5SAlan Somers uint64_t ino = 42; 526ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 527ff4fbdf5SAlan Somers 528a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 529ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 530ff4fbdf5SAlan Somers expect_removexattr(); 531ff4fbdf5SAlan Somers 532ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 533ff4fbdf5SAlan Somers << strerror(errno); 534ff4fbdf5SAlan Somers } 535ff4fbdf5SAlan Somers 536ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 537ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 538ff4fbdf5SAlan Somers { 539ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 540ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 541ff4fbdf5SAlan Somers uint64_t ino = 42; 542ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 543ff4fbdf5SAlan Somers 544a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 545ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 546ff4fbdf5SAlan Somers 547ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 548ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 549ff4fbdf5SAlan Somers } 550ff4fbdf5SAlan Somers 551d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */ 552d943c93eSAlan Somers TEST_F(Utimensat, utime_now) 553d943c93eSAlan Somers { 554d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 555d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 556d943c93eSAlan Somers const uint64_t ino = 42; 557d943c93eSAlan Somers /* Write permissions for everybody */ 558d943c93eSAlan Somers const mode_t mode = 0666; 559d943c93eSAlan Somers uid_t owner = 0; 560d943c93eSAlan Somers const timespec times[2] = { 561d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 562d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 563d943c93eSAlan Somers }; 564d943c93eSAlan Somers 565a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 566d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 567d943c93eSAlan Somers EXPECT_CALL(*m_mock, process( 568d943c93eSAlan Somers ResultOf([](auto in) { 56929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 57029edc611SAlan Somers in.header.nodeid == ino && 57129edc611SAlan Somers in.body.setattr.valid & FATTR_ATIME && 57229edc611SAlan Somers in.body.setattr.valid & FATTR_MTIME); 573d943c93eSAlan Somers }, Eq(true)), 574d943c93eSAlan Somers _) 57529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 576d943c93eSAlan Somers SET_OUT_HEADER_LEN(out, attr); 57729edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 578d943c93eSAlan Somers }))); 579d943c93eSAlan Somers 580d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 581d943c93eSAlan Somers << strerror(errno); 582d943c93eSAlan Somers } 583d943c93eSAlan Somers 584d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */ 585d943c93eSAlan Somers TEST_F(Utimensat, utime_omit) 586d943c93eSAlan Somers { 587d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 588d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 589d943c93eSAlan Somers const uint64_t ino = 42; 590d943c93eSAlan Somers /* Write permissions for no one */ 591d943c93eSAlan Somers const mode_t mode = 0444; 592d943c93eSAlan Somers uid_t owner = 0; 593d943c93eSAlan Somers const timespec times[2] = { 594d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 595d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 596d943c93eSAlan Somers }; 597d943c93eSAlan Somers 598a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 599d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 600d943c93eSAlan Somers 601d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 602d943c93eSAlan Somers << strerror(errno); 603d943c93eSAlan Somers } 604d943c93eSAlan Somers 605ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 606ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 607ff4fbdf5SAlan Somers { 608ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 609ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 610ff4fbdf5SAlan Somers uint64_t ino = 42; 611ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 612ff4fbdf5SAlan Somers 613a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 614ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 615ff4fbdf5SAlan Somers expect_removexattr(); 616ff4fbdf5SAlan Somers 617ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 618ff4fbdf5SAlan Somers << strerror(errno); 619ff4fbdf5SAlan Somers } 620ff4fbdf5SAlan Somers 621ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 622ff4fbdf5SAlan Somers { 623ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 624ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 625ff4fbdf5SAlan Somers uint64_t ino = 42; 626ff4fbdf5SAlan Somers char data[80]; 627ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 628ff4fbdf5SAlan Somers 629a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 630ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 631ff4fbdf5SAlan Somers 632ff4fbdf5SAlan Somers ASSERT_EQ(-1, 633ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 634ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 635ff4fbdf5SAlan Somers } 636ff4fbdf5SAlan Somers 637ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 638ff4fbdf5SAlan Somers { 639ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 640ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 641ff4fbdf5SAlan Somers uint64_t ino = 42; 642ff4fbdf5SAlan Somers char data[80]; 643ff4fbdf5SAlan Somers const char value[] = "whatever"; 644ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 645ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 646ff4fbdf5SAlan Somers ssize_t r; 647ff4fbdf5SAlan Somers 648a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 649ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 650ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 651ff4fbdf5SAlan Somers expect_getxattr( 65229edc611SAlan Somers ReturnImmediate([&](auto in __unused, auto& out) { 65329edc611SAlan Somers memcpy((void*)out.body.bytes, value, value_len); 65429edc611SAlan Somers out.header.len = sizeof(out.header) + value_len; 655ff4fbdf5SAlan Somers }) 656ff4fbdf5SAlan Somers ); 657ff4fbdf5SAlan Somers 658ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 659ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 660ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 661ff4fbdf5SAlan Somers } 662ff4fbdf5SAlan Somers 663ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 664ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 665ff4fbdf5SAlan Somers { 666ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 667ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 668ff4fbdf5SAlan Somers uint64_t ino = 42; 669ff4fbdf5SAlan Somers char data[80]; 670ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 671ff4fbdf5SAlan Somers 672a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 673ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 674ff4fbdf5SAlan Somers 675ff4fbdf5SAlan Somers ASSERT_EQ(-1, 676ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 677ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 678ff4fbdf5SAlan Somers } 679ff4fbdf5SAlan Somers 680ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 681ff4fbdf5SAlan Somers { 682ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 683ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 684ff4fbdf5SAlan Somers uint64_t ino = 42; 685ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 686ff4fbdf5SAlan Somers 687a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 688ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 689ff4fbdf5SAlan Somers 690ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 691ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 692ff4fbdf5SAlan Somers } 693ff4fbdf5SAlan Somers 694ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 695ff4fbdf5SAlan Somers { 696ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 697ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 698ff4fbdf5SAlan Somers uint64_t ino = 42; 699ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 700ff4fbdf5SAlan Somers 701a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 702ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 703ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 704ff4fbdf5SAlan Somers expect_listxattr(); 705ff4fbdf5SAlan Somers 706ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 707ff4fbdf5SAlan Somers << strerror(errno); 708ff4fbdf5SAlan Somers } 709ff4fbdf5SAlan Somers 710ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 711ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 712ff4fbdf5SAlan Somers { 713ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 714ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 715ff4fbdf5SAlan Somers uint64_t ino = 42; 716ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 717ff4fbdf5SAlan Somers 718a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 719ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 720ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 721ff4fbdf5SAlan Somers 722ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 723ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 724ff4fbdf5SAlan Somers } 725ff4fbdf5SAlan Somers 726ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 727ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 728ff4fbdf5SAlan Somers { 729ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 730ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 731ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 732ff4fbdf5SAlan Somers 733a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 734ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 735ff4fbdf5SAlan Somers 736ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 737ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 738ff4fbdf5SAlan Somers } 739ff4fbdf5SAlan Somers 740ff4fbdf5SAlan Somers TEST_F(Open, eacces) 741ff4fbdf5SAlan Somers { 742ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 743ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 744ff4fbdf5SAlan Somers uint64_t ino = 42; 745ff4fbdf5SAlan Somers 746a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 747ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 748ff4fbdf5SAlan Somers 749ff4fbdf5SAlan Somers EXPECT_NE(0, open(FULLPATH, O_RDWR)); 750ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 751ff4fbdf5SAlan Somers } 752ff4fbdf5SAlan Somers 7539821f1d3SAlan Somers TEST_F(Open, ok) 7549821f1d3SAlan Somers { 7559821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 7569821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 7579821f1d3SAlan Somers uint64_t ino = 42; 7589821f1d3SAlan Somers int fd; 7599821f1d3SAlan Somers 760a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 761ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 7629821f1d3SAlan Somers expect_open(ino, 0, 1); 7639821f1d3SAlan Somers 7649821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 7659821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 766*7fc0921dSAlan Somers leak(fd); 7679821f1d3SAlan Somers } 7689821f1d3SAlan Somers 769ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 770ff4fbdf5SAlan Somers { 771ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 772ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 773ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 774ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 775ff4fbdf5SAlan Somers uint64_t ino = 42; 776ff4fbdf5SAlan Somers 777a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0); 778ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 779a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 780ff4fbdf5SAlan Somers .Times(AnyNumber()) 781ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 782ff4fbdf5SAlan Somers 783ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 784ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 785ff4fbdf5SAlan Somers } 786ff4fbdf5SAlan Somers 787ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 788ff4fbdf5SAlan Somers { 789ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 790ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 791ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 792ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 793ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 794ff4fbdf5SAlan Somers uint64_t src_ino = 42; 795ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 796ff4fbdf5SAlan Somers 797a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 798ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 799ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 800ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 801ff4fbdf5SAlan Somers 802ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 803ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 804ff4fbdf5SAlan Somers } 805ff4fbdf5SAlan Somers 806ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 807ff4fbdf5SAlan Somers { 808ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 809ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 810ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 811ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 812ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 813ff4fbdf5SAlan Somers uint64_t src_ino = 42; 814ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 815ff4fbdf5SAlan Somers 816a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 817ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 818ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 819ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 820ff4fbdf5SAlan Somers 821ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 822ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 823ff4fbdf5SAlan Somers } 824ff4fbdf5SAlan Somers 8256124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 826ff4fbdf5SAlan Somers { 827ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 828ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 829ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 830ff4fbdf5SAlan Somers uint64_t ino = 42; 831ff4fbdf5SAlan Somers 832a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 833ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 834ff4fbdf5SAlan Somers 835ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 836ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 837ff4fbdf5SAlan Somers } 838ff4fbdf5SAlan Somers 8398e45ec4eSAlan Somers /* 8408e45ec4eSAlan Somers * A user cannot move out a subdirectory that he does not own, because that 8418e45ec4eSAlan Somers * would require changing the subdirectory's ".." dirent 8428e45ec4eSAlan Somers */ 8438e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory) 8448e45ec4eSAlan Somers { 8458e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 8468e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 8478e45ec4eSAlan Somers const char RELDSTDIR[] = "d"; 8488e45ec4eSAlan Somers const char RELDST[] = "dst"; 8498e45ec4eSAlan Somers const char RELSRC[] = "src"; 8508e45ec4eSAlan Somers uint64_t ino = 42; 8518e45ec4eSAlan Somers uint64_t dstdir_ino = 43; 8528e45ec4eSAlan Somers 853a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 8548e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 8558e45ec4eSAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 8568e45ec4eSAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 8578e45ec4eSAlan Somers 8588e45ec4eSAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 8598e45ec4eSAlan Somers ASSERT_EQ(EACCES, errno); 8608e45ec4eSAlan Somers } 8618e45ec4eSAlan Somers 8628e45ec4eSAlan Somers /* 8638e45ec4eSAlan Somers * A user _can_ rename a subdirectory to which he lacks write permissions, if 8648e45ec4eSAlan Somers * it will keep the same parent 8658e45ec4eSAlan Somers */ 8668e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir) 8678e45ec4eSAlan Somers { 8688e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/dst"; 8698e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 8708e45ec4eSAlan Somers const char RELDST[] = "dst"; 8718e45ec4eSAlan Somers const char RELSRC[] = "src"; 8728e45ec4eSAlan Somers uint64_t ino = 42; 8738e45ec4eSAlan Somers 874a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 8758e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 876a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 877a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 8788e45ec4eSAlan Somers expect_rename(0); 8798e45ec4eSAlan Somers 8808e45ec4eSAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 8818e45ec4eSAlan Somers } 8828e45ec4eSAlan Somers 8836124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 884ff4fbdf5SAlan Somers { 885ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 886ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 8876124fd71SAlan Somers const char RELDST[] = "dst"; 888ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 889ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 890ff4fbdf5SAlan Somers uint64_t src_ino = 42; 891ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 892ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 893ff4fbdf5SAlan Somers 894a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 895ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 896ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 8976124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 89829edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 8996124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 90029edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 90129edc611SAlan Somers out.body.entry.nodeid = dst_ino; 90229edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 90329edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 90429edc611SAlan Somers out.body.entry.attr.uid = 0; 9056124fd71SAlan Somers }))); 906ff4fbdf5SAlan Somers 907ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 908ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 909ff4fbdf5SAlan Somers } 910ff4fbdf5SAlan Somers 911ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 912ff4fbdf5SAlan Somers TEST_F(Rename, ok) 913ff4fbdf5SAlan Somers { 914ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 915ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 916ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 917ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 918ff4fbdf5SAlan Somers // The inode of the already-existing destination file 919ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 920ff4fbdf5SAlan Somers uint64_t ino = 42; 921ff4fbdf5SAlan Somers 922a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 923ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 924ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 925ff4fbdf5SAlan Somers expect_rename(0); 926ff4fbdf5SAlan Somers 927ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 928ff4fbdf5SAlan Somers } 929ff4fbdf5SAlan Somers 930ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 931ff4fbdf5SAlan Somers { 932ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 933ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 934ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 935ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 936ff4fbdf5SAlan Somers uint64_t ino = 42; 937ff4fbdf5SAlan Somers 938a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 939ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 940a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 941a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 942ff4fbdf5SAlan Somers expect_rename(0); 943ff4fbdf5SAlan Somers 944ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 945ff4fbdf5SAlan Somers } 946ff4fbdf5SAlan Somers 947ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 948ff4fbdf5SAlan Somers { 949ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 950ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 951ff4fbdf5SAlan Somers const uint64_t ino = 42; 952ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 953ff4fbdf5SAlan Somers const mode_t newmode = 0644; 954ff4fbdf5SAlan Somers 955a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 956ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 957ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 958ff4fbdf5SAlan Somers ResultOf([](auto in) { 95929edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 96029edc611SAlan Somers in.header.nodeid == ino && 96129edc611SAlan Somers in.body.setattr.mode == newmode); 962ff4fbdf5SAlan Somers }, Eq(true)), 963ff4fbdf5SAlan Somers _) 96429edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 965ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 96629edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | newmode; 967ff4fbdf5SAlan Somers }))); 968ff4fbdf5SAlan Somers 969ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 970ff4fbdf5SAlan Somers } 971ff4fbdf5SAlan Somers 972ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 973ff4fbdf5SAlan Somers { 974ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 975ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 976ff4fbdf5SAlan Somers const uint64_t ino = 42; 977ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 978ff4fbdf5SAlan Somers const mode_t newmode = 0644; 979ff4fbdf5SAlan Somers 980a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 981ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 982ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 983ff4fbdf5SAlan Somers ResultOf([](auto in) { 98429edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 985ff4fbdf5SAlan Somers }, Eq(true)), 986ff4fbdf5SAlan Somers _) 987ff4fbdf5SAlan Somers ).Times(0); 988ff4fbdf5SAlan Somers 989ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 990ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 991ff4fbdf5SAlan Somers } 992ff4fbdf5SAlan Somers 9938cfb4431SAlan Somers /* 9943fa12789SAlan Somers * ftruncate() of a file without writable permissions should succeed as long as 9953fa12789SAlan Somers * the file descriptor is writable. This is important when combined with 9963fa12789SAlan Somers * O_CREAT 9973fa12789SAlan Somers */ 9983fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file) 9993fa12789SAlan Somers { 10003fa12789SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 10013fa12789SAlan Somers const char RELPATH[] = "some_file.txt"; 10023fa12789SAlan Somers const uint64_t ino = 42; 10033fa12789SAlan Somers const mode_t mode = 0000; 10043fa12789SAlan Somers int fd; 10053fa12789SAlan Somers 1006a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1007a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1008a34cdd26SAlan Somers .WillOnce(Invoke(ReturnErrno(ENOENT))); 10093fa12789SAlan Somers expect_create(RELPATH, ino); 10103fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 10113fa12789SAlan Somers ResultOf([](auto in) { 101229edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR && 101329edc611SAlan Somers in.header.nodeid == ino && 101429edc611SAlan Somers (in.body.setattr.valid & FATTR_SIZE)); 10153fa12789SAlan Somers }, Eq(true)), 10163fa12789SAlan Somers _) 101729edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 10183fa12789SAlan Somers SET_OUT_HEADER_LEN(out, attr); 101929edc611SAlan Somers out.body.attr.attr.ino = ino; 102029edc611SAlan Somers out.body.attr.attr.mode = S_IFREG | mode; 102129edc611SAlan Somers out.body.attr.attr_valid = UINT64_MAX; 10223fa12789SAlan Somers }))); 10233fa12789SAlan Somers 10243fa12789SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 10253fa12789SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 10263fa12789SAlan Somers ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 1027*7fc0921dSAlan Somers leak(fd); 10283fa12789SAlan Somers } 10293fa12789SAlan Somers 10303fa12789SAlan Somers /* 10318cfb4431SAlan Somers * Setting the sgid bit should fail for an unprivileged user who doesn't belong 10328cfb4431SAlan Somers * to the file's group 10338cfb4431SAlan Somers */ 10348cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member) 10358cfb4431SAlan Somers { 10368cfb4431SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 10378cfb4431SAlan Somers const char RELPATH[] = "some_file.txt"; 10388cfb4431SAlan Somers const uint64_t ino = 42; 10398cfb4431SAlan Somers const mode_t oldmode = 0755; 10408cfb4431SAlan Somers const mode_t newmode = 02755; 10418cfb4431SAlan Somers uid_t uid = geteuid(); 10428cfb4431SAlan Somers gid_t gid = excluded_group(); 10438cfb4431SAlan Somers 1044a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 10458cfb4431SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 10468cfb4431SAlan Somers EXPECT_CALL(*m_mock, process( 10478cfb4431SAlan Somers ResultOf([](auto in) { 104829edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 10498cfb4431SAlan Somers }, Eq(true)), 10508cfb4431SAlan Somers _) 10518cfb4431SAlan Somers ).Times(0); 10528cfb4431SAlan Somers 10538cfb4431SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 10548cfb4431SAlan Somers EXPECT_EQ(EPERM, errno); 10558cfb4431SAlan Somers } 10568cfb4431SAlan Somers 1057e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 1058e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 1059e5ff3a7eSAlan Somers { 1060e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1061e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 1062e5ff3a7eSAlan Somers const uint64_t ino = 42; 1063e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 1064e5ff3a7eSAlan Somers const mode_t newmode = 01644; 1065e5ff3a7eSAlan Somers 1066a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1067e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1068e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 1069e5ff3a7eSAlan Somers ResultOf([](auto in) { 107029edc611SAlan Somers return (in.header.opcode == FUSE_SETATTR); 1071e5ff3a7eSAlan Somers }, Eq(true)), 1072e5ff3a7eSAlan Somers _) 1073e5ff3a7eSAlan Somers ).Times(0); 1074e5ff3a7eSAlan Somers 1075e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1076e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 1077e5ff3a7eSAlan Somers } 1078e5ff3a7eSAlan Somers 1079ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 1080ff4fbdf5SAlan Somers { 1081ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1082ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1083ff4fbdf5SAlan Somers uint64_t ino = 42; 1084ff4fbdf5SAlan Somers const char value[] = "whatever"; 1085ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1086ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1087ff4fbdf5SAlan Somers ssize_t r; 1088ff4fbdf5SAlan Somers 1089a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1090ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1091ff4fbdf5SAlan Somers expect_setxattr(0); 1092ff4fbdf5SAlan Somers 1093ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 1094ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1095ff4fbdf5SAlan Somers } 1096ff4fbdf5SAlan Somers 1097ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 1098ff4fbdf5SAlan Somers { 1099ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1100ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1101ff4fbdf5SAlan Somers uint64_t ino = 42; 1102ff4fbdf5SAlan Somers const char value[] = "whatever"; 1103ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1104ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1105ff4fbdf5SAlan Somers 1106a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1107ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1108ff4fbdf5SAlan Somers 1109ff4fbdf5SAlan Somers ASSERT_EQ(-1, 1110ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 1111ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1112ff4fbdf5SAlan Somers } 1113ff4fbdf5SAlan Somers 1114ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 1115ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 1116ff4fbdf5SAlan Somers { 1117ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1118ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1119ff4fbdf5SAlan Somers uint64_t ino = 42; 1120ff4fbdf5SAlan Somers const char value[] = "whatever"; 1121ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1122ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 1123ff4fbdf5SAlan Somers 1124a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1125ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1126ff4fbdf5SAlan Somers 1127ff4fbdf5SAlan Somers ASSERT_EQ(-1, 1128ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 1129ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1130ff4fbdf5SAlan Somers } 1131ff4fbdf5SAlan Somers 1132ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 1133ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 1134ff4fbdf5SAlan Somers { 1135ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1136ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1137ff4fbdf5SAlan Somers uint64_t ino = 42; 1138ff4fbdf5SAlan Somers const char value[] = "whatever"; 1139ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1140ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1141ff4fbdf5SAlan Somers ssize_t r; 1142ff4fbdf5SAlan Somers 1143a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1144ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1145ff4fbdf5SAlan Somers expect_setxattr(0); 1146ff4fbdf5SAlan Somers 1147ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 1148ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1149ff4fbdf5SAlan Somers } 1150ff4fbdf5SAlan Somers 1151ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 11529821f1d3SAlan Somers { 11539821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11549821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 11559821f1d3SAlan Somers uint64_t ino = 42; 11569821f1d3SAlan Somers 1157a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1158ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1159a34cdd26SAlan Somers expect_unlink(FUSE_ROOT_ID, RELPATH, 0); 11609821f1d3SAlan Somers 1161ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1162ff4fbdf5SAlan Somers } 1163ff4fbdf5SAlan Somers 11646124fd71SAlan Somers /* 11656124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 11666124fd71SAlan Somers * in VOP_LOOKUP. 11676124fd71SAlan Somers * 11686124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 11696124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 11706124fd71SAlan Somers */ 11716124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 11726124fd71SAlan Somers { 11736124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11746124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 11756124fd71SAlan Somers uint64_t ino = 42; 11766124fd71SAlan Somers 1177a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1178a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 11796124fd71SAlan Somers .Times(AnyNumber()) 11806124fd71SAlan Somers .WillRepeatedly(Invoke( 118129edc611SAlan Somers ReturnImmediate([=](auto i __unused, auto& out) { 11826124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 118329edc611SAlan Somers out.body.entry.attr.mode = S_IFREG | 0644; 118429edc611SAlan Somers out.body.entry.nodeid = ino; 118529edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 11866124fd71SAlan Somers })) 11876124fd71SAlan Somers ); 11886124fd71SAlan Somers 11896124fd71SAlan Somers /* Fill name cache */ 11906124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 11916124fd71SAlan Somers /* Despite cached name , unlink should fail */ 11926124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 11936124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 11946124fd71SAlan Somers } 11956124fd71SAlan Somers 1196ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 1197ff4fbdf5SAlan Somers { 1198ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1199ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1200ff4fbdf5SAlan Somers uint64_t ino = 42; 1201ff4fbdf5SAlan Somers 1202a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1203ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1204ff4fbdf5SAlan Somers 1205ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1206ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1207ff4fbdf5SAlan Somers } 1208ff4fbdf5SAlan Somers 12096124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 1210ff4fbdf5SAlan Somers { 1211ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1212ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1213ff4fbdf5SAlan Somers uint64_t ino = 42; 1214ff4fbdf5SAlan Somers 1215a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1); 1216ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1217ff4fbdf5SAlan Somers 1218ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1219ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 12209821f1d3SAlan Somers } 1221a90e32deSAlan Somers 1222a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1223a90e32deSAlan Somers TEST_F(Write, clear_suid) 1224a90e32deSAlan Somers { 1225a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1226a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1227a90e32deSAlan Somers struct stat sb; 1228a90e32deSAlan Somers uint64_t ino = 42; 1229a90e32deSAlan Somers mode_t oldmode = 04777; 1230a90e32deSAlan Somers mode_t newmode = 0777; 1231a90e32deSAlan Somers char wbuf[1] = {'x'}; 1232a90e32deSAlan Somers int fd; 1233a90e32deSAlan Somers 1234a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1235a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1236a90e32deSAlan Somers expect_open(ino, 0, 1); 1237bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 123818a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1239a90e32deSAlan Somers 1240a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1241a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1242a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1243a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1244a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1245*7fc0921dSAlan Somers leak(fd); 1246a90e32deSAlan Somers } 1247a90e32deSAlan Somers 1248a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 1249a90e32deSAlan Somers TEST_F(Write, clear_sgid) 1250a90e32deSAlan Somers { 1251a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1252a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1253a90e32deSAlan Somers struct stat sb; 1254a90e32deSAlan Somers uint64_t ino = 42; 1255a90e32deSAlan Somers mode_t oldmode = 02777; 1256a90e32deSAlan Somers mode_t newmode = 0777; 1257a90e32deSAlan Somers char wbuf[1] = {'x'}; 1258a90e32deSAlan Somers int fd; 1259a90e32deSAlan Somers 1260a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1261a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1262a90e32deSAlan Somers expect_open(ino, 0, 1); 1263bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 126418a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1265a90e32deSAlan Somers 1266a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1267a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1268a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1269a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1270a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1271*7fc0921dSAlan Somers leak(fd); 1272a90e32deSAlan Somers } 127318a2264eSAlan Somers 127418a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic 127518a2264eSAlan Somers * 127618a2264eSAlan Somers * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 127718a2264eSAlan Somers * may panic. That happens if the FUSE_SETATTR response indicates that the 127818a2264eSAlan Somers * file's size has changed since the write. 127918a2264eSAlan Somers */ 128018a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid) 128118a2264eSAlan Somers { 128218a2264eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 128318a2264eSAlan Somers const char RELPATH[] = "some_file.txt"; 128418a2264eSAlan Somers uint64_t ino = 42; 128518a2264eSAlan Somers mode_t oldmode = 04777; 128618a2264eSAlan Somers mode_t newmode = 0777; 128718a2264eSAlan Somers char wbuf[1] = {'x'}; 128818a2264eSAlan Somers int fd; 128918a2264eSAlan Somers 1290a34cdd26SAlan Somers expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 129118a2264eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 129218a2264eSAlan Somers expect_open(ino, 0, 1); 1293bda39894SAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 129418a2264eSAlan Somers /* XXX Return a smaller file size than what we just wrote! */ 129518a2264eSAlan Somers expect_chmod(ino, newmode, 0); 129618a2264eSAlan Somers 129718a2264eSAlan Somers fd = open(FULLPATH, O_WRONLY); 129818a2264eSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 129918a2264eSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1300*7fc0921dSAlan Somers leak(fd); 130118a2264eSAlan Somers } 130218a2264eSAlan Somers 130318a2264eSAlan Somers 1304