1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 /* 32 * Tests for the "default_permissions" mount option. They must be in their own 33 * file so they can be run as an unprivileged user 34 */ 35 36 extern "C" { 37 #include <sys/types.h> 38 #include <sys/extattr.h> 39 40 #include <fcntl.h> 41 #include <semaphore.h> 42 #include <unistd.h> 43 } 44 45 #include "mockfs.hh" 46 #include "utils.hh" 47 48 using namespace testing; 49 50 class DefaultPermissions: public FuseTest { 51 52 virtual void SetUp() { 53 m_default_permissions = true; 54 FuseTest::SetUp(); 55 if (HasFatalFailure() || IsSkipped()) 56 return; 57 58 if (geteuid() == 0) { 59 GTEST_SKIP() << "This test requires an unprivileged user"; 60 } 61 62 /* With -o default_permissions, FUSE_ACCESS should never be called */ 63 EXPECT_CALL(*m_mock, process( 64 ResultOf([=](auto in) { 65 return (in.header.opcode == FUSE_ACCESS); 66 }, Eq(true)), 67 _) 68 ).Times(0); 69 } 70 71 public: 72 void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0) 73 { 74 EXPECT_CALL(*m_mock, process( 75 ResultOf([=](auto in) { 76 return (in.header.opcode == FUSE_SETATTR && 77 in.header.nodeid == ino && 78 in.body.setattr.valid == FATTR_MODE && 79 in.body.setattr.mode == mode); 80 }, Eq(true)), 81 _) 82 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 83 SET_OUT_HEADER_LEN(out, attr); 84 out.body.attr.attr.ino = ino; // Must match nodeid 85 out.body.attr.attr.mode = S_IFREG | mode; 86 out.body.attr.attr.size = size; 87 out.body.attr.attr_valid = UINT64_MAX; 88 }))); 89 } 90 91 void expect_create(const char *relpath, uint64_t ino) 92 { 93 EXPECT_CALL(*m_mock, process( 94 ResultOf([=](auto in) { 95 const char *name = (const char*)in.body.bytes + 96 sizeof(fuse_create_in); 97 return (in.header.opcode == FUSE_CREATE && 98 (0 == strcmp(relpath, name))); 99 }, Eq(true)), 100 _) 101 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 102 SET_OUT_HEADER_LEN(out, create); 103 out.body.create.entry.attr.mode = S_IFREG | 0644; 104 out.body.create.entry.nodeid = ino; 105 out.body.create.entry.entry_valid = UINT64_MAX; 106 out.body.create.entry.attr_valid = UINT64_MAX; 107 }))); 108 } 109 110 void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out, 111 uint64_t off_out, uint64_t len) 112 { 113 EXPECT_CALL(*m_mock, process( 114 ResultOf([=](auto in) { 115 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 116 in.header.nodeid == ino_in && 117 in.body.copy_file_range.off_in == off_in && 118 in.body.copy_file_range.nodeid_out == ino_out && 119 in.body.copy_file_range.off_out == off_out && 120 in.body.copy_file_range.len == len); 121 }, Eq(true)), 122 _) 123 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 124 SET_OUT_HEADER_LEN(out, write); 125 out.body.write.size = len; 126 }))); 127 } 128 129 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times, 130 uid_t uid = 0, gid_t gid = 0) 131 { 132 EXPECT_CALL(*m_mock, process( 133 ResultOf([=](auto in) { 134 return (in.header.opcode == FUSE_GETATTR && 135 in.header.nodeid == ino); 136 }, Eq(true)), 137 _) 138 ).Times(times) 139 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 140 SET_OUT_HEADER_LEN(out, attr); 141 out.body.attr.attr.ino = ino; // Must match nodeid 142 out.body.attr.attr.mode = mode; 143 out.body.attr.attr.size = 0; 144 out.body.attr.attr.uid = uid; 145 out.body.attr.attr.gid = gid; 146 out.body.attr.attr_valid = attr_valid; 147 }))); 148 } 149 150 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 151 uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0) 152 { 153 FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid); 154 } 155 156 }; 157 158 class Access: public DefaultPermissions {}; 159 class Chown: public DefaultPermissions {}; 160 class Chgrp: public DefaultPermissions {}; 161 class CopyFileRange: public DefaultPermissions {}; 162 class Fspacectl: public DefaultPermissions {}; 163 class Lookup: public DefaultPermissions {}; 164 class Open: public DefaultPermissions {}; 165 class PosixFallocate: public DefaultPermissions {}; 166 class Setattr: public DefaultPermissions {}; 167 class Unlink: public DefaultPermissions {}; 168 class Utimensat: public DefaultPermissions {}; 169 class Write: public DefaultPermissions {}; 170 171 /* 172 * Test permission handling during create, mkdir, mknod, link, symlink, and 173 * rename vops (they all share a common path for permission checks in 174 * VOP_LOOKUP) 175 */ 176 class Create: public DefaultPermissions {}; 177 178 class Deleteextattr: public DefaultPermissions { 179 public: 180 void expect_removexattr() 181 { 182 EXPECT_CALL(*m_mock, process( 183 ResultOf([=](auto in) { 184 return (in.header.opcode == FUSE_REMOVEXATTR); 185 }, Eq(true)), 186 _) 187 ).WillOnce(Invoke(ReturnErrno(0))); 188 } 189 }; 190 191 class Getextattr: public DefaultPermissions { 192 public: 193 void expect_getxattr(ProcessMockerT r) 194 { 195 EXPECT_CALL(*m_mock, process( 196 ResultOf([=](auto in) { 197 return (in.header.opcode == FUSE_GETXATTR); 198 }, Eq(true)), 199 _) 200 ).WillOnce(Invoke(r)); 201 } 202 }; 203 204 class Listextattr: public DefaultPermissions { 205 public: 206 void expect_listxattr() 207 { 208 EXPECT_CALL(*m_mock, process( 209 ResultOf([=](auto in) { 210 return (in.header.opcode == FUSE_LISTXATTR); 211 }, Eq(true)), 212 _) 213 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 214 out.body.listxattr.size = 0; 215 SET_OUT_HEADER_LEN(out, listxattr); 216 }))); 217 } 218 }; 219 220 class Rename: public DefaultPermissions { 221 public: 222 /* 223 * Expect a rename and respond with the given error. Don't both to 224 * validate arguments; the tests in rename.cc do that. 225 */ 226 void expect_rename(int error) 227 { 228 EXPECT_CALL(*m_mock, process( 229 ResultOf([=](auto in) { 230 return (in.header.opcode == FUSE_RENAME); 231 }, Eq(true)), 232 _) 233 ).WillOnce(Invoke(ReturnErrno(error))); 234 } 235 }; 236 237 class Setextattr: public DefaultPermissions { 238 public: 239 void expect_setxattr(int error) 240 { 241 EXPECT_CALL(*m_mock, process( 242 ResultOf([=](auto in) { 243 return (in.header.opcode == FUSE_SETXATTR); 244 }, Eq(true)), 245 _) 246 ).WillOnce(Invoke(ReturnErrno(error))); 247 } 248 }; 249 250 /* Return a group to which this user does not belong */ 251 static gid_t excluded_group() 252 { 253 int i, ngroups = 64; 254 gid_t newgid, groups[ngroups]; 255 256 getgrouplist(getlogin(), getegid(), groups, &ngroups); 257 for (newgid = 0; ; newgid++) { 258 bool belongs = false; 259 260 for (i = 0; i < ngroups; i++) { 261 if (groups[i] == newgid) 262 belongs = true; 263 } 264 if (!belongs) 265 break; 266 } 267 /* newgid is now a group to which the current user does not belong */ 268 return newgid; 269 } 270 271 TEST_F(Access, eacces) 272 { 273 const char FULLPATH[] = "mountpoint/some_file.txt"; 274 const char RELPATH[] = "some_file.txt"; 275 uint64_t ino = 42; 276 mode_t access_mode = X_OK; 277 278 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 279 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 280 281 ASSERT_NE(0, access(FULLPATH, access_mode)); 282 ASSERT_EQ(EACCES, errno); 283 } 284 285 TEST_F(Access, eacces_no_cached_attrs) 286 { 287 const char FULLPATH[] = "mountpoint/some_file.txt"; 288 const char RELPATH[] = "some_file.txt"; 289 uint64_t ino = 42; 290 mode_t access_mode = X_OK; 291 292 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1); 293 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0); 294 expect_getattr(ino, S_IFREG | 0644, 0, 1); 295 /* 296 * Once default_permissions is properly implemented, there might be 297 * another FUSE_GETATTR or something in here. But there should not be 298 * a FUSE_ACCESS 299 */ 300 301 ASSERT_NE(0, access(FULLPATH, access_mode)); 302 ASSERT_EQ(EACCES, errno); 303 } 304 305 TEST_F(Access, ok) 306 { 307 const char FULLPATH[] = "mountpoint/some_file.txt"; 308 const char RELPATH[] = "some_file.txt"; 309 uint64_t ino = 42; 310 mode_t access_mode = R_OK; 311 312 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 313 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 314 /* 315 * Once default_permissions is properly implemented, there might be 316 * another FUSE_GETATTR or something in here. 317 */ 318 319 ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno); 320 } 321 322 /* Unprivileged users may chown a file to their own uid */ 323 TEST_F(Chown, chown_to_self) 324 { 325 const char FULLPATH[] = "mountpoint/some_file.txt"; 326 const char RELPATH[] = "some_file.txt"; 327 const uint64_t ino = 42; 328 const mode_t mode = 0755; 329 uid_t uid; 330 331 uid = geteuid(); 332 333 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 334 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid); 335 /* The OS may optimize chown by omitting the redundant setattr */ 336 EXPECT_CALL(*m_mock, process( 337 ResultOf([](auto in) { 338 return (in.header.opcode == FUSE_SETATTR); 339 }, Eq(true)), 340 _) 341 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 342 SET_OUT_HEADER_LEN(out, attr); 343 out.body.attr.attr.mode = S_IFREG | mode; 344 out.body.attr.attr.uid = uid; 345 }))); 346 347 EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 348 } 349 350 /* 351 * A successful chown by a non-privileged non-owner should clear a file's SUID 352 * bit 353 */ 354 TEST_F(Chown, clear_suid) 355 { 356 const char FULLPATH[] = "mountpoint/some_file.txt"; 357 const char RELPATH[] = "some_file.txt"; 358 uint64_t ino = 42; 359 const mode_t oldmode = 06755; 360 const mode_t newmode = 0755; 361 uid_t uid = geteuid(); 362 uint32_t valid = FATTR_UID | FATTR_MODE; 363 364 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 365 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid); 366 EXPECT_CALL(*m_mock, process( 367 ResultOf([=](auto in) { 368 return (in.header.opcode == FUSE_SETATTR && 369 in.header.nodeid == ino && 370 in.body.setattr.valid == valid && 371 in.body.setattr.mode == newmode); 372 }, Eq(true)), 373 _) 374 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 375 SET_OUT_HEADER_LEN(out, attr); 376 out.body.attr.attr.ino = ino; // Must match nodeid 377 out.body.attr.attr.mode = S_IFREG | newmode; 378 out.body.attr.attr_valid = UINT64_MAX; 379 }))); 380 381 EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno); 382 } 383 384 385 /* Only root may change a file's owner */ 386 TEST_F(Chown, eperm) 387 { 388 const char FULLPATH[] = "mountpoint/some_file.txt"; 389 const char RELPATH[] = "some_file.txt"; 390 const uint64_t ino = 42; 391 const mode_t mode = 0755; 392 393 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid()); 394 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid()); 395 EXPECT_CALL(*m_mock, process( 396 ResultOf([](auto in) { 397 return (in.header.opcode == FUSE_SETATTR); 398 }, Eq(true)), 399 _) 400 ).Times(0); 401 402 EXPECT_NE(0, chown(FULLPATH, 0, -1)); 403 EXPECT_EQ(EPERM, errno); 404 } 405 406 /* 407 * A successful chgrp by a non-privileged non-owner should clear a file's SUID 408 * bit 409 */ 410 TEST_F(Chgrp, clear_suid) 411 { 412 const char FULLPATH[] = "mountpoint/some_file.txt"; 413 const char RELPATH[] = "some_file.txt"; 414 uint64_t ino = 42; 415 const mode_t oldmode = 06755; 416 const mode_t newmode = 0755; 417 uid_t uid = geteuid(); 418 gid_t gid = getegid(); 419 uint32_t valid = FATTR_GID | FATTR_MODE; 420 421 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid); 422 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 423 EXPECT_CALL(*m_mock, process( 424 ResultOf([=](auto in) { 425 return (in.header.opcode == FUSE_SETATTR && 426 in.header.nodeid == ino && 427 in.body.setattr.valid == valid && 428 in.body.setattr.mode == newmode); 429 }, Eq(true)), 430 _) 431 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 432 SET_OUT_HEADER_LEN(out, attr); 433 out.body.attr.attr.ino = ino; // Must match nodeid 434 out.body.attr.attr.mode = S_IFREG | newmode; 435 out.body.attr.attr_valid = UINT64_MAX; 436 }))); 437 438 EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno); 439 } 440 441 /* non-root users may only chgrp a file to a group they belong to */ 442 TEST_F(Chgrp, eperm) 443 { 444 const char FULLPATH[] = "mountpoint/some_file.txt"; 445 const char RELPATH[] = "some_file.txt"; 446 const uint64_t ino = 42; 447 const mode_t mode = 0755; 448 uid_t uid; 449 gid_t gid, newgid; 450 451 uid = geteuid(); 452 gid = getegid(); 453 newgid = excluded_group(); 454 455 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 456 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 457 EXPECT_CALL(*m_mock, process( 458 ResultOf([](auto in) { 459 return (in.header.opcode == FUSE_SETATTR); 460 }, Eq(true)), 461 _) 462 ).Times(0); 463 464 EXPECT_NE(0, chown(FULLPATH, -1, newgid)); 465 EXPECT_EQ(EPERM, errno); 466 } 467 468 TEST_F(Chgrp, ok) 469 { 470 const char FULLPATH[] = "mountpoint/some_file.txt"; 471 const char RELPATH[] = "some_file.txt"; 472 const uint64_t ino = 42; 473 const mode_t mode = 0755; 474 uid_t uid; 475 gid_t gid, newgid; 476 477 uid = geteuid(); 478 gid = 0; 479 newgid = getegid(); 480 481 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid); 482 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid); 483 /* The OS may optimize chgrp by omitting the redundant setattr */ 484 EXPECT_CALL(*m_mock, process( 485 ResultOf([](auto in) { 486 return (in.header.opcode == FUSE_SETATTR && 487 in.header.nodeid == ino); 488 }, Eq(true)), 489 _) 490 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){ 491 SET_OUT_HEADER_LEN(out, attr); 492 out.body.attr.attr.mode = S_IFREG | mode; 493 out.body.attr.attr.uid = uid; 494 out.body.attr.attr.gid = newgid; 495 }))); 496 497 EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno); 498 } 499 500 /* A write by a non-owner should clear a file's SGID bit */ 501 TEST_F(CopyFileRange, clear_sgid) 502 { 503 const char FULLPATH_IN[] = "mountpoint/in.txt"; 504 const char RELPATH_IN[] = "in.txt"; 505 const char FULLPATH_OUT[] = "mountpoint/out.txt"; 506 const char RELPATH_OUT[] = "out.txt"; 507 struct stat sb; 508 uint64_t ino_in = 42; 509 uint64_t ino_out = 43; 510 mode_t oldmode = 02777; 511 mode_t newmode = 0777; 512 off_t fsize = 16; 513 off_t off_in = 0; 514 off_t off_out = 8; 515 off_t len = 8; 516 int fd_in, fd_out; 517 518 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 519 FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 520 UINT64_MAX, 0, 0); 521 expect_open(ino_in, 0, 1); 522 FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 523 1, UINT64_MAX, 0, 0); 524 expect_open(ino_out, 0, 1); 525 expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 526 expect_chmod(ino_out, newmode, fsize); 527 528 fd_in = open(FULLPATH_IN, O_RDONLY); 529 ASSERT_LE(0, fd_in) << strerror(errno); 530 fd_out = open(FULLPATH_OUT, O_WRONLY); 531 ASSERT_LE(0, fd_out) << strerror(errno); 532 ASSERT_EQ(len, 533 copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 534 << strerror(errno); 535 ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 536 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 537 ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 538 EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 539 540 leak(fd_in); 541 leak(fd_out); 542 } 543 544 /* A write by a non-owner should clear a file's SUID bit */ 545 TEST_F(CopyFileRange, clear_suid) 546 { 547 const char FULLPATH_IN[] = "mountpoint/in.txt"; 548 const char RELPATH_IN[] = "in.txt"; 549 const char FULLPATH_OUT[] = "mountpoint/out.txt"; 550 const char RELPATH_OUT[] = "out.txt"; 551 struct stat sb; 552 uint64_t ino_in = 42; 553 uint64_t ino_out = 43; 554 mode_t oldmode = 04777; 555 mode_t newmode = 0777; 556 off_t fsize = 16; 557 off_t off_in = 0; 558 off_t off_out = 8; 559 off_t len = 8; 560 int fd_in, fd_out; 561 562 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 563 FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1, 564 UINT64_MAX, 0, 0); 565 expect_open(ino_in, 0, 1); 566 FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize, 567 1, UINT64_MAX, 0, 0); 568 expect_open(ino_out, 0, 1); 569 expect_copy_file_range(ino_in, off_in, ino_out, off_out, len); 570 expect_chmod(ino_out, newmode, fsize); 571 572 fd_in = open(FULLPATH_IN, O_RDONLY); 573 ASSERT_LE(0, fd_in) << strerror(errno); 574 fd_out = open(FULLPATH_OUT, O_WRONLY); 575 ASSERT_LE(0, fd_out) << strerror(errno); 576 ASSERT_EQ(len, 577 copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0)) 578 << strerror(errno); 579 ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno); 580 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 581 ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno); 582 EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 583 584 leak(fd_in); 585 leak(fd_out); 586 } 587 588 TEST_F(Create, ok) 589 { 590 const char FULLPATH[] = "mountpoint/some_file.txt"; 591 const char RELPATH[] = "some_file.txt"; 592 uint64_t ino = 42; 593 int fd; 594 595 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 596 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 597 .WillOnce(Invoke(ReturnErrno(ENOENT))); 598 expect_create(RELPATH, ino); 599 600 fd = open(FULLPATH, O_CREAT | O_EXCL, 0644); 601 ASSERT_LE(0, fd) << strerror(errno); 602 leak(fd); 603 } 604 605 TEST_F(Create, eacces) 606 { 607 const char FULLPATH[] = "mountpoint/some_file.txt"; 608 const char RELPATH[] = "some_file.txt"; 609 610 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 611 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 612 .WillOnce(Invoke(ReturnErrno(ENOENT))); 613 614 ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644)); 615 EXPECT_EQ(EACCES, errno); 616 } 617 618 TEST_F(Deleteextattr, eacces) 619 { 620 const char FULLPATH[] = "mountpoint/some_file.txt"; 621 const char RELPATH[] = "some_file.txt"; 622 uint64_t ino = 42; 623 int ns = EXTATTR_NAMESPACE_USER; 624 625 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 626 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 627 628 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 629 ASSERT_EQ(EACCES, errno); 630 } 631 632 TEST_F(Deleteextattr, ok) 633 { 634 const char FULLPATH[] = "mountpoint/some_file.txt"; 635 const char RELPATH[] = "some_file.txt"; 636 uint64_t ino = 42; 637 int ns = EXTATTR_NAMESPACE_USER; 638 639 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 640 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 641 expect_removexattr(); 642 643 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 644 << strerror(errno); 645 } 646 647 /* Delete system attributes requires superuser privilege */ 648 TEST_F(Deleteextattr, system) 649 { 650 const char FULLPATH[] = "mountpoint/some_file.txt"; 651 const char RELPATH[] = "some_file.txt"; 652 uint64_t ino = 42; 653 int ns = EXTATTR_NAMESPACE_SYSTEM; 654 655 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 656 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 657 658 ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo")); 659 ASSERT_EQ(EPERM, errno); 660 } 661 662 /* Anybody with write permission can set both timestamps to UTIME_NOW */ 663 TEST_F(Utimensat, utime_now) 664 { 665 const char FULLPATH[] = "mountpoint/some_file.txt"; 666 const char RELPATH[] = "some_file.txt"; 667 const uint64_t ino = 42; 668 /* Write permissions for everybody */ 669 const mode_t mode = 0666; 670 uid_t owner = 0; 671 const timespec times[2] = { 672 {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 673 {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 674 }; 675 676 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 677 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 678 EXPECT_CALL(*m_mock, process( 679 ResultOf([](auto in) { 680 return (in.header.opcode == FUSE_SETATTR && 681 in.header.nodeid == ino && 682 in.body.setattr.valid & FATTR_ATIME && 683 in.body.setattr.valid & FATTR_MTIME); 684 }, Eq(true)), 685 _) 686 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 687 SET_OUT_HEADER_LEN(out, attr); 688 out.body.attr.attr.mode = S_IFREG | mode; 689 }))); 690 691 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 692 << strerror(errno); 693 } 694 695 /* Anybody can set both timestamps to UTIME_OMIT */ 696 TEST_F(Utimensat, utime_omit) 697 { 698 const char FULLPATH[] = "mountpoint/some_file.txt"; 699 const char RELPATH[] = "some_file.txt"; 700 const uint64_t ino = 42; 701 /* Write permissions for no one */ 702 const mode_t mode = 0444; 703 uid_t owner = 0; 704 const timespec times[2] = { 705 {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 706 {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, 707 }; 708 709 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 710 expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner); 711 712 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, ×[0], 0)) 713 << strerror(errno); 714 } 715 716 /* Deleting user attributes merely requires WRITE privilege */ 717 TEST_F(Deleteextattr, user) 718 { 719 const char FULLPATH[] = "mountpoint/some_file.txt"; 720 const char RELPATH[] = "some_file.txt"; 721 uint64_t ino = 42; 722 int ns = EXTATTR_NAMESPACE_USER; 723 724 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 725 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 726 expect_removexattr(); 727 728 ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo")) 729 << strerror(errno); 730 } 731 732 TEST_F(Getextattr, eacces) 733 { 734 const char FULLPATH[] = "mountpoint/some_file.txt"; 735 const char RELPATH[] = "some_file.txt"; 736 uint64_t ino = 42; 737 char data[80]; 738 int ns = EXTATTR_NAMESPACE_USER; 739 740 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 741 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 742 743 ASSERT_EQ(-1, 744 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 745 ASSERT_EQ(EACCES, errno); 746 } 747 748 TEST_F(Getextattr, ok) 749 { 750 const char FULLPATH[] = "mountpoint/some_file.txt"; 751 const char RELPATH[] = "some_file.txt"; 752 uint64_t ino = 42; 753 char data[80]; 754 const char value[] = "whatever"; 755 ssize_t value_len = strlen(value) + 1; 756 int ns = EXTATTR_NAMESPACE_USER; 757 ssize_t r; 758 759 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 760 /* Getting user attributes only requires read access */ 761 expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0); 762 expect_getxattr( 763 ReturnImmediate([&](auto in __unused, auto& out) { 764 memcpy((void*)out.body.bytes, value, value_len); 765 out.header.len = sizeof(out.header) + value_len; 766 }) 767 ); 768 769 r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)); 770 ASSERT_EQ(value_len, r) << strerror(errno); 771 EXPECT_STREQ(value, data); 772 } 773 774 /* Getting system attributes requires superuser privileges */ 775 TEST_F(Getextattr, system) 776 { 777 const char FULLPATH[] = "mountpoint/some_file.txt"; 778 const char RELPATH[] = "some_file.txt"; 779 uint64_t ino = 42; 780 char data[80]; 781 int ns = EXTATTR_NAMESPACE_SYSTEM; 782 783 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 784 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 785 786 ASSERT_EQ(-1, 787 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data))); 788 ASSERT_EQ(EPERM, errno); 789 } 790 791 TEST_F(Listextattr, eacces) 792 { 793 const char FULLPATH[] = "mountpoint/some_file.txt"; 794 const char RELPATH[] = "some_file.txt"; 795 uint64_t ino = 42; 796 int ns = EXTATTR_NAMESPACE_USER; 797 798 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 799 expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0); 800 801 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 802 ASSERT_EQ(EACCES, errno); 803 } 804 805 TEST_F(Listextattr, ok) 806 { 807 const char FULLPATH[] = "mountpoint/some_file.txt"; 808 const char RELPATH[] = "some_file.txt"; 809 uint64_t ino = 42; 810 int ns = EXTATTR_NAMESPACE_USER; 811 812 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 813 /* Listing user extended attributes merely requires read access */ 814 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 815 expect_listxattr(); 816 817 ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0)) 818 << strerror(errno); 819 } 820 821 /* Listing system xattrs requires superuser privileges */ 822 TEST_F(Listextattr, system) 823 { 824 const char FULLPATH[] = "mountpoint/some_file.txt"; 825 const char RELPATH[] = "some_file.txt"; 826 uint64_t ino = 42; 827 int ns = EXTATTR_NAMESPACE_SYSTEM; 828 829 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 830 /* Listing user extended attributes merely requires read access */ 831 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 832 833 ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0)); 834 ASSERT_EQ(EPERM, errno); 835 } 836 837 /* A write by a non-owner should clear a file's SGID bit */ 838 TEST_F(Fspacectl, clear_sgid) 839 { 840 const char FULLPATH[] = "mountpoint/file.txt"; 841 const char RELPATH[] = "file.txt"; 842 struct stat sb; 843 struct spacectl_range rqsr; 844 uint64_t ino = 42; 845 mode_t oldmode = 02777; 846 mode_t newmode = 0777; 847 off_t fsize = 16; 848 off_t off = 8; 849 off_t len = 8; 850 int fd; 851 852 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 853 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 854 1, UINT64_MAX, 0, 0); 855 expect_open(ino, 0, 1); 856 expect_fallocate(ino, off, len, 857 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 858 expect_chmod(ino, newmode, fsize); 859 860 fd = open(FULLPATH, O_WRONLY); 861 ASSERT_LE(0, fd) << strerror(errno); 862 rqsr.r_len = len; 863 rqsr.r_offset = off; 864 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 865 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 866 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 867 868 leak(fd); 869 } 870 871 /* A write by a non-owner should clear a file's SUID bit */ 872 TEST_F(Fspacectl, clear_suid) 873 { 874 const char FULLPATH[] = "mountpoint/file.txt"; 875 const char RELPATH[] = "file.txt"; 876 struct stat sb; 877 struct spacectl_range rqsr; 878 uint64_t ino = 42; 879 mode_t oldmode = 04777; 880 mode_t newmode = 0777; 881 off_t fsize = 16; 882 off_t off = 8; 883 off_t len = 8; 884 int fd; 885 886 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 887 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 888 1, UINT64_MAX, 0, 0); 889 expect_open(ino, 0, 1); 890 expect_fallocate(ino, off, len, 891 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 892 expect_chmod(ino, newmode, fsize); 893 894 fd = open(FULLPATH, O_WRONLY); 895 ASSERT_LE(0, fd) << strerror(errno); 896 rqsr.r_len = len; 897 rqsr.r_offset = off; 898 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 899 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 900 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 901 902 leak(fd); 903 } 904 905 /* 906 * fspacectl() of a file without writable permissions should succeed as 907 * long as the file descriptor is writable. This is important when combined 908 * with O_CREAT 909 */ 910 TEST_F(Fspacectl, posix_fallocate_of_newly_created_file) 911 { 912 const char FULLPATH[] = "mountpoint/some_file.txt"; 913 const char RELPATH[] = "some_file.txt"; 914 struct spacectl_range rqsr; 915 const uint64_t ino = 42; 916 off_t off = 8; 917 off_t len = 8; 918 int fd; 919 920 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 921 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 922 .WillOnce(Invoke(ReturnErrno(ENOENT))); 923 expect_create(RELPATH, ino); 924 expect_fallocate(ino, off, len, 925 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 926 927 fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 928 ASSERT_LE(0, fd) << strerror(errno); 929 rqsr.r_len = len; 930 rqsr.r_offset = off; 931 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 932 leak(fd); 933 } 934 935 /* A component of the search path lacks execute permissions */ 936 TEST_F(Lookup, eacces) 937 { 938 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 939 const char RELDIRPATH[] = "some_dir"; 940 uint64_t dir_ino = 42; 941 942 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 943 expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0); 944 945 EXPECT_EQ(-1, access(FULLPATH, F_OK)); 946 EXPECT_EQ(EACCES, errno); 947 } 948 949 TEST_F(Open, eacces) 950 { 951 const char FULLPATH[] = "mountpoint/some_file.txt"; 952 const char RELPATH[] = "some_file.txt"; 953 uint64_t ino = 42; 954 955 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 956 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 957 958 EXPECT_EQ(-1, open(FULLPATH, O_RDWR)); 959 EXPECT_EQ(EACCES, errno); 960 } 961 962 TEST_F(Open, ok) 963 { 964 const char FULLPATH[] = "mountpoint/some_file.txt"; 965 const char RELPATH[] = "some_file.txt"; 966 uint64_t ino = 42; 967 int fd; 968 969 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 970 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX); 971 expect_open(ino, 0, 1); 972 973 fd = open(FULLPATH, O_RDONLY); 974 ASSERT_LE(0, fd) << strerror(errno); 975 leak(fd); 976 } 977 978 /* A write by a non-owner should clear a file's SGID bit */ 979 TEST_F(PosixFallocate, clear_sgid) 980 { 981 const char FULLPATH[] = "mountpoint/file.txt"; 982 const char RELPATH[] = "file.txt"; 983 struct stat sb; 984 uint64_t ino = 42; 985 mode_t oldmode = 02777; 986 mode_t newmode = 0777; 987 off_t fsize = 16; 988 off_t off = 8; 989 off_t len = 8; 990 int fd; 991 992 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 993 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 994 1, UINT64_MAX, 0, 0); 995 expect_open(ino, 0, 1); 996 expect_fallocate(ino, off, len, 0, 0); 997 expect_chmod(ino, newmode, fsize); 998 999 fd = open(FULLPATH, O_WRONLY); 1000 ASSERT_LE(0, fd) << strerror(errno); 1001 EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 1002 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1003 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1004 1005 leak(fd); 1006 } 1007 1008 /* A write by a non-owner should clear a file's SUID bit */ 1009 TEST_F(PosixFallocate, clear_suid) 1010 { 1011 const char FULLPATH[] = "mountpoint/file.txt"; 1012 const char RELPATH[] = "file.txt"; 1013 struct stat sb; 1014 uint64_t ino = 42; 1015 mode_t oldmode = 04777; 1016 mode_t newmode = 0777; 1017 off_t fsize = 16; 1018 off_t off = 8; 1019 off_t len = 8; 1020 int fd; 1021 1022 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1023 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize, 1024 1, UINT64_MAX, 0, 0); 1025 expect_open(ino, 0, 1); 1026 expect_fallocate(ino, off, len, 0, 0); 1027 expect_chmod(ino, newmode, fsize); 1028 1029 fd = open(FULLPATH, O_WRONLY); 1030 ASSERT_LE(0, fd) << strerror(errno); 1031 EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 1032 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1033 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1034 1035 leak(fd); 1036 } 1037 1038 /* 1039 * posix_fallocate() of a file without writable permissions should succeed as 1040 * long as the file descriptor is writable. This is important when combined 1041 * with O_CREAT 1042 */ 1043 TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file) 1044 { 1045 const char FULLPATH[] = "mountpoint/some_file.txt"; 1046 const char RELPATH[] = "some_file.txt"; 1047 const uint64_t ino = 42; 1048 off_t off = 8; 1049 off_t len = 8; 1050 int fd; 1051 1052 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1053 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1054 .WillOnce(Invoke(ReturnErrno(ENOENT))); 1055 expect_create(RELPATH, ino); 1056 expect_fallocate(ino, off, len, 0, 0); 1057 1058 fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 1059 ASSERT_LE(0, fd) << strerror(errno); 1060 EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno); 1061 leak(fd); 1062 } 1063 1064 TEST_F(Rename, eacces_on_srcdir) 1065 { 1066 const char FULLDST[] = "mountpoint/d/dst"; 1067 const char RELDST[] = "d/dst"; 1068 const char FULLSRC[] = "mountpoint/src"; 1069 const char RELSRC[] = "src"; 1070 uint64_t ino = 42; 1071 1072 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0); 1073 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1074 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1075 .Times(AnyNumber()) 1076 .WillRepeatedly(Invoke(ReturnErrno(ENOENT))); 1077 1078 ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1079 ASSERT_EQ(EACCES, errno); 1080 } 1081 1082 TEST_F(Rename, eacces_on_dstdir_for_creating) 1083 { 1084 const char FULLDST[] = "mountpoint/d/dst"; 1085 const char RELDSTDIR[] = "d"; 1086 const char RELDST[] = "dst"; 1087 const char FULLSRC[] = "mountpoint/src"; 1088 const char RELSRC[] = "src"; 1089 uint64_t src_ino = 42; 1090 uint64_t dstdir_ino = 43; 1091 1092 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1093 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1094 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 1095 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 1096 1097 ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1098 ASSERT_EQ(EACCES, errno); 1099 } 1100 1101 TEST_F(Rename, eacces_on_dstdir_for_removing) 1102 { 1103 const char FULLDST[] = "mountpoint/d/dst"; 1104 const char RELDSTDIR[] = "d"; 1105 const char RELDST[] = "dst"; 1106 const char FULLSRC[] = "mountpoint/src"; 1107 const char RELSRC[] = "src"; 1108 uint64_t src_ino = 42; 1109 uint64_t dstdir_ino = 43; 1110 1111 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1112 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1113 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX); 1114 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 1115 1116 ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1117 ASSERT_EQ(EACCES, errno); 1118 } 1119 1120 TEST_F(Rename, eperm_on_sticky_srcdir) 1121 { 1122 const char FULLDST[] = "mountpoint/d/dst"; 1123 const char FULLSRC[] = "mountpoint/src"; 1124 const char RELSRC[] = "src"; 1125 uint64_t ino = 42; 1126 1127 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1128 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1129 1130 ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1131 ASSERT_EQ(EPERM, errno); 1132 } 1133 1134 /* 1135 * A user cannot move out a subdirectory that he does not own, because that 1136 * would require changing the subdirectory's ".." dirent 1137 */ 1138 TEST_F(Rename, eperm_for_subdirectory) 1139 { 1140 const char FULLDST[] = "mountpoint/d/dst"; 1141 const char FULLSRC[] = "mountpoint/src"; 1142 const char RELDSTDIR[] = "d"; 1143 const char RELDST[] = "dst"; 1144 const char RELSRC[] = "src"; 1145 uint64_t ino = 42; 1146 uint64_t dstdir_ino = 43; 1147 1148 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1149 expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 1150 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0); 1151 EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT))); 1152 1153 ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1154 ASSERT_EQ(EACCES, errno); 1155 } 1156 1157 /* 1158 * A user _can_ rename a subdirectory to which he lacks write permissions, if 1159 * it will keep the same parent 1160 */ 1161 TEST_F(Rename, subdirectory_to_same_dir) 1162 { 1163 const char FULLDST[] = "mountpoint/dst"; 1164 const char FULLSRC[] = "mountpoint/src"; 1165 const char RELDST[] = "dst"; 1166 const char RELSRC[] = "src"; 1167 uint64_t ino = 42; 1168 1169 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1170 expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0); 1171 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1172 .WillOnce(Invoke(ReturnErrno(ENOENT))); 1173 expect_rename(0); 1174 1175 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1176 } 1177 1178 TEST_F(Rename, eperm_on_sticky_dstdir) 1179 { 1180 const char FULLDST[] = "mountpoint/d/dst"; 1181 const char RELDSTDIR[] = "d"; 1182 const char RELDST[] = "dst"; 1183 const char FULLSRC[] = "mountpoint/src"; 1184 const char RELSRC[] = "src"; 1185 uint64_t src_ino = 42; 1186 uint64_t dstdir_ino = 43; 1187 uint64_t dst_ino = 44; 1188 1189 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0); 1190 expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX); 1191 expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX); 1192 EXPECT_LOOKUP(dstdir_ino, RELDST) 1193 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 1194 SET_OUT_HEADER_LEN(out, entry); 1195 out.body.entry.attr.mode = S_IFREG | 0644; 1196 out.body.entry.nodeid = dst_ino; 1197 out.body.entry.attr_valid = UINT64_MAX; 1198 out.body.entry.entry_valid = UINT64_MAX; 1199 out.body.entry.attr.uid = 0; 1200 }))); 1201 1202 ASSERT_EQ(-1, rename(FULLSRC, FULLDST)); 1203 ASSERT_EQ(EPERM, errno); 1204 } 1205 1206 /* Successfully rename a file, overwriting the destination */ 1207 TEST_F(Rename, ok) 1208 { 1209 const char FULLDST[] = "mountpoint/dst"; 1210 const char RELDST[] = "dst"; 1211 const char FULLSRC[] = "mountpoint/src"; 1212 const char RELSRC[] = "src"; 1213 // The inode of the already-existing destination file 1214 uint64_t dst_ino = 2; 1215 uint64_t ino = 42; 1216 1217 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid()); 1218 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX); 1219 expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX); 1220 expect_rename(0); 1221 1222 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1223 } 1224 1225 TEST_F(Rename, ok_to_remove_src_because_of_stickiness) 1226 { 1227 const char FULLDST[] = "mountpoint/dst"; 1228 const char RELDST[] = "dst"; 1229 const char FULLSRC[] = "mountpoint/src"; 1230 const char RELSRC[] = "src"; 1231 uint64_t ino = 42; 1232 1233 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0); 1234 expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1235 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 1236 .WillOnce(Invoke(ReturnErrno(ENOENT))); 1237 expect_rename(0); 1238 1239 ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno); 1240 } 1241 1242 TEST_F(Setattr, ok) 1243 { 1244 const char FULLPATH[] = "mountpoint/some_file.txt"; 1245 const char RELPATH[] = "some_file.txt"; 1246 const uint64_t ino = 42; 1247 const mode_t oldmode = 0755; 1248 const mode_t newmode = 0644; 1249 1250 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1251 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1252 EXPECT_CALL(*m_mock, process( 1253 ResultOf([](auto in) { 1254 return (in.header.opcode == FUSE_SETATTR && 1255 in.header.nodeid == ino && 1256 in.body.setattr.mode == newmode); 1257 }, Eq(true)), 1258 _) 1259 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 1260 SET_OUT_HEADER_LEN(out, attr); 1261 out.body.attr.attr.mode = S_IFREG | newmode; 1262 }))); 1263 1264 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 1265 } 1266 1267 TEST_F(Setattr, eacces) 1268 { 1269 const char FULLPATH[] = "mountpoint/some_file.txt"; 1270 const char RELPATH[] = "some_file.txt"; 1271 const uint64_t ino = 42; 1272 const mode_t oldmode = 0755; 1273 const mode_t newmode = 0644; 1274 1275 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1276 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0); 1277 EXPECT_CALL(*m_mock, process( 1278 ResultOf([](auto in) { 1279 return (in.header.opcode == FUSE_SETATTR); 1280 }, Eq(true)), 1281 _) 1282 ).Times(0); 1283 1284 EXPECT_NE(0, chmod(FULLPATH, newmode)); 1285 EXPECT_EQ(EPERM, errno); 1286 } 1287 1288 /* 1289 * ftruncate() of a file without writable permissions should succeed as long as 1290 * the file descriptor is writable. This is important when combined with 1291 * O_CREAT 1292 */ 1293 TEST_F(Setattr, ftruncate_of_newly_created_file) 1294 { 1295 const char FULLPATH[] = "mountpoint/some_file.txt"; 1296 const char RELPATH[] = "some_file.txt"; 1297 const uint64_t ino = 42; 1298 const mode_t mode = 0000; 1299 int fd; 1300 1301 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1302 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1303 .WillOnce(Invoke(ReturnErrno(ENOENT))); 1304 expect_create(RELPATH, ino); 1305 EXPECT_CALL(*m_mock, process( 1306 ResultOf([](auto in) { 1307 return (in.header.opcode == FUSE_SETATTR && 1308 in.header.nodeid == ino && 1309 (in.body.setattr.valid & FATTR_SIZE)); 1310 }, Eq(true)), 1311 _) 1312 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 1313 SET_OUT_HEADER_LEN(out, attr); 1314 out.body.attr.attr.ino = ino; 1315 out.body.attr.attr.mode = S_IFREG | mode; 1316 out.body.attr.attr_valid = UINT64_MAX; 1317 }))); 1318 1319 fd = open(FULLPATH, O_CREAT | O_RDWR, 0); 1320 ASSERT_LE(0, fd) << strerror(errno); 1321 ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno); 1322 leak(fd); 1323 } 1324 1325 /* 1326 * Setting the sgid bit should fail for an unprivileged user who doesn't belong 1327 * to the file's group 1328 */ 1329 TEST_F(Setattr, sgid_by_non_group_member) 1330 { 1331 const char FULLPATH[] = "mountpoint/some_file.txt"; 1332 const char RELPATH[] = "some_file.txt"; 1333 const uint64_t ino = 42; 1334 const mode_t oldmode = 0755; 1335 const mode_t newmode = 02755; 1336 uid_t uid = geteuid(); 1337 gid_t gid = excluded_group(); 1338 1339 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1340 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid); 1341 EXPECT_CALL(*m_mock, process( 1342 ResultOf([](auto in) { 1343 return (in.header.opcode == FUSE_SETATTR); 1344 }, Eq(true)), 1345 _) 1346 ).Times(0); 1347 1348 EXPECT_NE(0, chmod(FULLPATH, newmode)); 1349 EXPECT_EQ(EPERM, errno); 1350 } 1351 1352 /* Only the superuser may set the sticky bit on a non-directory */ 1353 TEST_F(Setattr, sticky_regular_file) 1354 { 1355 const char FULLPATH[] = "mountpoint/some_file.txt"; 1356 const char RELPATH[] = "some_file.txt"; 1357 const uint64_t ino = 42; 1358 const mode_t oldmode = 0644; 1359 const mode_t newmode = 01644; 1360 1361 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1362 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid()); 1363 EXPECT_CALL(*m_mock, process( 1364 ResultOf([](auto in) { 1365 return (in.header.opcode == FUSE_SETATTR); 1366 }, Eq(true)), 1367 _) 1368 ).Times(0); 1369 1370 EXPECT_NE(0, chmod(FULLPATH, newmode)); 1371 EXPECT_EQ(EFTYPE, errno); 1372 } 1373 1374 TEST_F(Setextattr, ok) 1375 { 1376 const char FULLPATH[] = "mountpoint/some_file.txt"; 1377 const char RELPATH[] = "some_file.txt"; 1378 uint64_t ino = 42; 1379 const char value[] = "whatever"; 1380 ssize_t value_len = strlen(value) + 1; 1381 int ns = EXTATTR_NAMESPACE_USER; 1382 ssize_t r; 1383 1384 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1385 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1386 expect_setxattr(0); 1387 1388 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 1389 value_len); 1390 ASSERT_EQ(value_len, r) << strerror(errno); 1391 } 1392 1393 TEST_F(Setextattr, eacces) 1394 { 1395 const char FULLPATH[] = "mountpoint/some_file.txt"; 1396 const char RELPATH[] = "some_file.txt"; 1397 uint64_t ino = 42; 1398 const char value[] = "whatever"; 1399 ssize_t value_len = strlen(value) + 1; 1400 int ns = EXTATTR_NAMESPACE_USER; 1401 1402 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1403 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1404 1405 ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 1406 value_len)); 1407 ASSERT_EQ(EACCES, errno); 1408 } 1409 1410 // Setting system attributes requires superuser privileges 1411 TEST_F(Setextattr, system) 1412 { 1413 const char FULLPATH[] = "mountpoint/some_file.txt"; 1414 const char RELPATH[] = "some_file.txt"; 1415 uint64_t ino = 42; 1416 const char value[] = "whatever"; 1417 ssize_t value_len = strlen(value) + 1; 1418 int ns = EXTATTR_NAMESPACE_SYSTEM; 1419 1420 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1421 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid()); 1422 1423 ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 1424 value_len)); 1425 ASSERT_EQ(EPERM, errno); 1426 } 1427 1428 // Setting user attributes merely requires write privileges 1429 TEST_F(Setextattr, user) 1430 { 1431 const char FULLPATH[] = "mountpoint/some_file.txt"; 1432 const char RELPATH[] = "some_file.txt"; 1433 uint64_t ino = 42; 1434 const char value[] = "whatever"; 1435 ssize_t value_len = strlen(value) + 1; 1436 int ns = EXTATTR_NAMESPACE_USER; 1437 ssize_t r; 1438 1439 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1440 expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0); 1441 expect_setxattr(0); 1442 1443 r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value, 1444 value_len); 1445 ASSERT_EQ(value_len, r) << strerror(errno); 1446 } 1447 1448 TEST_F(Unlink, ok) 1449 { 1450 const char FULLPATH[] = "mountpoint/some_file.txt"; 1451 const char RELPATH[] = "some_file.txt"; 1452 uint64_t ino = 42; 1453 sem_t sem; 1454 1455 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 1456 1457 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1); 1458 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1459 expect_unlink(FUSE_ROOT_ID, RELPATH, 0); 1460 expect_forget(ino, 1, &sem); 1461 1462 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 1463 1464 sem_wait(&sem); 1465 sem_destroy(&sem); 1466 } 1467 1468 /* 1469 * Ensure that a cached name doesn't cause unlink to bypass permission checks 1470 * in VOP_LOOKUP. 1471 * 1472 * This test should pass because lookup(9) purges the namecache entry by doing 1473 * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE. 1474 */ 1475 TEST_F(Unlink, cached_unwritable_directory) 1476 { 1477 const char FULLPATH[] = "mountpoint/some_file.txt"; 1478 const char RELPATH[] = "some_file.txt"; 1479 uint64_t ino = 42; 1480 1481 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1482 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 1483 .Times(AnyNumber()) 1484 .WillRepeatedly(Invoke( 1485 ReturnImmediate([=](auto i __unused, auto& out) { 1486 SET_OUT_HEADER_LEN(out, entry); 1487 out.body.entry.attr.mode = S_IFREG | 0644; 1488 out.body.entry.nodeid = ino; 1489 out.body.entry.entry_valid = UINT64_MAX; 1490 })) 1491 ); 1492 1493 /* Fill name cache */ 1494 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 1495 /* Despite cached name , unlink should fail */ 1496 ASSERT_EQ(-1, unlink(FULLPATH)); 1497 ASSERT_EQ(EACCES, errno); 1498 } 1499 1500 TEST_F(Unlink, unwritable_directory) 1501 { 1502 const char FULLPATH[] = "mountpoint/some_file.txt"; 1503 const char RELPATH[] = "some_file.txt"; 1504 uint64_t ino = 42; 1505 1506 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1507 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid()); 1508 1509 ASSERT_EQ(-1, unlink(FULLPATH)); 1510 ASSERT_EQ(EACCES, errno); 1511 } 1512 1513 TEST_F(Unlink, sticky_directory) 1514 { 1515 const char FULLPATH[] = "mountpoint/some_file.txt"; 1516 const char RELPATH[] = "some_file.txt"; 1517 uint64_t ino = 42; 1518 1519 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1); 1520 expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0); 1521 1522 ASSERT_EQ(-1, unlink(FULLPATH)); 1523 ASSERT_EQ(EPERM, errno); 1524 } 1525 1526 /* A write by a non-owner should clear a file's SUID bit */ 1527 TEST_F(Write, clear_suid) 1528 { 1529 const char FULLPATH[] = "mountpoint/some_file.txt"; 1530 const char RELPATH[] = "some_file.txt"; 1531 struct stat sb; 1532 uint64_t ino = 42; 1533 mode_t oldmode = 04777; 1534 mode_t newmode = 0777; 1535 char wbuf[1] = {'x'}; 1536 int fd; 1537 1538 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1539 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1540 expect_open(ino, 0, 1); 1541 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 1542 expect_chmod(ino, newmode, sizeof(wbuf)); 1543 1544 fd = open(FULLPATH, O_WRONLY); 1545 ASSERT_LE(0, fd) << strerror(errno); 1546 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1547 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1548 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1549 leak(fd); 1550 } 1551 1552 /* A write by a non-owner should clear a file's SGID bit */ 1553 TEST_F(Write, clear_sgid) 1554 { 1555 const char FULLPATH[] = "mountpoint/some_file.txt"; 1556 const char RELPATH[] = "some_file.txt"; 1557 struct stat sb; 1558 uint64_t ino = 42; 1559 mode_t oldmode = 02777; 1560 mode_t newmode = 0777; 1561 char wbuf[1] = {'x'}; 1562 int fd; 1563 1564 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1565 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1566 expect_open(ino, 0, 1); 1567 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 1568 expect_chmod(ino, newmode, sizeof(wbuf)); 1569 1570 fd = open(FULLPATH, O_WRONLY); 1571 ASSERT_LE(0, fd) << strerror(errno); 1572 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1573 ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno); 1574 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 1575 leak(fd); 1576 } 1577 1578 /* Regression test for a specific recurse-of-nonrecursive-lock panic 1579 * 1580 * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it 1581 * may panic. That happens if the FUSE_SETATTR response indicates that the 1582 * file's size has changed since the write. 1583 */ 1584 TEST_F(Write, recursion_panic_while_clearing_suid) 1585 { 1586 const char FULLPATH[] = "mountpoint/some_file.txt"; 1587 const char RELPATH[] = "some_file.txt"; 1588 uint64_t ino = 42; 1589 mode_t oldmode = 04777; 1590 mode_t newmode = 0777; 1591 char wbuf[1] = {'x'}; 1592 int fd; 1593 1594 expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1); 1595 expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX); 1596 expect_open(ino, 0, 1); 1597 expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf); 1598 /* XXX Return a smaller file size than what we just wrote! */ 1599 expect_chmod(ino, newmode, 0); 1600 1601 fd = open(FULLPATH, O_WRONLY); 1602 ASSERT_LE(0, fd) << strerror(errno); 1603 ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno); 1604 leak(fd); 1605 } 1606