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: 71ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times, 72*474ba6faSAlan Somers uid_t uid = 0, gid_t gid = 0) 739821f1d3SAlan Somers { 74ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 75ff4fbdf5SAlan Somers ResultOf([=](auto in) { 76ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_GETATTR && 77ff4fbdf5SAlan Somers in->header.nodeid == ino); 78ff4fbdf5SAlan Somers }, Eq(true)), 79ff4fbdf5SAlan Somers _) 80ff4fbdf5SAlan Somers ).Times(times) 81ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 82ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 83ff4fbdf5SAlan Somers out->body.attr.attr.ino = ino; // Must match nodeid 84ff4fbdf5SAlan Somers out->body.attr.attr.mode = mode; 85ff4fbdf5SAlan Somers out->body.attr.attr.size = 0; 86ff4fbdf5SAlan Somers out->body.attr.attr.uid = uid; 87*474ba6faSAlan Somers out->body.attr.attr.uid = gid; 88ff4fbdf5SAlan Somers out->body.attr.attr_valid = attr_valid; 89ff4fbdf5SAlan Somers }))); 90ff4fbdf5SAlan Somers } 91ff4fbdf5SAlan Somers 92ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 93*474ba6faSAlan Somers uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0) 94ff4fbdf5SAlan Somers { 95*474ba6faSAlan Somers FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid); 969821f1d3SAlan Somers } 979821f1d3SAlan Somers 989821f1d3SAlan Somers }; 999821f1d3SAlan Somers 1009821f1d3SAlan Somers class Access: public DefaultPermissions {}; 101*474ba6faSAlan Somers class Chown: public DefaultPermissions {}; 102*474ba6faSAlan Somers class Chgrp: public DefaultPermissions {}; 103ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {}; 1049821f1d3SAlan Somers class Open: public DefaultPermissions {}; 105ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {}; 106ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {}; 1079821f1d3SAlan Somers 108ff4fbdf5SAlan Somers /* 109ff4fbdf5SAlan Somers * Test permission handling during create, mkdir, mknod, link, symlink, and 110ff4fbdf5SAlan Somers * rename vops (they all share a common path for permission checks in 111ff4fbdf5SAlan Somers * VOP_LOOKUP) 112ff4fbdf5SAlan Somers */ 113ff4fbdf5SAlan Somers class Create: public DefaultPermissions { 114ff4fbdf5SAlan Somers public: 115ff4fbdf5SAlan Somers 116ff4fbdf5SAlan Somers void expect_create(const char *relpath, uint64_t ino) 117ff4fbdf5SAlan Somers { 118ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 119ff4fbdf5SAlan Somers ResultOf([=](auto in) { 120ff4fbdf5SAlan Somers const char *name = (const char*)in->body.bytes + 121ff4fbdf5SAlan Somers sizeof(fuse_open_in); 122ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_CREATE && 123ff4fbdf5SAlan Somers (0 == strcmp(relpath, name))); 124ff4fbdf5SAlan Somers }, Eq(true)), 125ff4fbdf5SAlan Somers _) 126ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 127ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, create); 128ff4fbdf5SAlan Somers out->body.create.entry.attr.mode = S_IFREG | 0644; 129ff4fbdf5SAlan Somers out->body.create.entry.nodeid = ino; 130ff4fbdf5SAlan Somers out->body.create.entry.entry_valid = UINT64_MAX; 131ff4fbdf5SAlan Somers out->body.create.entry.attr_valid = UINT64_MAX; 132ff4fbdf5SAlan Somers }))); 133ff4fbdf5SAlan Somers } 134ff4fbdf5SAlan Somers 135ff4fbdf5SAlan Somers }; 136ff4fbdf5SAlan Somers 137ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions { 138ff4fbdf5SAlan Somers public: 139ff4fbdf5SAlan Somers void expect_removexattr() 140ff4fbdf5SAlan Somers { 141ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 142ff4fbdf5SAlan Somers ResultOf([=](auto in) { 143ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_REMOVEXATTR); 144ff4fbdf5SAlan Somers }, Eq(true)), 145ff4fbdf5SAlan Somers _) 146ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(0))); 147ff4fbdf5SAlan Somers } 148ff4fbdf5SAlan Somers }; 149ff4fbdf5SAlan Somers 150ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions { 151ff4fbdf5SAlan Somers public: 152ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r) 153ff4fbdf5SAlan Somers { 154ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 155ff4fbdf5SAlan Somers ResultOf([=](auto in) { 156ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_GETXATTR); 157ff4fbdf5SAlan Somers }, Eq(true)), 158ff4fbdf5SAlan Somers _) 159ff4fbdf5SAlan Somers ).WillOnce(Invoke(r)); 160ff4fbdf5SAlan Somers } 161ff4fbdf5SAlan Somers }; 162ff4fbdf5SAlan Somers 163ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions { 164ff4fbdf5SAlan Somers public: 165ff4fbdf5SAlan Somers void expect_listxattr() 166ff4fbdf5SAlan Somers { 167ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 168ff4fbdf5SAlan Somers ResultOf([=](auto in) { 169ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_LISTXATTR); 170ff4fbdf5SAlan Somers }, Eq(true)), 171ff4fbdf5SAlan Somers _) 172ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto out) { 173ff4fbdf5SAlan Somers out->body.listxattr.size = 0; 174ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, listxattr); 175ff4fbdf5SAlan Somers }))); 176ff4fbdf5SAlan Somers } 177ff4fbdf5SAlan Somers }; 178ff4fbdf5SAlan Somers 179ff4fbdf5SAlan Somers class Rename: public DefaultPermissions { 180ff4fbdf5SAlan Somers public: 181ff4fbdf5SAlan Somers /* 182ff4fbdf5SAlan Somers * Expect a rename and respond with the given error. Don't both to 183ff4fbdf5SAlan Somers * validate arguments; the tests in rename.cc do that. 184ff4fbdf5SAlan Somers */ 185ff4fbdf5SAlan Somers void expect_rename(int error) 186ff4fbdf5SAlan Somers { 187ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 188ff4fbdf5SAlan Somers ResultOf([=](auto in) { 189ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_RENAME); 190ff4fbdf5SAlan Somers }, Eq(true)), 191ff4fbdf5SAlan Somers _) 192ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 193ff4fbdf5SAlan Somers } 194ff4fbdf5SAlan Somers }; 195ff4fbdf5SAlan Somers 196ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions { 197ff4fbdf5SAlan Somers public: 198ff4fbdf5SAlan Somers void expect_setxattr(int error) 199ff4fbdf5SAlan Somers { 200ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 201ff4fbdf5SAlan Somers ResultOf([=](auto in) { 202ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETXATTR); 203ff4fbdf5SAlan Somers }, Eq(true)), 204ff4fbdf5SAlan Somers _) 205ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnErrno(error))); 206ff4fbdf5SAlan Somers } 207ff4fbdf5SAlan Somers }; 208ff4fbdf5SAlan Somers 209ff4fbdf5SAlan Somers TEST_F(Access, eacces) 2109821f1d3SAlan Somers { 2119821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2129821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2139821f1d3SAlan Somers uint64_t ino = 42; 2149821f1d3SAlan Somers mode_t access_mode = X_OK; 2159821f1d3SAlan Somers 216ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 217ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 218ff4fbdf5SAlan Somers 219ff4fbdf5SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 220ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 221ff4fbdf5SAlan Somers } 222ff4fbdf5SAlan Somers 223ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs) 224ff4fbdf5SAlan Somers { 225ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 226ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 227ff4fbdf5SAlan Somers uint64_t ino = 42; 228ff4fbdf5SAlan Somers mode_t access_mode = X_OK; 229ff4fbdf5SAlan Somers 230ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, 0, 1); 231ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 232ff4fbdf5SAlan Somers expect_getattr(ino, S_IFREG | 0644, 0, 1); 2339821f1d3SAlan Somers /* 2349821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 2359821f1d3SAlan Somers * another FUSE_GETATTR or something in here. But there should not be 2369821f1d3SAlan Somers * a FUSE_ACCESS 2379821f1d3SAlan Somers */ 2389821f1d3SAlan Somers 2399821f1d3SAlan Somers ASSERT_NE(0, access(FULLPATH, access_mode)); 2409821f1d3SAlan Somers ASSERT_EQ(EACCES, errno); 2419821f1d3SAlan Somers } 2429821f1d3SAlan Somers 243ff4fbdf5SAlan Somers TEST_F(Access, ok) 2449821f1d3SAlan Somers { 2459821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 2469821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 2479821f1d3SAlan Somers uint64_t ino = 42; 2489821f1d3SAlan Somers mode_t access_mode = R_OK; 2499821f1d3SAlan Somers 250ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 251ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 2529821f1d3SAlan Somers /* 2539821f1d3SAlan Somers * Once default_permissions is properly implemented, there might be 25491ff3a0dSAlan Somers * another FUSE_GETATTR or something in here. 2559821f1d3SAlan Somers */ 2569821f1d3SAlan Somers 2579821f1d3SAlan Somers ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 2589821f1d3SAlan Somers } 2599821f1d3SAlan Somers 260*474ba6faSAlan Somers /* Only root may change a file's owner */ 261*474ba6faSAlan Somers TEST_F(Chown, eperm) 262*474ba6faSAlan Somers { 263*474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 264*474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 265*474ba6faSAlan Somers const uint64_t ino = 42; 266*474ba6faSAlan Somers const mode_t mode = 0755; 267*474ba6faSAlan Somers 268*474ba6faSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 269*474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 270*474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 271*474ba6faSAlan Somers ResultOf([](auto in) { 272*474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR); 273*474ba6faSAlan Somers }, Eq(true)), 274*474ba6faSAlan Somers _) 275*474ba6faSAlan Somers ).Times(0); 276*474ba6faSAlan Somers 277*474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, 0, -1)); 278*474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 279*474ba6faSAlan Somers } 280*474ba6faSAlan Somers 281*474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */ 282*474ba6faSAlan Somers TEST_F(Chgrp, eperm) 283*474ba6faSAlan Somers { 284*474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 285*474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 286*474ba6faSAlan Somers const uint64_t ino = 42; 287*474ba6faSAlan Somers const mode_t mode = 0755; 288*474ba6faSAlan Somers int ngroups = 64; 289*474ba6faSAlan Somers gid_t groups[ngroups]; 290*474ba6faSAlan Somers uid_t uid; 291*474ba6faSAlan Somers gid_t gid, newgid; 292*474ba6faSAlan Somers int i; 293*474ba6faSAlan Somers 294*474ba6faSAlan Somers uid = geteuid(); 295*474ba6faSAlan Somers gid = getegid(); 296*474ba6faSAlan Somers getgrouplist(getlogin(), getegid(), groups, &ngroups); 297*474ba6faSAlan Somers for (newgid = 0; newgid >= 0; newgid++) { 298*474ba6faSAlan Somers bool belongs = false; 299*474ba6faSAlan Somers 300*474ba6faSAlan Somers for (i = 0; i < ngroups; i++) { 301*474ba6faSAlan Somers if (groups[i] == newgid) 302*474ba6faSAlan Somers belongs = true; 303*474ba6faSAlan Somers } 304*474ba6faSAlan Somers if (!belongs) 305*474ba6faSAlan Somers break; 306*474ba6faSAlan Somers } 307*474ba6faSAlan Somers /* newgid is now a group to which the current user does not belong */ 308*474ba6faSAlan Somers 309*474ba6faSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 310*474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 311*474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 312*474ba6faSAlan Somers ResultOf([](auto in) { 313*474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR); 314*474ba6faSAlan Somers }, Eq(true)), 315*474ba6faSAlan Somers _) 316*474ba6faSAlan Somers ).Times(0); 317*474ba6faSAlan Somers 318*474ba6faSAlan Somers EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 319*474ba6faSAlan Somers EXPECT_EQ(EPERM, errno); 320*474ba6faSAlan Somers } 321*474ba6faSAlan Somers 322*474ba6faSAlan Somers TEST_F(Chgrp, ok) 323*474ba6faSAlan Somers { 324*474ba6faSAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 325*474ba6faSAlan Somers const char RELPATH[] = "some_file.txt"; 326*474ba6faSAlan Somers const uint64_t ino = 42; 327*474ba6faSAlan Somers const mode_t mode = 0755; 328*474ba6faSAlan Somers uid_t uid; 329*474ba6faSAlan Somers gid_t gid, newgid; 330*474ba6faSAlan Somers 331*474ba6faSAlan Somers uid = geteuid(); 332*474ba6faSAlan Somers gid = 0; 333*474ba6faSAlan Somers newgid = getegid(); 334*474ba6faSAlan Somers 335*474ba6faSAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 336*474ba6faSAlan Somers expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 337*474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 338*474ba6faSAlan Somers ResultOf([](auto in) { 339*474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR); 340*474ba6faSAlan Somers }, Eq(true)), 341*474ba6faSAlan Somers _) 342*474ba6faSAlan Somers ).Times(0); 343*474ba6faSAlan Somers EXPECT_CALL(*m_mock, process( 344*474ba6faSAlan Somers ResultOf([](auto in) { 345*474ba6faSAlan Somers return (in->header.opcode == FUSE_SETATTR && 346*474ba6faSAlan Somers in->header.nodeid == ino); 347*474ba6faSAlan Somers }, Eq(true)), 348*474ba6faSAlan Somers _) 349*474ba6faSAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 350*474ba6faSAlan Somers SET_OUT_HEADER_LEN(out, attr); 351*474ba6faSAlan Somers out->body.attr.attr.mode = S_IFREG | mode; 352*474ba6faSAlan Somers out->body.attr.attr.uid = uid; 353*474ba6faSAlan Somers out->body.attr.attr.gid = newgid; 354*474ba6faSAlan Somers }))); 355*474ba6faSAlan Somers 356*474ba6faSAlan Somers EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 357*474ba6faSAlan Somers } 358*474ba6faSAlan Somers 359ff4fbdf5SAlan Somers TEST_F(Create, ok) 360ff4fbdf5SAlan Somers { 361ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 362ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 363ff4fbdf5SAlan Somers uint64_t ino = 42; 364ff4fbdf5SAlan Somers int fd; 365ff4fbdf5SAlan Somers 366ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 367ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 368ff4fbdf5SAlan Somers expect_create(RELPATH, ino); 369ff4fbdf5SAlan Somers 370ff4fbdf5SAlan Somers fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 371ff4fbdf5SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 372ff4fbdf5SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 373ff4fbdf5SAlan Somers } 374ff4fbdf5SAlan Somers 375ff4fbdf5SAlan Somers TEST_F(Create, eacces) 376ff4fbdf5SAlan Somers { 377ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 378ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 379ff4fbdf5SAlan Somers 380ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 381ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 382ff4fbdf5SAlan Somers 383ff4fbdf5SAlan Somers EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 384ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 385ff4fbdf5SAlan Somers } 386ff4fbdf5SAlan Somers 387ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces) 388ff4fbdf5SAlan Somers { 389ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 390ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 391ff4fbdf5SAlan Somers uint64_t ino = 42; 392ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 393ff4fbdf5SAlan Somers 394ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 395ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 396ff4fbdf5SAlan Somers 397ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 398ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 399ff4fbdf5SAlan Somers } 400ff4fbdf5SAlan Somers 401ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok) 402ff4fbdf5SAlan Somers { 403ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 404ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 405ff4fbdf5SAlan Somers uint64_t ino = 42; 406ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 407ff4fbdf5SAlan Somers 408ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 409ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 410ff4fbdf5SAlan Somers expect_removexattr(); 411ff4fbdf5SAlan Somers 412ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 413ff4fbdf5SAlan Somers << strerror(errno); 414ff4fbdf5SAlan Somers } 415ff4fbdf5SAlan Somers 416ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */ 417ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system) 418ff4fbdf5SAlan Somers { 419ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 420ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 421ff4fbdf5SAlan Somers uint64_t ino = 42; 422ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 423ff4fbdf5SAlan Somers 424ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 425ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 426ff4fbdf5SAlan Somers 427ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 428ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 429ff4fbdf5SAlan Somers } 430ff4fbdf5SAlan Somers 431ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */ 432ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user) 433ff4fbdf5SAlan Somers { 434ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 435ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 436ff4fbdf5SAlan Somers uint64_t ino = 42; 437ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 438ff4fbdf5SAlan Somers 439ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 440ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 441ff4fbdf5SAlan Somers expect_removexattr(); 442ff4fbdf5SAlan Somers 443ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 444ff4fbdf5SAlan Somers << strerror(errno); 445ff4fbdf5SAlan Somers } 446ff4fbdf5SAlan Somers 447ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces) 448ff4fbdf5SAlan Somers { 449ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 450ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 451ff4fbdf5SAlan Somers uint64_t ino = 42; 452ff4fbdf5SAlan Somers char data[80]; 453ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 454ff4fbdf5SAlan Somers 455ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 456ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 457ff4fbdf5SAlan Somers 458ff4fbdf5SAlan Somers ASSERT_EQ(-1, 459ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 460ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 461ff4fbdf5SAlan Somers } 462ff4fbdf5SAlan Somers 463ff4fbdf5SAlan Somers TEST_F(Getextattr, ok) 464ff4fbdf5SAlan Somers { 465ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 466ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 467ff4fbdf5SAlan Somers uint64_t ino = 42; 468ff4fbdf5SAlan Somers char data[80]; 469ff4fbdf5SAlan Somers const char value[] = "whatever"; 470ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 471ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 472ff4fbdf5SAlan Somers ssize_t r; 473ff4fbdf5SAlan Somers 474ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 475ff4fbdf5SAlan Somers /* Getting user attributes only requires read access */ 476ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 477ff4fbdf5SAlan Somers expect_getxattr( 478ff4fbdf5SAlan Somers ReturnImmediate([&](auto in __unused, auto out) { 479ff4fbdf5SAlan Somers memcpy((void*)out->body.bytes, value, value_len); 480ff4fbdf5SAlan Somers out->header.len = sizeof(out->header) + value_len; 481ff4fbdf5SAlan Somers }) 482ff4fbdf5SAlan Somers ); 483ff4fbdf5SAlan Somers 484ff4fbdf5SAlan Somers r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 485ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 486ff4fbdf5SAlan Somers EXPECT_STREQ(value, data); 487ff4fbdf5SAlan Somers } 488ff4fbdf5SAlan Somers 489ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */ 490ff4fbdf5SAlan Somers TEST_F(Getextattr, system) 491ff4fbdf5SAlan Somers { 492ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 493ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 494ff4fbdf5SAlan Somers uint64_t ino = 42; 495ff4fbdf5SAlan Somers char data[80]; 496ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 497ff4fbdf5SAlan Somers 498ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 499ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 500ff4fbdf5SAlan Somers 501ff4fbdf5SAlan Somers ASSERT_EQ(-1, 502ff4fbdf5SAlan Somers extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 503ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 504ff4fbdf5SAlan Somers } 505ff4fbdf5SAlan Somers 506ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces) 507ff4fbdf5SAlan Somers { 508ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 509ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 510ff4fbdf5SAlan Somers uint64_t ino = 42; 511ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 512ff4fbdf5SAlan Somers 513ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 514ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 515ff4fbdf5SAlan Somers 516ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 517ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 518ff4fbdf5SAlan Somers } 519ff4fbdf5SAlan Somers 520ff4fbdf5SAlan Somers TEST_F(Listextattr, ok) 521ff4fbdf5SAlan Somers { 522ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 523ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 524ff4fbdf5SAlan Somers uint64_t ino = 42; 525ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 526ff4fbdf5SAlan Somers 527ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 528ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 529ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 530ff4fbdf5SAlan Somers expect_listxattr(); 531ff4fbdf5SAlan Somers 532ff4fbdf5SAlan Somers ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 533ff4fbdf5SAlan Somers << strerror(errno); 534ff4fbdf5SAlan Somers } 535ff4fbdf5SAlan Somers 536ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */ 537ff4fbdf5SAlan Somers TEST_F(Listextattr, 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 544ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 545ff4fbdf5SAlan Somers /* Listing user extended attributes merely requires read access */ 546ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 547ff4fbdf5SAlan Somers 548ff4fbdf5SAlan Somers ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 549ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 550ff4fbdf5SAlan Somers } 551ff4fbdf5SAlan Somers 552ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */ 553ff4fbdf5SAlan Somers TEST_F(Lookup, eacces) 554ff4fbdf5SAlan Somers { 555ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 556ff4fbdf5SAlan Somers const char RELDIRPATH[] = "some_dir"; 557ff4fbdf5SAlan Somers uint64_t dir_ino = 42; 558ff4fbdf5SAlan Somers 559ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 560ff4fbdf5SAlan Somers expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 561ff4fbdf5SAlan Somers 562ff4fbdf5SAlan Somers EXPECT_EQ(-1, access(FULLPATH, F_OK)); 563ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 564ff4fbdf5SAlan Somers } 565ff4fbdf5SAlan Somers 566ff4fbdf5SAlan Somers TEST_F(Open, eacces) 567ff4fbdf5SAlan Somers { 568ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 569ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 570ff4fbdf5SAlan Somers uint64_t ino = 42; 571ff4fbdf5SAlan Somers 572ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 573ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 574ff4fbdf5SAlan Somers 575ff4fbdf5SAlan Somers EXPECT_NE(0, open(FULLPATH, O_RDWR)); 576ff4fbdf5SAlan Somers EXPECT_EQ(EACCES, errno); 577ff4fbdf5SAlan Somers } 578ff4fbdf5SAlan Somers 5799821f1d3SAlan Somers TEST_F(Open, ok) 5809821f1d3SAlan Somers { 5819821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 5829821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 5839821f1d3SAlan Somers uint64_t ino = 42; 5849821f1d3SAlan Somers int fd; 5859821f1d3SAlan Somers 586ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 587ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 5889821f1d3SAlan Somers expect_open(ino, 0, 1); 5899821f1d3SAlan Somers 5909821f1d3SAlan Somers fd = open(FULLPATH, O_RDONLY); 5919821f1d3SAlan Somers EXPECT_LE(0, fd) << strerror(errno); 5929821f1d3SAlan Somers /* Deliberately leak fd. close(2) will be tested in release.cc */ 5939821f1d3SAlan Somers } 5949821f1d3SAlan Somers 595ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir) 596ff4fbdf5SAlan Somers { 597ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 598ff4fbdf5SAlan Somers const char RELDST[] = "d/dst"; 599ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 600ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 601ff4fbdf5SAlan Somers uint64_t ino = 42; 602ff4fbdf5SAlan Somers 603ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0); 604ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 605ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELDST) 606ff4fbdf5SAlan Somers .Times(AnyNumber()) 607ff4fbdf5SAlan Somers .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 608ff4fbdf5SAlan Somers 609ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 610ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 611ff4fbdf5SAlan Somers } 612ff4fbdf5SAlan Somers 613ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating) 614ff4fbdf5SAlan Somers { 615ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 616ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 617ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 618ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 619ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 620ff4fbdf5SAlan Somers uint64_t src_ino = 42; 621ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 622ff4fbdf5SAlan Somers 623ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 624ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 625ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 626ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 627ff4fbdf5SAlan Somers 628ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 629ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 630ff4fbdf5SAlan Somers } 631ff4fbdf5SAlan Somers 632ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing) 633ff4fbdf5SAlan Somers { 634ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 635ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 636ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 637ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 638ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 639ff4fbdf5SAlan Somers uint64_t src_ino = 42; 640ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 641ff4fbdf5SAlan Somers 642ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 643ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 644ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 645ff4fbdf5SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 646ff4fbdf5SAlan Somers 647ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 648ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 649ff4fbdf5SAlan Somers } 650ff4fbdf5SAlan Somers 6516124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir) 652ff4fbdf5SAlan Somers { 653ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 654ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 655ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 656ff4fbdf5SAlan Somers uint64_t ino = 42; 657ff4fbdf5SAlan Somers 658ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0); 659ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 660ff4fbdf5SAlan Somers 661ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 662ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 663ff4fbdf5SAlan Somers } 664ff4fbdf5SAlan Somers 6656124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir) 666ff4fbdf5SAlan Somers { 667ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/d/dst"; 668ff4fbdf5SAlan Somers const char RELDSTDIR[] = "d"; 6696124fd71SAlan Somers const char RELDST[] = "dst"; 670ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 671ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 672ff4fbdf5SAlan Somers uint64_t src_ino = 42; 673ff4fbdf5SAlan Somers uint64_t dstdir_ino = 43; 674ff4fbdf5SAlan Somers uint64_t dst_ino = 44; 675ff4fbdf5SAlan Somers 676ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0); 677ff4fbdf5SAlan Somers expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 678ff4fbdf5SAlan Somers expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 6796124fd71SAlan Somers EXPECT_LOOKUP(dstdir_ino, RELDST) 6806124fd71SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 6816124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 6826124fd71SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 6836124fd71SAlan Somers out->body.entry.nodeid = dst_ino; 6846124fd71SAlan Somers out->body.entry.attr_valid = UINT64_MAX; 6856124fd71SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 6866124fd71SAlan Somers out->body.entry.attr.uid = 0; 6876124fd71SAlan Somers }))); 688ff4fbdf5SAlan Somers 689ff4fbdf5SAlan Somers ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 690ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 691ff4fbdf5SAlan Somers } 692ff4fbdf5SAlan Somers 693ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */ 694ff4fbdf5SAlan Somers TEST_F(Rename, ok) 695ff4fbdf5SAlan Somers { 696ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 697ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 698ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 699ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 700ff4fbdf5SAlan Somers // The inode of the already-existing destination file 701ff4fbdf5SAlan Somers uint64_t dst_ino = 2; 702ff4fbdf5SAlan Somers uint64_t ino = 42; 703ff4fbdf5SAlan Somers 704ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 705ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 706ff4fbdf5SAlan Somers expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 707ff4fbdf5SAlan Somers expect_rename(0); 708ff4fbdf5SAlan Somers 709ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 710ff4fbdf5SAlan Somers } 711ff4fbdf5SAlan Somers 712ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 713ff4fbdf5SAlan Somers { 714ff4fbdf5SAlan Somers const char FULLDST[] = "mountpoint/dst"; 715ff4fbdf5SAlan Somers const char RELDST[] = "dst"; 716ff4fbdf5SAlan Somers const char FULLSRC[] = "mountpoint/src"; 717ff4fbdf5SAlan Somers const char RELSRC[] = "src"; 718ff4fbdf5SAlan Somers uint64_t ino = 42; 719ff4fbdf5SAlan Somers 720ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0); 721ff4fbdf5SAlan Somers expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 722ff4fbdf5SAlan Somers EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 723ff4fbdf5SAlan Somers expect_rename(0); 724ff4fbdf5SAlan Somers 725ff4fbdf5SAlan Somers ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 726ff4fbdf5SAlan Somers } 727ff4fbdf5SAlan Somers 728ff4fbdf5SAlan Somers TEST_F(Setattr, ok) 729ff4fbdf5SAlan Somers { 730ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 731ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 732ff4fbdf5SAlan Somers const uint64_t ino = 42; 733ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 734ff4fbdf5SAlan Somers const mode_t newmode = 0644; 735ff4fbdf5SAlan Somers 736ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 737ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 738ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 739ff4fbdf5SAlan Somers ResultOf([](auto in) { 740ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETATTR && 741ff4fbdf5SAlan Somers in->header.nodeid == ino && 742ff4fbdf5SAlan Somers in->body.setattr.mode == newmode); 743ff4fbdf5SAlan Somers }, Eq(true)), 744ff4fbdf5SAlan Somers _) 745ff4fbdf5SAlan Somers ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 746ff4fbdf5SAlan Somers SET_OUT_HEADER_LEN(out, attr); 747ff4fbdf5SAlan Somers out->body.attr.attr.mode = S_IFREG | newmode; 748ff4fbdf5SAlan Somers }))); 749ff4fbdf5SAlan Somers 750ff4fbdf5SAlan Somers EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 751ff4fbdf5SAlan Somers } 752ff4fbdf5SAlan Somers 753ff4fbdf5SAlan Somers TEST_F(Setattr, eacces) 754ff4fbdf5SAlan Somers { 755ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 756ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 757ff4fbdf5SAlan Somers const uint64_t ino = 42; 758ff4fbdf5SAlan Somers const mode_t oldmode = 0755; 759ff4fbdf5SAlan Somers const mode_t newmode = 0644; 760ff4fbdf5SAlan Somers 761ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 762ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 763ff4fbdf5SAlan Somers EXPECT_CALL(*m_mock, process( 764ff4fbdf5SAlan Somers ResultOf([](auto in) { 765ff4fbdf5SAlan Somers return (in->header.opcode == FUSE_SETATTR); 766ff4fbdf5SAlan Somers }, Eq(true)), 767ff4fbdf5SAlan Somers _) 768ff4fbdf5SAlan Somers ).Times(0); 769ff4fbdf5SAlan Somers 770ff4fbdf5SAlan Somers EXPECT_NE(0, chmod(FULLPATH, newmode)); 771ff4fbdf5SAlan Somers EXPECT_EQ(EPERM, errno); 772ff4fbdf5SAlan Somers } 773ff4fbdf5SAlan Somers 774ff4fbdf5SAlan Somers TEST_F(Setextattr, ok) 775ff4fbdf5SAlan Somers { 776ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 777ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 778ff4fbdf5SAlan Somers uint64_t ino = 42; 779ff4fbdf5SAlan Somers const char value[] = "whatever"; 780ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 781ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 782ff4fbdf5SAlan Somers ssize_t r; 783ff4fbdf5SAlan Somers 784ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 785ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 786ff4fbdf5SAlan Somers expect_setxattr(0); 787ff4fbdf5SAlan Somers 788ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 789ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 790ff4fbdf5SAlan Somers } 791ff4fbdf5SAlan Somers 792ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces) 793ff4fbdf5SAlan Somers { 794ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 795ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 796ff4fbdf5SAlan Somers uint64_t ino = 42; 797ff4fbdf5SAlan Somers const char value[] = "whatever"; 798ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 799ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 800ff4fbdf5SAlan Somers 801ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 802ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 803ff4fbdf5SAlan Somers 804ff4fbdf5SAlan Somers ASSERT_EQ(-1, 805ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 806ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 807ff4fbdf5SAlan Somers } 808ff4fbdf5SAlan Somers 809ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges 810ff4fbdf5SAlan Somers TEST_F(Setextattr, system) 811ff4fbdf5SAlan Somers { 812ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 813ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 814ff4fbdf5SAlan Somers uint64_t ino = 42; 815ff4fbdf5SAlan Somers const char value[] = "whatever"; 816ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 817ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_SYSTEM; 818ff4fbdf5SAlan Somers 819ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 820ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 821ff4fbdf5SAlan Somers 822ff4fbdf5SAlan Somers ASSERT_EQ(-1, 823ff4fbdf5SAlan Somers extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len)); 824ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 825ff4fbdf5SAlan Somers } 826ff4fbdf5SAlan Somers 827ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges 828ff4fbdf5SAlan Somers TEST_F(Setextattr, user) 829ff4fbdf5SAlan Somers { 830ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 831ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 832ff4fbdf5SAlan Somers uint64_t ino = 42; 833ff4fbdf5SAlan Somers const char value[] = "whatever"; 834ff4fbdf5SAlan Somers ssize_t value_len = strlen(value) + 1; 835ff4fbdf5SAlan Somers int ns = EXTATTR_NAMESPACE_USER; 836ff4fbdf5SAlan Somers ssize_t r; 837ff4fbdf5SAlan Somers 838ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 839ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 840ff4fbdf5SAlan Somers expect_setxattr(0); 841ff4fbdf5SAlan Somers 842ff4fbdf5SAlan Somers r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len); 843ff4fbdf5SAlan Somers ASSERT_EQ(value_len, r) << strerror(errno); 844ff4fbdf5SAlan Somers } 845ff4fbdf5SAlan Somers 846ff4fbdf5SAlan Somers TEST_F(Unlink, ok) 8479821f1d3SAlan Somers { 8489821f1d3SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 8499821f1d3SAlan Somers const char RELPATH[] = "some_file.txt"; 8509821f1d3SAlan Somers uint64_t ino = 42; 8519821f1d3SAlan Somers 852ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1); 853ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 854ff4fbdf5SAlan Somers expect_unlink(1, RELPATH, 0); 8559821f1d3SAlan Somers 856ff4fbdf5SAlan Somers ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 857ff4fbdf5SAlan Somers } 858ff4fbdf5SAlan Somers 8596124fd71SAlan Somers /* 8606124fd71SAlan Somers * Ensure that a cached name doesn't cause unlink to bypass permission checks 8616124fd71SAlan Somers * in VOP_LOOKUP. 8626124fd71SAlan Somers * 8636124fd71SAlan Somers * This test should pass because lookup(9) purges the namecache entry by doing 8646124fd71SAlan Somers * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 8656124fd71SAlan Somers */ 8666124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory) 8676124fd71SAlan Somers { 8686124fd71SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 8696124fd71SAlan Somers const char RELPATH[] = "some_file.txt"; 8706124fd71SAlan Somers uint64_t ino = 42; 8716124fd71SAlan Somers 8726124fd71SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 8736124fd71SAlan Somers EXPECT_LOOKUP(1, RELPATH) 8746124fd71SAlan Somers .Times(AnyNumber()) 8756124fd71SAlan Somers .WillRepeatedly(Invoke( 8766124fd71SAlan Somers ReturnImmediate([=](auto i __unused, auto out) { 8776124fd71SAlan Somers SET_OUT_HEADER_LEN(out, entry); 8786124fd71SAlan Somers out->body.entry.attr.mode = S_IFREG | 0644; 8796124fd71SAlan Somers out->body.entry.nodeid = ino; 8806124fd71SAlan Somers out->body.entry.entry_valid = UINT64_MAX; 8816124fd71SAlan Somers })) 8826124fd71SAlan Somers ); 8836124fd71SAlan Somers 8846124fd71SAlan Somers /* Fill name cache */ 8856124fd71SAlan Somers ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 8866124fd71SAlan Somers /* Despite cached name , unlink should fail */ 8876124fd71SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 8886124fd71SAlan Somers ASSERT_EQ(EACCES, errno); 8896124fd71SAlan Somers } 8906124fd71SAlan Somers 891ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory) 892ff4fbdf5SAlan Somers { 893ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 894ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 895ff4fbdf5SAlan Somers uint64_t ino = 42; 896ff4fbdf5SAlan Somers 897ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1); 898ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 899ff4fbdf5SAlan Somers 900ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 901ff4fbdf5SAlan Somers ASSERT_EQ(EACCES, errno); 902ff4fbdf5SAlan Somers } 903ff4fbdf5SAlan Somers 9046124fd71SAlan Somers TEST_F(Unlink, sticky_directory) 905ff4fbdf5SAlan Somers { 906ff4fbdf5SAlan Somers const char FULLPATH[] = "mountpoint/some_file.txt"; 907ff4fbdf5SAlan Somers const char RELPATH[] = "some_file.txt"; 908ff4fbdf5SAlan Somers uint64_t ino = 42; 909ff4fbdf5SAlan Somers 910ff4fbdf5SAlan Somers expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1); 911ff4fbdf5SAlan Somers expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 912ff4fbdf5SAlan Somers 913ff4fbdf5SAlan Somers ASSERT_EQ(-1, unlink(FULLPATH)); 914ff4fbdf5SAlan Somers ASSERT_EQ(EPERM, errno); 9159821f1d3SAlan Somers } 916