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