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) { 64ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_ACCESS); 65ff4fbdf5SAlan Somers }, Eq(true)), 66ff4fbdf5SAlan Somers _) 67ff4fbdf5SAlan Somers ).Times(0); 689821f1d3SAlan Somers } 699821f1d3SAlan Somers 709821f1d3SAlan Somers public: 71*18a2264eSAlan 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) { 75a90e32deSAlan Somers return (in->header.opcode == FUSE_SETATTR && 76a90e32deSAlan Somers in->header.nodeid == ino && 77a90e32deSAlan Somers in->body.setattr.valid == FATTR_MODE && 78a90e32deSAlan Somers in->body.setattr.mode == mode); 79a90e32deSAlan Somers }, Eq(true)), 80a90e32deSAlan Somers _) 81a90e32deSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 82a90e32deSAlan Somers SET_OUT_HEADER_LEN(out, attr); 83a90e32deSAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 84a90e32deSAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 85*18a2264eSAlan Somers out->body.attr.attr.size = size; 86a90e32deSAlan 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) { 943fa12789SAlan Somers const char *name = (const char*)in->body.bytes + 953fa12789SAlan Somers sizeof(fuse_open_in); 963fa12789SAlan Somers return (in->header.opcode == FUSE_CREATE && 973fa12789SAlan Somers (0 == strcmp(relpath, name))); 983fa12789SAlan Somers }, Eq(true)), 993fa12789SAlan Somers _) 1003fa12789SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 1013fa12789SAlan Somers SET_OUT_HEADER_LEN(out, create); 1023fa12789SAlan Somers out->body.create.entry.attr.mode = S_IFREG | 0644; 1033fa12789SAlan Somers out->body.create.entry.nodeid = ino; 1043fa12789SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 1053fa12789SAlan 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) { 114ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_GETATTR && 115ff4fbdf5SAlan Somers in->header.nodeid == ino); 116ff4fbdf5SAlan Somers }, Eq(true)), 117ff4fbdf5SAlan Somers _) 118ff4fbdf5SAlan Somers ).Times(times) 119ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 120ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 121ff4fbdf5SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 122ff4fbdf5SAlan Somers out->body.attr.attr.mode = mode; 123ff4fbdf5SAlan Somers out->body.attr.attr.size = 0; 124ff4fbdf5SAlan Somers out->body.attr.attr.uid = uid; 125474ba6faSAlan Somers out->body.attr.attr.uid = gid; 126ff4fbdf5SAlan 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) { 161ff4fbdf5SAlan 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) { 174ff4fbdf5SAlan 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) { 187ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_LISTXATTR); 188ff4fbdf5SAlan Somers }, Eq(true)), 189ff4fbdf5SAlan Somers _) 190ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) { 191ff4fbdf5SAlan 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) { 207ff4fbdf5SAlan 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) { 220ff4fbdf5SAlan 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 255ff4fbdf5SAlan Somers expect_getattr(1, 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 269ff4fbdf5SAlan Somers expect_getattr(1, 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 289ff4fbdf5SAlan Somers expect_getattr(1, 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 3104e83d655SAlan Somers expect_getattr(1, 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) { 3154e83d655SAlan Somers return (in->header.opcode == FUSE_SETATTR); 3164e83d655SAlan Somers }, Eq(true)), 3174e83d655SAlan Somers _) 3184e83d655SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out){ 3194e83d655SAlan Somers SET_OUT_HEADER_LEN(out, attr); 3204e83d655SAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 3214e83d655SAlan 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 341a2bdd737SAlan Somers expect_getattr(1, 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) { 345a2bdd737SAlan Somers return (in->header.opcode == FUSE_SETATTR && 346a2bdd737SAlan Somers in->header.nodeid == ino && 347a2bdd737SAlan Somers in->body.setattr.valid == valid && 348a2bdd737SAlan Somers in->body.setattr.mode == newmode); 349a2bdd737SAlan Somers }, Eq(true)), 350a2bdd737SAlan Somers _) 351a2bdd737SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 352a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 353a2bdd737SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 354a2bdd737SAlan Somers out->body.attr.attr.mode = S_IFREG | newmode; 355a2bdd737SAlan 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 370474ba6faSAlan Somers expect_getattr(1, 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) { 374474ba6faSAlan 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 398a2bdd737SAlan Somers expect_getattr(1, 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) { 402a2bdd737SAlan Somers return (in->header.opcode == FUSE_SETATTR && 403a2bdd737SAlan Somers in->header.nodeid == ino && 404a2bdd737SAlan Somers in->body.setattr.valid == valid && 405a2bdd737SAlan Somers in->body.setattr.mode == newmode); 406a2bdd737SAlan Somers }, Eq(true)), 407a2bdd737SAlan Somers _) 408a2bdd737SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 409a2bdd737SAlan Somers SET_OUT_HEADER_LEN(out, attr); 410a2bdd737SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 411a2bdd737SAlan Somers out->body.attr.attr.mode = S_IFREG | newmode; 412a2bdd737SAlan 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 432474ba6faSAlan Somers expect_getattr(1, 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) { 436474ba6faSAlan 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 458474ba6faSAlan Somers expect_getattr(1, 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) { 463474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR && 464474ba6faSAlan Somers in->header.nodeid == ino); 465474ba6faSAlan Somers }, Eq(true)), 466474ba6faSAlan Somers _) 4674e83d655SAlan Somers ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out){ 468474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 469474ba6faSAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 470474ba6faSAlan Somers out->body.attr.attr.uid = uid; 471474ba6faSAlan 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 484ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 485ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 486ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 487ff4fbdf5SAlan Somers 488ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 489ff4fbdf5SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 490ff4fbdf5SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 491ff4fbdf5SAlan Somers } 492ff4fbdf5SAlan Somers 493ff4fbdf5SAlan Somers TEST_F(Create, eacces) 494ff4fbdf5SAlan Somers { 495ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 496ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 497ff4fbdf5SAlan Somers 498ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 499ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 500ff4fbdf5SAlan Somers 501ff4fbdf5SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 502ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 503ff4fbdf5SAlan Somers } 504ff4fbdf5SAlan Somers 505ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 506ff4fbdf5SAlan Somers { 507ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 508ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 509ff4fbdf5SAlan Somers uint64_t ino = 42; 510ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 511ff4fbdf5SAlan Somers 512ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 513ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 514ff4fbdf5SAlan Somers 515ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 516ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 517ff4fbdf5SAlan Somers } 518ff4fbdf5SAlan Somers 519ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 520ff4fbdf5SAlan Somers { 521ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 522ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 523ff4fbdf5SAlan Somers uint64_t ino = 42; 524ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 525ff4fbdf5SAlan Somers 526ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 527ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 528ff4fbdf5SAlan Somers expect_removexattr(); 529ff4fbdf5SAlan Somers 530ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 531ff4fbdf5SAlan Somers << strerror(errno); 532ff4fbdf5SAlan Somers } 533ff4fbdf5SAlan Somers 534ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 535ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 536ff4fbdf5SAlan Somers { 537ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 538ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 539ff4fbdf5SAlan Somers uint64_t ino = 42; 540ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 541ff4fbdf5SAlan Somers 542ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 543ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 544ff4fbdf5SAlan Somers 545ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 546ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 547ff4fbdf5SAlan Somers } 548ff4fbdf5SAlan Somers 549d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */ 550d943c93eSAlan Somers TEST_F(Utimensat, utime_now) 551d943c93eSAlan Somers { 552d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 553d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 554d943c93eSAlan Somers const uint64_t ino = 42; 555d943c93eSAlan Somers /* Write permissions for everybody */ 556d943c93eSAlan Somers const mode_t mode = 0666; 557d943c93eSAlan Somers uid_t owner = 0; 558d943c93eSAlan Somers const timespec times[2] = { 559d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 560d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 561d943c93eSAlan Somers }; 562d943c93eSAlan Somers 563d943c93eSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 564d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 565d943c93eSAlan Somers EXPECT_CALL(*m_mock, process( 566d943c93eSAlan Somers ResultOf([](auto in) { 567d943c93eSAlan Somers return (in->header.opcode == FUSE_SETATTR && 568d943c93eSAlan Somers in->header.nodeid == ino && 569d943c93eSAlan Somers in->body.setattr.valid & FATTR_ATIME && 570d943c93eSAlan Somers in->body.setattr.valid & FATTR_MTIME); 571d943c93eSAlan Somers }, Eq(true)), 572d943c93eSAlan Somers _) 573d943c93eSAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 574d943c93eSAlan Somers SET_OUT_HEADER_LEN(out, attr); 575d943c93eSAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 576d943c93eSAlan Somers }))); 577d943c93eSAlan Somers 578d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 579d943c93eSAlan Somers << strerror(errno); 580d943c93eSAlan Somers } 581d943c93eSAlan Somers 582d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */ 583d943c93eSAlan Somers TEST_F(Utimensat, utime_omit) 584d943c93eSAlan Somers { 585d943c93eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 586d943c93eSAlan Somers const char RELPATH[] = "some_file.txt"; 587d943c93eSAlan Somers const uint64_t ino = 42; 588d943c93eSAlan Somers /* Write permissions for no one */ 589d943c93eSAlan Somers const mode_t mode = 0444; 590d943c93eSAlan Somers uid_t owner = 0; 591d943c93eSAlan Somers const timespec times[2] = { 592d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 593d943c93eSAlan Somers {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 594d943c93eSAlan Somers }; 595d943c93eSAlan Somers 596d943c93eSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 597d943c93eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 598d943c93eSAlan Somers 599d943c93eSAlan Somers ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 600d943c93eSAlan Somers << strerror(errno); 601d943c93eSAlan Somers } 602d943c93eSAlan Somers 603ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 604ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 605ff4fbdf5SAlan Somers { 606ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 607ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 608ff4fbdf5SAlan Somers uint64_t ino = 42; 609ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 610ff4fbdf5SAlan Somers 611ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 612ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 613ff4fbdf5SAlan Somers expect_removexattr(); 614ff4fbdf5SAlan Somers 615ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 616ff4fbdf5SAlan Somers << strerror(errno); 617ff4fbdf5SAlan Somers } 618ff4fbdf5SAlan Somers 619ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 620ff4fbdf5SAlan Somers { 621ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 622ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 623ff4fbdf5SAlan Somers uint64_t ino = 42; 624ff4fbdf5SAlan Somers char data[80]; 625ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 626ff4fbdf5SAlan Somers 627ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 628ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 629ff4fbdf5SAlan Somers 630ff4fbdf5SAlan Somers ASSERT_EQ(-1, 631ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 632ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 633ff4fbdf5SAlan Somers } 634ff4fbdf5SAlan Somers 635ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 636ff4fbdf5SAlan Somers { 637ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 638ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 639ff4fbdf5SAlan Somers uint64_t ino = 42; 640ff4fbdf5SAlan Somers char data[80]; 641ff4fbdf5SAlan Somers const char value[] = "whatever"; 642ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 643ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 644ff4fbdf5SAlan Somers ssize_t r; 645ff4fbdf5SAlan Somers 646ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 647ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 648ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 649ff4fbdf5SAlan Somers expect_getxattr( 650ff4fbdf5SAlan Somers ReturnImmediate([&](auto in __unused, auto out) { 651ff4fbdf5SAlan Somers memcpy((void*)out->body.bytes, value, value_len); 652ff4fbdf5SAlan Somers out->header.len = sizeof(out->header) + value_len; 653ff4fbdf5SAlan Somers }) 654ff4fbdf5SAlan Somers ); 655ff4fbdf5SAlan Somers 656ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 657ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 658ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 659ff4fbdf5SAlan Somers } 660ff4fbdf5SAlan Somers 661ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 662ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 663ff4fbdf5SAlan Somers { 664ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 665ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 666ff4fbdf5SAlan Somers uint64_t ino = 42; 667ff4fbdf5SAlan Somers char data[80]; 668ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 669ff4fbdf5SAlan Somers 670ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 671ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 672ff4fbdf5SAlan Somers 673ff4fbdf5SAlan Somers ASSERT_EQ(-1, 674ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 675ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 676ff4fbdf5SAlan Somers } 677ff4fbdf5SAlan Somers 678ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 679ff4fbdf5SAlan Somers { 680ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 681ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 682ff4fbdf5SAlan Somers uint64_t ino = 42; 683ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 684ff4fbdf5SAlan Somers 685ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 686ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 687ff4fbdf5SAlan Somers 688ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 689ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 690ff4fbdf5SAlan Somers } 691ff4fbdf5SAlan Somers 692ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 693ff4fbdf5SAlan Somers { 694ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 695ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 696ff4fbdf5SAlan Somers uint64_t ino = 42; 697ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 698ff4fbdf5SAlan Somers 699ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 700ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 701ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 702ff4fbdf5SAlan Somers expect_listxattr(); 703ff4fbdf5SAlan Somers 704ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 705ff4fbdf5SAlan Somers << strerror(errno); 706ff4fbdf5SAlan Somers } 707ff4fbdf5SAlan Somers 708ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 709ff4fbdf5SAlan Somers TEST_F(Listextattr, system) 710ff4fbdf5SAlan Somers { 711ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 712ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 713ff4fbdf5SAlan Somers uint64_t ino = 42; 714ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 715ff4fbdf5SAlan Somers 716ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 717ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 718ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 719ff4fbdf5SAlan Somers 720ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 721ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 722ff4fbdf5SAlan Somers } 723ff4fbdf5SAlan Somers 724ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 725ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 726ff4fbdf5SAlan Somers { 727ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 728ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 729ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 730ff4fbdf5SAlan Somers 731ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 732ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 733ff4fbdf5SAlan Somers 734ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 735ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 736ff4fbdf5SAlan Somers } 737ff4fbdf5SAlan Somers 738ff4fbdf5SAlan Somers TEST_F(Open, eacces) 739ff4fbdf5SAlan Somers { 740ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 741ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 742ff4fbdf5SAlan Somers uint64_t ino = 42; 743ff4fbdf5SAlan Somers 744ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 745ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 746ff4fbdf5SAlan Somers 747ff4fbdf5SAlan Somers EXPECT_NE(0, open(FULLPATH, O_RDWR)); 748ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 749ff4fbdf5SAlan Somers } 750ff4fbdf5SAlan Somers 7519821f1d3SAlan Somers TEST_F(Open, ok) 7529821f1d3SAlan Somers { 7539821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 7549821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 7559821f1d3SAlan Somers uint64_t ino = 42; 7569821f1d3SAlan Somers int fd; 7579821f1d3SAlan Somers 758ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 759ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 7609821f1d3SAlan Somers expect_open(ino, 0, 1); 7619821f1d3SAlan Somers 7629821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 7639821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 7649821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 7659821f1d3SAlan Somers } 7669821f1d3SAlan Somers 767ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 768ff4fbdf5SAlan Somers { 769ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 770ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 771ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 772ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 773ff4fbdf5SAlan Somers uint64_t ino = 42; 774ff4fbdf5SAlan Somers 775ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0); 776ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 777ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELDST) 778ff4fbdf5SAlan Somers .Times(AnyNumber()) 779ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 780ff4fbdf5SAlan Somers 781ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 782ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 783ff4fbdf5SAlan Somers } 784ff4fbdf5SAlan Somers 785ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 786ff4fbdf5SAlan Somers { 787ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 788ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 789ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 790ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 791ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 792ff4fbdf5SAlan Somers uint64_t src_ino = 42; 793ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 794ff4fbdf5SAlan Somers 795ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 796ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 797ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 798ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 799ff4fbdf5SAlan Somers 800ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 801ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 802ff4fbdf5SAlan Somers } 803ff4fbdf5SAlan Somers 804ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 805ff4fbdf5SAlan Somers { 806ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 807ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 808ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 809ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 810ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 811ff4fbdf5SAlan Somers uint64_t src_ino = 42; 812ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 813ff4fbdf5SAlan Somers 814ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 815ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 816ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 817ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 818ff4fbdf5SAlan Somers 819ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 820ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 821ff4fbdf5SAlan Somers } 822ff4fbdf5SAlan Somers 8236124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 824ff4fbdf5SAlan Somers { 825ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 826ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 827ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 828ff4fbdf5SAlan Somers uint64_t ino = 42; 829ff4fbdf5SAlan Somers 830ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0); 831ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 832ff4fbdf5SAlan Somers 833ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 834ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 835ff4fbdf5SAlan Somers } 836ff4fbdf5SAlan Somers 8378e45ec4eSAlan Somers /* 8388e45ec4eSAlan Somers * A user cannot move out a subdirectory that he does not own, because that 8398e45ec4eSAlan Somers * would require changing the subdirectory's ".." dirent 8408e45ec4eSAlan Somers */ 8418e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory) 8428e45ec4eSAlan Somers { 8438e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 8448e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 8458e45ec4eSAlan Somers const char RELDSTDIR[] = "d"; 8468e45ec4eSAlan Somers const char RELDST[] = "dst"; 8478e45ec4eSAlan Somers const char RELSRC[] = "src"; 8488e45ec4eSAlan Somers uint64_t ino = 42; 8498e45ec4eSAlan Somers uint64_t dstdir_ino = 43; 8508e45ec4eSAlan Somers 8518e45ec4eSAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 8528e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 8538e45ec4eSAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 8548e45ec4eSAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 8558e45ec4eSAlan Somers 8568e45ec4eSAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 8578e45ec4eSAlan Somers ASSERT_EQ(EACCES, errno); 8588e45ec4eSAlan Somers } 8598e45ec4eSAlan Somers 8608e45ec4eSAlan Somers /* 8618e45ec4eSAlan Somers * A user _can_ rename a subdirectory to which he lacks write permissions, if 8628e45ec4eSAlan Somers * it will keep the same parent 8638e45ec4eSAlan Somers */ 8648e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir) 8658e45ec4eSAlan Somers { 8668e45ec4eSAlan Somers const char FULLDST[] = "mountpoint/dst"; 8678e45ec4eSAlan Somers const char FULLSRC[] = "mountpoint/src"; 8688e45ec4eSAlan Somers const char RELDST[] = "dst"; 8698e45ec4eSAlan Somers const char RELSRC[] = "src"; 8708e45ec4eSAlan Somers uint64_t ino = 42; 8718e45ec4eSAlan Somers 8728e45ec4eSAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 8738e45ec4eSAlan Somers expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 8748e45ec4eSAlan Somers EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 8758e45ec4eSAlan Somers expect_rename(0); 8768e45ec4eSAlan Somers 8778e45ec4eSAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 8788e45ec4eSAlan Somers } 8798e45ec4eSAlan Somers 8806124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 881ff4fbdf5SAlan Somers { 882ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 883ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 8846124fd71SAlan Somers const char RELDST[] = "dst"; 885ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 886ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 887ff4fbdf5SAlan Somers uint64_t src_ino = 42; 888ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 889ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 890ff4fbdf5SAlan Somers 891ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 892ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 893ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 8946124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 8956124fd71SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 8966124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 8976124fd71SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 8986124fd71SAlan Somers out->body.entry.nodeid = dst_ino; 8996124fd71SAlan Somers out->body.entry.attr_valid = UINT64_MAX; 9006124fd71SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 9016124fd71SAlan Somers out->body.entry.attr.uid = 0; 9026124fd71SAlan Somers }))); 903ff4fbdf5SAlan Somers 904ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 905ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 906ff4fbdf5SAlan Somers } 907ff4fbdf5SAlan Somers 908ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 909ff4fbdf5SAlan Somers TEST_F(Rename, ok) 910ff4fbdf5SAlan Somers { 911ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 912ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 913ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 914ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 915ff4fbdf5SAlan Somers // The inode of the already-existing destination file 916ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 917ff4fbdf5SAlan Somers uint64_t ino = 42; 918ff4fbdf5SAlan Somers 919ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 920ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 921ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 922ff4fbdf5SAlan Somers expect_rename(0); 923ff4fbdf5SAlan Somers 924ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 925ff4fbdf5SAlan Somers } 926ff4fbdf5SAlan Somers 927ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 928ff4fbdf5SAlan Somers { 929ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 930ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 931ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 932ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 933ff4fbdf5SAlan Somers uint64_t ino = 42; 934ff4fbdf5SAlan Somers 935ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0); 936ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 937ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 938ff4fbdf5SAlan Somers expect_rename(0); 939ff4fbdf5SAlan Somers 940ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 941ff4fbdf5SAlan Somers } 942ff4fbdf5SAlan Somers 943ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 944ff4fbdf5SAlan Somers { 945ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 946ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 947ff4fbdf5SAlan Somers const uint64_t ino = 42; 948ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 949ff4fbdf5SAlan Somers const mode_t newmode = 0644; 950ff4fbdf5SAlan Somers 951ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 952ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 953ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 954ff4fbdf5SAlan Somers ResultOf([](auto in) { 955ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETATTR && 956ff4fbdf5SAlan Somers in->header.nodeid == ino && 957ff4fbdf5SAlan Somers in->body.setattr.mode == newmode); 958ff4fbdf5SAlan Somers }, Eq(true)), 959ff4fbdf5SAlan Somers _) 960ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 961ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 962ff4fbdf5SAlan Somers out->body.attr.attr.mode = S_IFREG | newmode; 963ff4fbdf5SAlan Somers }))); 964ff4fbdf5SAlan Somers 965ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 966ff4fbdf5SAlan Somers } 967ff4fbdf5SAlan Somers 968ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 969ff4fbdf5SAlan Somers { 970ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 971ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 972ff4fbdf5SAlan Somers const uint64_t ino = 42; 973ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 974ff4fbdf5SAlan Somers const mode_t newmode = 0644; 975ff4fbdf5SAlan Somers 976ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 977ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 978ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 979ff4fbdf5SAlan Somers ResultOf([](auto in) { 980ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETATTR); 981ff4fbdf5SAlan Somers }, Eq(true)), 982ff4fbdf5SAlan Somers _) 983ff4fbdf5SAlan Somers ).Times(0); 984ff4fbdf5SAlan Somers 985ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 986ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 987ff4fbdf5SAlan Somers } 988ff4fbdf5SAlan Somers 9898cfb4431SAlan Somers /* 9903fa12789SAlan Somers * ftruncate() of a file without writable permissions should succeed as long as 9913fa12789SAlan Somers * the file descriptor is writable. This is important when combined with 9923fa12789SAlan Somers * O_CREAT 9933fa12789SAlan Somers */ 9943fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file) 9953fa12789SAlan Somers { 9963fa12789SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 9973fa12789SAlan Somers const char RELPATH[] = "some_file.txt"; 9983fa12789SAlan Somers const uint64_t ino = 42; 9993fa12789SAlan Somers const mode_t mode = 0000; 10003fa12789SAlan Somers int fd; 10013fa12789SAlan Somers 10023fa12789SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 10033fa12789SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 10043fa12789SAlan Somers expect_create(RELPATH, ino); 10053fa12789SAlan Somers EXPECT_CALL(*m_mock, process( 10063fa12789SAlan Somers ResultOf([](auto in) { 10073fa12789SAlan Somers return (in->header.opcode == FUSE_SETATTR && 10083fa12789SAlan Somers in->header.nodeid == ino && 10093fa12789SAlan Somers (in->body.setattr.valid & FATTR_SIZE)); 10103fa12789SAlan Somers }, Eq(true)), 10113fa12789SAlan Somers _) 10123fa12789SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 10133fa12789SAlan Somers SET_OUT_HEADER_LEN(out, attr); 10143fa12789SAlan Somers out->body.attr.attr.ino = ino; 10153fa12789SAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 10163fa12789SAlan Somers out->body.attr.attr_valid = UINT64_MAX; 10173fa12789SAlan Somers }))); 10183fa12789SAlan Somers 10193fa12789SAlan Somers fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 10203fa12789SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 10213fa12789SAlan Somers ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 10223fa12789SAlan Somers /* Deliberately leak fd */ 10233fa12789SAlan Somers } 10243fa12789SAlan Somers 10253fa12789SAlan Somers /* 10268cfb4431SAlan Somers * Setting the sgid bit should fail for an unprivileged user who doesn't belong 10278cfb4431SAlan Somers * to the file's group 10288cfb4431SAlan Somers */ 10298cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member) 10308cfb4431SAlan Somers { 10318cfb4431SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 10328cfb4431SAlan Somers const char RELPATH[] = "some_file.txt"; 10338cfb4431SAlan Somers const uint64_t ino = 42; 10348cfb4431SAlan Somers const mode_t oldmode = 0755; 10358cfb4431SAlan Somers const mode_t newmode = 02755; 10368cfb4431SAlan Somers uid_t uid = geteuid(); 10378cfb4431SAlan Somers gid_t gid = excluded_group(); 10388cfb4431SAlan Somers 10398cfb4431SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 10408cfb4431SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 10418cfb4431SAlan Somers EXPECT_CALL(*m_mock, process( 10428cfb4431SAlan Somers ResultOf([](auto in) { 10438cfb4431SAlan Somers return (in->header.opcode == FUSE_SETATTR); 10448cfb4431SAlan Somers }, Eq(true)), 10458cfb4431SAlan Somers _) 10468cfb4431SAlan Somers ).Times(0); 10478cfb4431SAlan Somers 10488cfb4431SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 10498cfb4431SAlan Somers EXPECT_EQ(EPERM, errno); 10508cfb4431SAlan Somers } 10518cfb4431SAlan Somers 1052e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */ 1053e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file) 1054e5ff3a7eSAlan Somers { 1055e5ff3a7eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1056e5ff3a7eSAlan Somers const char RELPATH[] = "some_file.txt"; 1057e5ff3a7eSAlan Somers const uint64_t ino = 42; 1058e5ff3a7eSAlan Somers const mode_t oldmode = 0644; 1059e5ff3a7eSAlan Somers const mode_t newmode = 01644; 1060e5ff3a7eSAlan Somers 1061e5ff3a7eSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1062e5ff3a7eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1063e5ff3a7eSAlan Somers EXPECT_CALL(*m_mock, process( 1064e5ff3a7eSAlan Somers ResultOf([](auto in) { 1065e5ff3a7eSAlan Somers return (in->header.opcode == FUSE_SETATTR); 1066e5ff3a7eSAlan Somers }, Eq(true)), 1067e5ff3a7eSAlan Somers _) 1068e5ff3a7eSAlan Somers ).Times(0); 1069e5ff3a7eSAlan Somers 1070e5ff3a7eSAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 1071e5ff3a7eSAlan Somers EXPECT_EQ(EFTYPE, errno); 1072e5ff3a7eSAlan Somers } 1073e5ff3a7eSAlan Somers 1074ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 1075ff4fbdf5SAlan Somers { 1076ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1077ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1078ff4fbdf5SAlan Somers uint64_t ino = 42; 1079ff4fbdf5SAlan Somers const char value[] = "whatever"; 1080ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1081ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1082ff4fbdf5SAlan Somers ssize_t r; 1083ff4fbdf5SAlan Somers 1084ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1085ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1086ff4fbdf5SAlan Somers expect_setxattr(0); 1087ff4fbdf5SAlan Somers 1088ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 1089ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1090ff4fbdf5SAlan Somers } 1091ff4fbdf5SAlan Somers 1092ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 1093ff4fbdf5SAlan Somers { 1094ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1095ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1096ff4fbdf5SAlan Somers uint64_t ino = 42; 1097ff4fbdf5SAlan Somers const char value[] = "whatever"; 1098ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1099ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1100ff4fbdf5SAlan Somers 1101ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1102ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1103ff4fbdf5SAlan Somers 1104ff4fbdf5SAlan Somers ASSERT_EQ(-1, 1105ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 1106ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1107ff4fbdf5SAlan Somers } 1108ff4fbdf5SAlan Somers 1109ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 1110ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 1111ff4fbdf5SAlan Somers { 1112ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1113ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1114ff4fbdf5SAlan Somers uint64_t ino = 42; 1115ff4fbdf5SAlan Somers const char value[] = "whatever"; 1116ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1117ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 1118ff4fbdf5SAlan Somers 1119ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1120ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1121ff4fbdf5SAlan Somers 1122ff4fbdf5SAlan Somers ASSERT_EQ(-1, 1123ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 1124ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 1125ff4fbdf5SAlan Somers } 1126ff4fbdf5SAlan Somers 1127ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 1128ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 1129ff4fbdf5SAlan Somers { 1130ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1131ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1132ff4fbdf5SAlan Somers uint64_t ino = 42; 1133ff4fbdf5SAlan Somers const char value[] = "whatever"; 1134ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 1135ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 1136ff4fbdf5SAlan Somers ssize_t r; 1137ff4fbdf5SAlan Somers 1138ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1139ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1140ff4fbdf5SAlan Somers expect_setxattr(0); 1141ff4fbdf5SAlan Somers 1142ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 1143ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 1144ff4fbdf5SAlan Somers } 1145ff4fbdf5SAlan Somers 1146ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 11479821f1d3SAlan Somers { 11489821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11499821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 11509821f1d3SAlan Somers uint64_t ino = 42; 11519821f1d3SAlan Somers 1152ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 1153ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1154ff4fbdf5SAlan Somers expect_unlink(1, RELPATH, 0); 11559821f1d3SAlan Somers 1156ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1157ff4fbdf5SAlan Somers } 1158ff4fbdf5SAlan Somers 11596124fd71SAlan Somers /* 11606124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 11616124fd71SAlan Somers * in VOP_LOOKUP. 11626124fd71SAlan Somers * 11636124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 11646124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 11656124fd71SAlan Somers */ 11666124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 11676124fd71SAlan Somers { 11686124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 11696124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 11706124fd71SAlan Somers uint64_t ino = 42; 11716124fd71SAlan Somers 11726124fd71SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 11736124fd71SAlan Somers EXPECT_LOOKUP(1, RELPATH) 11746124fd71SAlan Somers .Times(AnyNumber()) 11756124fd71SAlan Somers .WillRepeatedly(Invoke( 11766124fd71SAlan Somers ReturnImmediate([=](auto i __unused, auto out) { 11776124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 11786124fd71SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 11796124fd71SAlan Somers out->body.entry.nodeid = ino; 11806124fd71SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 11816124fd71SAlan Somers })) 11826124fd71SAlan Somers ); 11836124fd71SAlan Somers 11846124fd71SAlan Somers /* Fill name cache */ 11856124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 11866124fd71SAlan Somers /* Despite cached name , unlink should fail */ 11876124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 11886124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 11896124fd71SAlan Somers } 11906124fd71SAlan Somers 1191ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 1192ff4fbdf5SAlan Somers { 1193ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1194ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1195ff4fbdf5SAlan Somers uint64_t ino = 42; 1196ff4fbdf5SAlan Somers 1197ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1198ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1199ff4fbdf5SAlan Somers 1200ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1201ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 1202ff4fbdf5SAlan Somers } 1203ff4fbdf5SAlan Somers 12046124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 1205ff4fbdf5SAlan Somers { 1206ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1207ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 1208ff4fbdf5SAlan Somers uint64_t ino = 42; 1209ff4fbdf5SAlan Somers 1210ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1); 1211ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1212ff4fbdf5SAlan Somers 1213ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 1214ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 12159821f1d3SAlan Somers } 1216a90e32deSAlan Somers 1217a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */ 1218a90e32deSAlan Somers TEST_F(Write, clear_suid) 1219a90e32deSAlan Somers { 1220a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1221a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1222a90e32deSAlan Somers struct stat sb; 1223a90e32deSAlan Somers uint64_t ino = 42; 1224a90e32deSAlan Somers mode_t oldmode = 04777; 1225a90e32deSAlan Somers mode_t newmode = 0777; 1226a90e32deSAlan Somers char wbuf[1] = {'x'}; 1227a90e32deSAlan Somers int fd; 1228a90e32deSAlan Somers 1229a90e32deSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1230a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1231a90e32deSAlan Somers expect_open(ino, 0, 1); 1232a90e32deSAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf); 1233*18a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1234a90e32deSAlan Somers 1235a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1236a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1237a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1238a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1239a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1240a90e32deSAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1241a90e32deSAlan Somers } 1242a90e32deSAlan Somers 1243a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */ 1244a90e32deSAlan Somers TEST_F(Write, clear_sgid) 1245a90e32deSAlan Somers { 1246a90e32deSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1247a90e32deSAlan Somers const char RELPATH[] = "some_file.txt"; 1248a90e32deSAlan Somers struct stat sb; 1249a90e32deSAlan Somers uint64_t ino = 42; 1250a90e32deSAlan Somers mode_t oldmode = 02777; 1251a90e32deSAlan Somers mode_t newmode = 0777; 1252a90e32deSAlan Somers char wbuf[1] = {'x'}; 1253a90e32deSAlan Somers int fd; 1254a90e32deSAlan Somers 1255a90e32deSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1256a90e32deSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1257a90e32deSAlan Somers expect_open(ino, 0, 1); 1258a90e32deSAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf); 1259*18a2264eSAlan Somers expect_chmod(ino, newmode, sizeof(wbuf)); 1260a90e32deSAlan Somers 1261a90e32deSAlan Somers fd = open(FULLPATH, O_WRONLY); 1262a90e32deSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1263a90e32deSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1264a90e32deSAlan Somers ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1265a90e32deSAlan Somers EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1266a90e32deSAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1267a90e32deSAlan Somers } 1268*18a2264eSAlan Somers 1269*18a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic 1270*18a2264eSAlan Somers * 1271*18a2264eSAlan Somers * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 1272*18a2264eSAlan Somers * may panic. That happens if the FUSE_SETATTR response indicates that the 1273*18a2264eSAlan Somers * file's size has changed since the write. 1274*18a2264eSAlan Somers */ 1275*18a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid) 1276*18a2264eSAlan Somers { 1277*18a2264eSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 1278*18a2264eSAlan Somers const char RELPATH[] = "some_file.txt"; 1279*18a2264eSAlan Somers uint64_t ino = 42; 1280*18a2264eSAlan Somers mode_t oldmode = 04777; 1281*18a2264eSAlan Somers mode_t newmode = 0777; 1282*18a2264eSAlan Somers char wbuf[1] = {'x'}; 1283*18a2264eSAlan Somers int fd; 1284*18a2264eSAlan Somers 1285*18a2264eSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 1286*18a2264eSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1287*18a2264eSAlan Somers expect_open(ino, 0, 1); 1288*18a2264eSAlan Somers expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, wbuf); 1289*18a2264eSAlan Somers /* XXX Return a smaller file size than what we just wrote! */ 1290*18a2264eSAlan Somers expect_chmod(ino, newmode, 0); 1291*18a2264eSAlan Somers 1292*18a2264eSAlan Somers fd = open(FULLPATH, O_WRONLY); 1293*18a2264eSAlan Somers ASSERT_LE(0, fd) << strerror(errno); 1294*18a2264eSAlan Somers ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1295*18a2264eSAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 1296*18a2264eSAlan Somers } 1297*18a2264eSAlan Somers 1298*18a2264eSAlan Somers 1299