]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/default_permissions.cc
fusefs: set FUSE_WRITE_CACHE when writing from cache
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / default_permissions.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 <unistd.h>
42 }
43
44 #include "mockfs.hh"
45 #include "utils.hh"
46
47 using namespace testing;
48
49 class DefaultPermissions: public FuseTest {
50
51 virtual void SetUp() {
52         m_default_permissions = true;
53         FuseTest::SetUp();
54         if (HasFatalFailure() || IsSkipped())
55                 return;
56
57         if (geteuid() == 0) {
58                 GTEST_SKIP() << "This test requires an unprivileged user";
59         }
60         
61         /* With -o default_permissions, FUSE_ACCESS should never be called */
62         EXPECT_CALL(*m_mock, process(
63                 ResultOf([=](auto in) {
64                         return (in.header.opcode == FUSE_ACCESS);
65                 }, Eq(true)),
66                 _)
67         ).Times(0);
68 }
69
70 public:
71 void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
72 {
73         EXPECT_CALL(*m_mock, process(
74                 ResultOf([=](auto in) {
75                         return (in.header.opcode == FUSE_SETATTR &&
76                                 in.header.nodeid == ino &&
77                                 in.body.setattr.valid == FATTR_MODE &&
78                                 in.body.setattr.mode == mode);
79                 }, Eq(true)),
80                 _)
81         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
82                 SET_OUT_HEADER_LEN(out, attr);
83                 out.body.attr.attr.ino = ino;   // Must match nodeid
84                 out.body.attr.attr.mode = S_IFREG | mode;
85                 out.body.attr.attr.size = size;
86                 out.body.attr.attr_valid = UINT64_MAX;
87         })));
88 }
89
90 void expect_create(const char *relpath, uint64_t ino)
91 {
92         EXPECT_CALL(*m_mock, process(
93                 ResultOf([=](auto in) {
94                         const char *name = (const char*)in.body.bytes +
95                                 sizeof(fuse_open_in);
96                         return (in.header.opcode == FUSE_CREATE &&
97                                 (0 == strcmp(relpath, name)));
98                 }, Eq(true)),
99                 _)
100         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
101                 SET_OUT_HEADER_LEN(out, create);
102                 out.body.create.entry.attr.mode = S_IFREG | 0644;
103                 out.body.create.entry.nodeid = ino;
104                 out.body.create.entry.entry_valid = UINT64_MAX;
105                 out.body.create.entry.attr_valid = UINT64_MAX;
106         })));
107 }
108
109 void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
110         uid_t uid = 0, gid_t gid = 0)
111 {
112         EXPECT_CALL(*m_mock, process(
113                 ResultOf([=](auto in) {
114                         return (in.header.opcode == FUSE_GETATTR &&
115                                 in.header.nodeid == ino);
116                 }, Eq(true)),
117                 _)
118         ).Times(times)
119         .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
120                 SET_OUT_HEADER_LEN(out, attr);
121                 out.body.attr.attr.ino = ino;   // Must match nodeid
122                 out.body.attr.attr.mode = mode;
123                 out.body.attr.attr.size = 0;
124                 out.body.attr.attr.uid = uid;
125                 out.body.attr.attr.uid = gid;
126                 out.body.attr.attr_valid = attr_valid;
127         })));
128 }
129
130 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
131         uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
132 {
133         FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
134 }
135
136 };
137
138 class Access: public DefaultPermissions {};
139 class Chown: public DefaultPermissions {};
140 class Chgrp: public DefaultPermissions {};
141 class Lookup: public DefaultPermissions {};
142 class Open: public DefaultPermissions {};
143 class Setattr: public DefaultPermissions {};
144 class Unlink: public DefaultPermissions {};
145 class Utimensat: public DefaultPermissions {};
146 class Write: public DefaultPermissions {};
147
148 /* 
149  * Test permission handling during create, mkdir, mknod, link, symlink, and
150  * rename vops (they all share a common path for permission checks in
151  * VOP_LOOKUP)
152  */
153 class Create: public DefaultPermissions {};
154
155 class Deleteextattr: public DefaultPermissions {
156 public:
157 void expect_removexattr()
158 {
159         EXPECT_CALL(*m_mock, process(
160                 ResultOf([=](auto in) {
161                         return (in.header.opcode == FUSE_REMOVEXATTR);
162                 }, Eq(true)),
163                 _)
164         ).WillOnce(Invoke(ReturnErrno(0)));
165 }
166 };
167
168 class Getextattr: public DefaultPermissions {
169 public:
170 void expect_getxattr(ProcessMockerT r)
171 {
172         EXPECT_CALL(*m_mock, process(
173                 ResultOf([=](auto in) {
174                         return (in.header.opcode == FUSE_GETXATTR);
175                 }, Eq(true)),
176                 _)
177         ).WillOnce(Invoke(r));
178 }
179 };
180
181 class Listextattr: public DefaultPermissions {
182 public:
183 void expect_listxattr()
184 {
185         EXPECT_CALL(*m_mock, process(
186                 ResultOf([=](auto in) {
187                         return (in.header.opcode == FUSE_LISTXATTR);
188                 }, Eq(true)),
189                 _)
190         ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
191                 out.body.listxattr.size = 0;
192                 SET_OUT_HEADER_LEN(out, listxattr);
193         })));
194 }
195 };
196
197 class Rename: public DefaultPermissions {
198 public:
199         /* 
200          * Expect a rename and respond with the given error.  Don't both to
201          * validate arguments; the tests in rename.cc do that.
202          */
203         void expect_rename(int error)
204         {
205                 EXPECT_CALL(*m_mock, process(
206                         ResultOf([=](auto in) {
207                                 return (in.header.opcode == FUSE_RENAME);
208                         }, Eq(true)),
209                         _)
210                 ).WillOnce(Invoke(ReturnErrno(error)));
211         }
212 };
213
214 class Setextattr: public DefaultPermissions {
215 public:
216 void expect_setxattr(int error)
217 {
218         EXPECT_CALL(*m_mock, process(
219                 ResultOf([=](auto in) {
220                         return (in.header.opcode == FUSE_SETXATTR);
221                 }, Eq(true)),
222                 _)
223         ).WillOnce(Invoke(ReturnErrno(error)));
224 }
225 };
226
227 /* Return a group to which this user does not belong */
228 static gid_t excluded_group()
229 {
230         int i, ngroups = 64;
231         gid_t newgid, groups[ngroups];
232
233         getgrouplist(getlogin(), getegid(), groups, &ngroups);
234         for (newgid = 0; newgid >= 0; newgid++) {
235                 bool belongs = false;
236
237                 for (i = 0; i < ngroups; i++) {
238                         if (groups[i] == newgid)
239                                 belongs = true;
240                 }
241                 if (!belongs)
242                         break;
243         }
244         /* newgid is now a group to which the current user does not belong */
245         return newgid;
246 }
247
248 TEST_F(Access, eacces)
249 {
250         const char FULLPATH[] = "mountpoint/some_file.txt";
251         const char RELPATH[] = "some_file.txt";
252         uint64_t ino = 42;
253         mode_t  access_mode = X_OK;
254
255         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
256         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
257
258         ASSERT_NE(0, access(FULLPATH, access_mode));
259         ASSERT_EQ(EACCES, errno);
260 }
261
262 TEST_F(Access, eacces_no_cached_attrs)
263 {
264         const char FULLPATH[] = "mountpoint/some_file.txt";
265         const char RELPATH[] = "some_file.txt";
266         uint64_t ino = 42;
267         mode_t  access_mode = X_OK;
268
269         expect_getattr(1, S_IFDIR | 0755, 0, 1);
270         expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
271         expect_getattr(ino, S_IFREG | 0644, 0, 1);
272         /* 
273          * Once default_permissions is properly implemented, there might be
274          * another FUSE_GETATTR or something in here.  But there should not be
275          * a FUSE_ACCESS
276          */
277
278         ASSERT_NE(0, access(FULLPATH, access_mode));
279         ASSERT_EQ(EACCES, errno);
280 }
281
282 TEST_F(Access, ok)
283 {
284         const char FULLPATH[] = "mountpoint/some_file.txt";
285         const char RELPATH[] = "some_file.txt";
286         uint64_t ino = 42;
287         mode_t  access_mode = R_OK;
288
289         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
290         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
291         /* 
292          * Once default_permissions is properly implemented, there might be
293          * another FUSE_GETATTR or something in here.
294          */
295
296         ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
297 }
298
299 /* Unprivileged users may chown a file to their own uid */
300 TEST_F(Chown, chown_to_self)
301 {
302         const char FULLPATH[] = "mountpoint/some_file.txt";
303         const char RELPATH[] = "some_file.txt";
304         const uint64_t ino = 42;
305         const mode_t mode = 0755;
306         uid_t uid;
307
308         uid = geteuid();
309
310         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
311         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
312         /* The OS may optimize chown by omitting the redundant setattr */
313         EXPECT_CALL(*m_mock, process(
314                 ResultOf([](auto in) {
315                         return (in.header.opcode == FUSE_SETATTR);
316                 }, Eq(true)),
317                 _)
318         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
319                 SET_OUT_HEADER_LEN(out, attr);
320                 out.body.attr.attr.mode = S_IFREG | mode;
321                 out.body.attr.attr.uid = uid;
322         })));
323
324         EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
325 }
326
327 /*
328  * A successful chown by a non-privileged non-owner should clear a file's SUID
329  * bit
330  */
331 TEST_F(Chown, clear_suid)
332 {
333         const char FULLPATH[] = "mountpoint/some_file.txt";
334         const char RELPATH[] = "some_file.txt";
335         uint64_t ino = 42;
336         const mode_t oldmode = 06755;
337         const mode_t newmode = 0755;
338         uid_t uid = geteuid();
339         uint32_t valid = FATTR_UID | FATTR_MODE;
340
341         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
342         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
343         EXPECT_CALL(*m_mock, process(
344                 ResultOf([=](auto in) {
345                         return (in.header.opcode == FUSE_SETATTR &&
346                                 in.header.nodeid == ino &&
347                                 in.body.setattr.valid == valid &&
348                                 in.body.setattr.mode == newmode);
349                 }, Eq(true)),
350                 _)
351         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
352                 SET_OUT_HEADER_LEN(out, attr);
353                 out.body.attr.attr.ino = ino;   // Must match nodeid
354                 out.body.attr.attr.mode = S_IFREG | newmode;
355                 out.body.attr.attr_valid = UINT64_MAX;
356         })));
357
358         EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
359 }
360
361
362 /* Only root may change a file's owner */
363 TEST_F(Chown, eperm)
364 {
365         const char FULLPATH[] = "mountpoint/some_file.txt";
366         const char RELPATH[] = "some_file.txt";
367         const uint64_t ino = 42;
368         const mode_t mode = 0755;
369
370         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
371         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
372         EXPECT_CALL(*m_mock, process(
373                 ResultOf([](auto in) {
374                         return (in.header.opcode == FUSE_SETATTR);
375                 }, Eq(true)),
376                 _)
377         ).Times(0);
378
379         EXPECT_NE(0, chown(FULLPATH, 0, -1));
380         EXPECT_EQ(EPERM, errno);
381 }
382
383 /*
384  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
385  * bit
386  */
387 TEST_F(Chgrp, clear_suid)
388 {
389         const char FULLPATH[] = "mountpoint/some_file.txt";
390         const char RELPATH[] = "some_file.txt";
391         uint64_t ino = 42;
392         const mode_t oldmode = 06755;
393         const mode_t newmode = 0755;
394         uid_t uid = geteuid();
395         gid_t gid = getegid();
396         uint32_t valid = FATTR_GID | FATTR_MODE;
397
398         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid);
399         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
400         EXPECT_CALL(*m_mock, process(
401                 ResultOf([=](auto in) {
402                         return (in.header.opcode == FUSE_SETATTR &&
403                                 in.header.nodeid == ino &&
404                                 in.body.setattr.valid == valid &&
405                                 in.body.setattr.mode == newmode);
406                 }, Eq(true)),
407                 _)
408         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
409                 SET_OUT_HEADER_LEN(out, attr);
410                 out.body.attr.attr.ino = ino;   // Must match nodeid
411                 out.body.attr.attr.mode = S_IFREG | newmode;
412                 out.body.attr.attr_valid = UINT64_MAX;
413         })));
414
415         EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
416 }
417
418 /* non-root users may only chgrp a file to a group they belong to */
419 TEST_F(Chgrp, eperm)
420 {
421         const char FULLPATH[] = "mountpoint/some_file.txt";
422         const char RELPATH[] = "some_file.txt";
423         const uint64_t ino = 42;
424         const mode_t mode = 0755;
425         uid_t uid;
426         gid_t gid, newgid;
427
428         uid = geteuid();
429         gid = getegid();
430         newgid = excluded_group();
431
432         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
433         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
434         EXPECT_CALL(*m_mock, process(
435                 ResultOf([](auto in) {
436                         return (in.header.opcode == FUSE_SETATTR);
437                 }, Eq(true)),
438                 _)
439         ).Times(0);
440
441         EXPECT_NE(0, chown(FULLPATH, -1, newgid));
442         EXPECT_EQ(EPERM, errno);
443 }
444
445 TEST_F(Chgrp, ok)
446 {
447         const char FULLPATH[] = "mountpoint/some_file.txt";
448         const char RELPATH[] = "some_file.txt";
449         const uint64_t ino = 42;
450         const mode_t mode = 0755;
451         uid_t uid;
452         gid_t gid, newgid;
453
454         uid = geteuid();
455         gid = 0;
456         newgid = getegid();
457
458         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
459         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
460         /* The OS may optimize chgrp by omitting the redundant setattr */
461         EXPECT_CALL(*m_mock, process(
462                 ResultOf([](auto in) {
463                         return (in.header.opcode == FUSE_SETATTR &&
464                                 in.header.nodeid == ino);
465                 }, Eq(true)),
466                 _)
467         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
468                 SET_OUT_HEADER_LEN(out, attr);
469                 out.body.attr.attr.mode = S_IFREG | mode;
470                 out.body.attr.attr.uid = uid;
471                 out.body.attr.attr.gid = newgid;
472         })));
473
474         EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
475 }
476
477 TEST_F(Create, ok)
478 {
479         const char FULLPATH[] = "mountpoint/some_file.txt";
480         const char RELPATH[] = "some_file.txt";
481         uint64_t ino = 42;
482         int fd;
483
484         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
485         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
486         expect_create(RELPATH, ino);
487
488         fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
489         EXPECT_LE(0, fd) << strerror(errno);
490         /* Deliberately leak fd.  close(2) will be tested in release.cc */
491 }
492
493 TEST_F(Create, eacces)
494 {
495         const char FULLPATH[] = "mountpoint/some_file.txt";
496         const char RELPATH[] = "some_file.txt";
497
498         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
499         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
500
501         EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
502         EXPECT_EQ(EACCES, errno);
503 }
504
505 TEST_F(Deleteextattr, eacces)
506 {
507         const char FULLPATH[] = "mountpoint/some_file.txt";
508         const char RELPATH[] = "some_file.txt";
509         uint64_t ino = 42;
510         int ns = EXTATTR_NAMESPACE_USER;
511
512         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
513         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
514
515         ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
516         ASSERT_EQ(EACCES, errno);
517 }
518
519 TEST_F(Deleteextattr, ok)
520 {
521         const char FULLPATH[] = "mountpoint/some_file.txt";
522         const char RELPATH[] = "some_file.txt";
523         uint64_t ino = 42;
524         int ns = EXTATTR_NAMESPACE_USER;
525
526         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
527         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
528         expect_removexattr();
529
530         ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
531                 << strerror(errno);
532 }
533
534 /* Delete system attributes requires superuser privilege */
535 TEST_F(Deleteextattr, system)
536 {
537         const char FULLPATH[] = "mountpoint/some_file.txt";
538         const char RELPATH[] = "some_file.txt";
539         uint64_t ino = 42;
540         int ns = EXTATTR_NAMESPACE_SYSTEM;
541
542         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
543         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
544
545         ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
546         ASSERT_EQ(EPERM, errno);
547 }
548
549 /* Anybody with write permission can set both timestamps to UTIME_NOW */
550 TEST_F(Utimensat, utime_now)
551 {
552         const char FULLPATH[] = "mountpoint/some_file.txt";
553         const char RELPATH[] = "some_file.txt";
554         const uint64_t ino = 42;
555         /* Write permissions for everybody */
556         const mode_t mode = 0666;
557         uid_t owner = 0;
558         const timespec times[2] = {
559                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
560                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
561         };
562
563         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
564         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
565         EXPECT_CALL(*m_mock, process(
566                 ResultOf([](auto in) {
567                         return (in.header.opcode == FUSE_SETATTR &&
568                                 in.header.nodeid == ino &&
569                                 in.body.setattr.valid & FATTR_ATIME &&
570                                 in.body.setattr.valid & FATTR_MTIME);
571                 }, Eq(true)),
572                 _)
573         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
574                 SET_OUT_HEADER_LEN(out, attr);
575                 out.body.attr.attr.mode = S_IFREG | mode;
576         })));
577
578         ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
579                 << strerror(errno);
580 }
581
582 /* Anybody can set both timestamps to UTIME_OMIT */
583 TEST_F(Utimensat, utime_omit)
584 {
585         const char FULLPATH[] = "mountpoint/some_file.txt";
586         const char RELPATH[] = "some_file.txt";
587         const uint64_t ino = 42;
588         /* Write permissions for no one */
589         const mode_t mode = 0444;
590         uid_t owner = 0;
591         const timespec times[2] = {
592                 {.tv_sec = 0, .tv_nsec = UTIME_OMIT},
593                 {.tv_sec = 0, .tv_nsec = UTIME_OMIT},
594         };
595
596         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
597         expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
598
599         ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
600                 << strerror(errno);
601 }
602
603 /* Deleting user attributes merely requires WRITE privilege */
604 TEST_F(Deleteextattr, user)
605 {
606         const char FULLPATH[] = "mountpoint/some_file.txt";
607         const char RELPATH[] = "some_file.txt";
608         uint64_t ino = 42;
609         int ns = EXTATTR_NAMESPACE_USER;
610
611         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
612         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
613         expect_removexattr();
614
615         ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
616                 << strerror(errno);
617 }
618
619 TEST_F(Getextattr, eacces)
620 {
621         const char FULLPATH[] = "mountpoint/some_file.txt";
622         const char RELPATH[] = "some_file.txt";
623         uint64_t ino = 42;
624         char data[80];
625         int ns = EXTATTR_NAMESPACE_USER;
626
627         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
628         expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
629
630         ASSERT_EQ(-1,
631                 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
632         ASSERT_EQ(EACCES, errno);
633 }
634
635 TEST_F(Getextattr, ok)
636 {
637         const char FULLPATH[] = "mountpoint/some_file.txt";
638         const char RELPATH[] = "some_file.txt";
639         uint64_t ino = 42;
640         char data[80];
641         const char value[] = "whatever";
642         ssize_t value_len = strlen(value) + 1;
643         int ns = EXTATTR_NAMESPACE_USER;
644         ssize_t r;
645
646         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
647         /* Getting user attributes only requires read access */
648         expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
649         expect_getxattr(
650                 ReturnImmediate([&](auto in __unused, auto& out) {
651                         memcpy((void*)out.body.bytes, value, value_len);
652                         out.header.len = sizeof(out.header) + value_len;
653                 })
654         );
655
656         r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
657         ASSERT_EQ(value_len, r)  << strerror(errno);
658         EXPECT_STREQ(value, data);
659 }
660
661 /* Getting system attributes requires superuser privileges */
662 TEST_F(Getextattr, system)
663 {
664         const char FULLPATH[] = "mountpoint/some_file.txt";
665         const char RELPATH[] = "some_file.txt";
666         uint64_t ino = 42;
667         char data[80];
668         int ns = EXTATTR_NAMESPACE_SYSTEM;
669
670         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
671         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
672
673         ASSERT_EQ(-1,
674                 extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
675         ASSERT_EQ(EPERM, errno);
676 }
677
678 TEST_F(Listextattr, eacces)
679 {
680         const char FULLPATH[] = "mountpoint/some_file.txt";
681         const char RELPATH[] = "some_file.txt";
682         uint64_t ino = 42;
683         int ns = EXTATTR_NAMESPACE_USER;
684
685         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
686         expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
687
688         ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
689         ASSERT_EQ(EACCES, errno);
690 }
691
692 TEST_F(Listextattr, ok)
693 {
694         const char FULLPATH[] = "mountpoint/some_file.txt";
695         const char RELPATH[] = "some_file.txt";
696         uint64_t ino = 42;
697         int ns = EXTATTR_NAMESPACE_USER;
698
699         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
700         /* Listing user extended attributes merely requires read access */
701         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
702         expect_listxattr();
703
704         ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
705                 << strerror(errno);
706 }
707
708 /* Listing system xattrs requires superuser privileges */
709 TEST_F(Listextattr, system)
710 {
711         const char FULLPATH[] = "mountpoint/some_file.txt";
712         const char RELPATH[] = "some_file.txt";
713         uint64_t ino = 42;
714         int ns = EXTATTR_NAMESPACE_SYSTEM;
715
716         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
717         /* Listing user extended attributes merely requires read access */
718         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
719
720         ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
721         ASSERT_EQ(EPERM, errno);
722 }
723
724 /* A component of the search path lacks execute permissions */
725 TEST_F(Lookup, eacces)
726 {
727         const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
728         const char RELDIRPATH[] = "some_dir";
729         uint64_t dir_ino = 42;
730
731         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
732         expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
733
734         EXPECT_EQ(-1, access(FULLPATH, F_OK));
735         EXPECT_EQ(EACCES, errno);
736 }
737
738 TEST_F(Open, eacces)
739 {
740         const char FULLPATH[] = "mountpoint/some_file.txt";
741         const char RELPATH[] = "some_file.txt";
742         uint64_t ino = 42;
743
744         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
745         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
746
747         EXPECT_NE(0, open(FULLPATH, O_RDWR));
748         EXPECT_EQ(EACCES, errno);
749 }
750
751 TEST_F(Open, ok)
752 {
753         const char FULLPATH[] = "mountpoint/some_file.txt";
754         const char RELPATH[] = "some_file.txt";
755         uint64_t ino = 42;
756         int fd;
757
758         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
759         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
760         expect_open(ino, 0, 1);
761
762         fd = open(FULLPATH, O_RDONLY);
763         EXPECT_LE(0, fd) << strerror(errno);
764         /* Deliberately leak fd.  close(2) will be tested in release.cc */
765 }
766
767 TEST_F(Rename, eacces_on_srcdir)
768 {
769         const char FULLDST[] = "mountpoint/d/dst";
770         const char RELDST[] = "d/dst";
771         const char FULLSRC[] = "mountpoint/src";
772         const char RELSRC[] = "src";
773         uint64_t ino = 42;
774
775         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1, 0);
776         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
777         EXPECT_LOOKUP(1, RELDST)
778                 .Times(AnyNumber())
779                 .WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
780
781         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
782         ASSERT_EQ(EACCES, errno);
783 }
784
785 TEST_F(Rename, eacces_on_dstdir_for_creating)
786 {
787         const char FULLDST[] = "mountpoint/d/dst";
788         const char RELDSTDIR[] = "d";
789         const char RELDST[] = "dst";
790         const char FULLSRC[] = "mountpoint/src";
791         const char RELSRC[] = "src";
792         uint64_t src_ino = 42;
793         uint64_t dstdir_ino = 43;
794
795         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
796         expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
797         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
798         EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
799
800         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
801         ASSERT_EQ(EACCES, errno);
802 }
803
804 TEST_F(Rename, eacces_on_dstdir_for_removing)
805 {
806         const char FULLDST[] = "mountpoint/d/dst";
807         const char RELDSTDIR[] = "d";
808         const char RELDST[] = "dst";
809         const char FULLSRC[] = "mountpoint/src";
810         const char RELSRC[] = "src";
811         uint64_t src_ino = 42;
812         uint64_t dstdir_ino = 43;
813
814         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
815         expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
816         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
817         EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
818
819         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
820         ASSERT_EQ(EACCES, errno);
821 }
822
823 TEST_F(Rename, eperm_on_sticky_srcdir)
824 {
825         const char FULLDST[] = "mountpoint/d/dst";
826         const char FULLSRC[] = "mountpoint/src";
827         const char RELSRC[] = "src";
828         uint64_t ino = 42;
829
830         expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
831         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
832
833         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
834         ASSERT_EQ(EPERM, errno);
835 }
836
837 /* 
838  * A user cannot move out a subdirectory that he does not own, because that
839  * would require changing the subdirectory's ".." dirent
840  */
841 TEST_F(Rename, eperm_for_subdirectory)
842 {
843         const char FULLDST[] = "mountpoint/d/dst";
844         const char FULLSRC[] = "mountpoint/src";
845         const char RELDSTDIR[] = "d";
846         const char RELDST[] = "dst";
847         const char RELSRC[] = "src";
848         uint64_t ino = 42;
849         uint64_t dstdir_ino = 43;
850
851         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
852         expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
853         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
854         EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
855
856         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
857         ASSERT_EQ(EACCES, errno);
858 }
859
860 /*
861  * A user _can_ rename a subdirectory to which he lacks write permissions, if
862  * it will keep the same parent
863  */
864 TEST_F(Rename, subdirectory_to_same_dir)
865 {
866         const char FULLDST[] = "mountpoint/dst";
867         const char FULLSRC[] = "mountpoint/src";
868         const char RELDST[] = "dst";
869         const char RELSRC[] = "src";
870         uint64_t ino = 42;
871
872         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
873         expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
874         EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
875         expect_rename(0);
876
877         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
878 }
879
880 TEST_F(Rename, eperm_on_sticky_dstdir)
881 {
882         const char FULLDST[] = "mountpoint/d/dst";
883         const char RELDSTDIR[] = "d";
884         const char RELDST[] = "dst";
885         const char FULLSRC[] = "mountpoint/src";
886         const char RELSRC[] = "src";
887         uint64_t src_ino = 42;
888         uint64_t dstdir_ino = 43;
889         uint64_t dst_ino = 44;
890
891         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, 0);
892         expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
893         expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
894         EXPECT_LOOKUP(dstdir_ino, RELDST)
895         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
896                 SET_OUT_HEADER_LEN(out, entry);
897                 out.body.entry.attr.mode = S_IFREG | 0644;
898                 out.body.entry.nodeid = dst_ino;
899                 out.body.entry.attr_valid = UINT64_MAX;
900                 out.body.entry.entry_valid = UINT64_MAX;
901                 out.body.entry.attr.uid = 0;
902         })));
903
904         ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
905         ASSERT_EQ(EPERM, errno);
906 }
907
908 /* Successfully rename a file, overwriting the destination */
909 TEST_F(Rename, ok)
910 {
911         const char FULLDST[] = "mountpoint/dst";
912         const char RELDST[] = "dst";
913         const char FULLSRC[] = "mountpoint/src";
914         const char RELSRC[] = "src";
915         // The inode of the already-existing destination file
916         uint64_t dst_ino = 2;
917         uint64_t ino = 42;
918
919         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
920         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
921         expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
922         expect_rename(0);
923
924         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
925 }
926
927 TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
928 {
929         const char FULLDST[] = "mountpoint/dst";
930         const char RELDST[] = "dst";
931         const char FULLSRC[] = "mountpoint/src";
932         const char RELSRC[] = "src";
933         uint64_t ino = 42;
934
935         expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1, 0);
936         expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
937         EXPECT_LOOKUP(1, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
938         expect_rename(0);
939
940         ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
941 }
942
943 TEST_F(Setattr, ok)
944 {
945         const char FULLPATH[] = "mountpoint/some_file.txt";
946         const char RELPATH[] = "some_file.txt";
947         const uint64_t ino = 42;
948         const mode_t oldmode = 0755;
949         const mode_t newmode = 0644;
950
951         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
952         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
953         EXPECT_CALL(*m_mock, process(
954                 ResultOf([](auto in) {
955                         return (in.header.opcode == FUSE_SETATTR &&
956                                 in.header.nodeid == ino &&
957                                 in.body.setattr.mode == newmode);
958                 }, Eq(true)),
959                 _)
960         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
961                 SET_OUT_HEADER_LEN(out, attr);
962                 out.body.attr.attr.mode = S_IFREG | newmode;
963         })));
964
965         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
966 }
967
968 TEST_F(Setattr, eacces)
969 {
970         const char FULLPATH[] = "mountpoint/some_file.txt";
971         const char RELPATH[] = "some_file.txt";
972         const uint64_t ino = 42;
973         const mode_t oldmode = 0755;
974         const mode_t newmode = 0644;
975
976         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
977         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
978         EXPECT_CALL(*m_mock, process(
979                 ResultOf([](auto in) {
980                         return (in.header.opcode == FUSE_SETATTR);
981                 }, Eq(true)),
982                 _)
983         ).Times(0);
984
985         EXPECT_NE(0, chmod(FULLPATH, newmode));
986         EXPECT_EQ(EPERM, errno);
987 }
988
989 /*
990  * ftruncate() of a file without writable permissions should succeed as long as
991  * the file descriptor is writable.  This is important when combined with
992  * O_CREAT
993  */
994 TEST_F(Setattr, ftruncate_of_newly_created_file)
995 {
996         const char FULLPATH[] = "mountpoint/some_file.txt";
997         const char RELPATH[] = "some_file.txt";
998         const uint64_t ino = 42;
999         const mode_t mode = 0000;
1000         int fd;
1001
1002         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
1003         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
1004         expect_create(RELPATH, ino);
1005         EXPECT_CALL(*m_mock, process(
1006                 ResultOf([](auto in) {
1007                         return (in.header.opcode == FUSE_SETATTR &&
1008                                 in.header.nodeid == ino &&
1009                                 (in.body.setattr.valid & FATTR_SIZE));
1010                 }, Eq(true)),
1011                 _)
1012         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1013                 SET_OUT_HEADER_LEN(out, attr);
1014                 out.body.attr.attr.ino = ino;
1015                 out.body.attr.attr.mode = S_IFREG | mode;
1016                 out.body.attr.attr_valid = UINT64_MAX;
1017         })));
1018
1019         fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1020         ASSERT_LE(0, fd) << strerror(errno);
1021         ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
1022         /* Deliberately leak fd */
1023 }
1024
1025 /* 
1026  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
1027  * to the file's group
1028  */
1029 TEST_F(Setattr, sgid_by_non_group_member)
1030 {
1031         const char FULLPATH[] = "mountpoint/some_file.txt";
1032         const char RELPATH[] = "some_file.txt";
1033         const uint64_t ino = 42;
1034         const mode_t oldmode = 0755;
1035         const mode_t newmode = 02755;
1036         uid_t uid = geteuid();
1037         gid_t gid = excluded_group();
1038
1039         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1040         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
1041         EXPECT_CALL(*m_mock, process(
1042                 ResultOf([](auto in) {
1043                         return (in.header.opcode == FUSE_SETATTR);
1044                 }, Eq(true)),
1045                 _)
1046         ).Times(0);
1047
1048         EXPECT_NE(0, chmod(FULLPATH, newmode));
1049         EXPECT_EQ(EPERM, errno);
1050 }
1051
1052 /* Only the superuser may set the sticky bit on a non-directory */
1053 TEST_F(Setattr, sticky_regular_file)
1054 {
1055         const char FULLPATH[] = "mountpoint/some_file.txt";
1056         const char RELPATH[] = "some_file.txt";
1057         const uint64_t ino = 42;
1058         const mode_t oldmode = 0644;
1059         const mode_t newmode = 01644;
1060
1061         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1062         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1063         EXPECT_CALL(*m_mock, process(
1064                 ResultOf([](auto in) {
1065                         return (in.header.opcode == FUSE_SETATTR);
1066                 }, Eq(true)),
1067                 _)
1068         ).Times(0);
1069
1070         EXPECT_NE(0, chmod(FULLPATH, newmode));
1071         EXPECT_EQ(EFTYPE, errno);
1072 }
1073
1074 TEST_F(Setextattr, ok)
1075 {
1076         const char FULLPATH[] = "mountpoint/some_file.txt";
1077         const char RELPATH[] = "some_file.txt";
1078         uint64_t ino = 42;
1079         const char value[] = "whatever";
1080         ssize_t value_len = strlen(value) + 1;
1081         int ns = EXTATTR_NAMESPACE_USER;
1082         ssize_t r;
1083
1084         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1085         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1086         expect_setxattr(0);
1087
1088         r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1089         ASSERT_EQ(value_len, r) << strerror(errno);
1090 }
1091
1092 TEST_F(Setextattr, eacces)
1093 {
1094         const char FULLPATH[] = "mountpoint/some_file.txt";
1095         const char RELPATH[] = "some_file.txt";
1096         uint64_t ino = 42;
1097         const char value[] = "whatever";
1098         ssize_t value_len = strlen(value) + 1;
1099         int ns = EXTATTR_NAMESPACE_USER;
1100
1101         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1102         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1103
1104         ASSERT_EQ(-1,
1105                 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1106         ASSERT_EQ(EACCES, errno);
1107 }
1108
1109 // Setting system attributes requires superuser privileges
1110 TEST_F(Setextattr, system)
1111 {
1112         const char FULLPATH[] = "mountpoint/some_file.txt";
1113         const char RELPATH[] = "some_file.txt";
1114         uint64_t ino = 42;
1115         const char value[] = "whatever";
1116         ssize_t value_len = strlen(value) + 1;
1117         int ns = EXTATTR_NAMESPACE_SYSTEM;
1118
1119         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1120         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1121
1122         ASSERT_EQ(-1,
1123                 extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len));
1124         ASSERT_EQ(EPERM, errno);
1125 }
1126
1127 // Setting user attributes merely requires write privileges
1128 TEST_F(Setextattr, user)
1129 {
1130         const char FULLPATH[] = "mountpoint/some_file.txt";
1131         const char RELPATH[] = "some_file.txt";
1132         uint64_t ino = 42;
1133         const char value[] = "whatever";
1134         ssize_t value_len = strlen(value) + 1;
1135         int ns = EXTATTR_NAMESPACE_USER;
1136         ssize_t r;
1137
1138         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1139         expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1140         expect_setxattr(0);
1141
1142         r = extattr_set_file(FULLPATH, ns, "foo", (void*)value, value_len);
1143         ASSERT_EQ(value_len, r) << strerror(errno);
1144 }
1145
1146 TEST_F(Unlink, ok)
1147 {
1148         const char FULLPATH[] = "mountpoint/some_file.txt";
1149         const char RELPATH[] = "some_file.txt";
1150         uint64_t ino = 42;
1151
1152         expect_getattr(1, S_IFDIR | 0777, UINT64_MAX, 1);
1153         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1154         expect_unlink(1, RELPATH, 0);
1155
1156         ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1157 }
1158
1159 /*
1160  * Ensure that a cached name doesn't cause unlink to bypass permission checks
1161  * in VOP_LOOKUP.
1162  *
1163  * This test should pass because lookup(9) purges the namecache entry by doing
1164  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
1165  */
1166 TEST_F(Unlink, cached_unwritable_directory)
1167 {
1168         const char FULLPATH[] = "mountpoint/some_file.txt";
1169         const char RELPATH[] = "some_file.txt";
1170         uint64_t ino = 42;
1171
1172         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1173         EXPECT_LOOKUP(1, RELPATH)
1174         .Times(AnyNumber())
1175         .WillRepeatedly(Invoke(
1176                 ReturnImmediate([=](auto i __unused, auto& out) {
1177                         SET_OUT_HEADER_LEN(out, entry);
1178                         out.body.entry.attr.mode = S_IFREG | 0644;
1179                         out.body.entry.nodeid = ino;
1180                         out.body.entry.entry_valid = UINT64_MAX;
1181                 }))
1182         );
1183
1184         /* Fill name cache */
1185         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
1186         /* Despite cached name , unlink should fail */
1187         ASSERT_EQ(-1, unlink(FULLPATH));
1188         ASSERT_EQ(EACCES, errno);
1189 }
1190
1191 TEST_F(Unlink, unwritable_directory)
1192 {
1193         const char FULLPATH[] = "mountpoint/some_file.txt";
1194         const char RELPATH[] = "some_file.txt";
1195         uint64_t ino = 42;
1196
1197         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1198         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1199
1200         ASSERT_EQ(-1, unlink(FULLPATH));
1201         ASSERT_EQ(EACCES, errno);
1202 }
1203
1204 TEST_F(Unlink, sticky_directory)
1205 {
1206         const char FULLPATH[] = "mountpoint/some_file.txt";
1207         const char RELPATH[] = "some_file.txt";
1208         uint64_t ino = 42;
1209
1210         expect_getattr(1, S_IFDIR | 01777, UINT64_MAX, 1);
1211         expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1212
1213         ASSERT_EQ(-1, unlink(FULLPATH));
1214         ASSERT_EQ(EPERM, errno);
1215 }
1216
1217 /* A write by a non-owner should clear a file's SUID bit */
1218 TEST_F(Write, clear_suid)
1219 {
1220         const char FULLPATH[] = "mountpoint/some_file.txt";
1221         const char RELPATH[] = "some_file.txt";
1222         struct stat sb;
1223         uint64_t ino = 42;
1224         mode_t oldmode = 04777;
1225         mode_t newmode = 0777;
1226         char wbuf[1] = {'x'};
1227         int fd;
1228
1229         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1230         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1231         expect_open(ino, 0, 1);
1232         expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1233         expect_chmod(ino, newmode, sizeof(wbuf));
1234
1235         fd = open(FULLPATH, O_WRONLY);
1236         ASSERT_LE(0, fd) << strerror(errno);
1237         ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1238         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1239         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1240         /* Deliberately leak fd.  close(2) will be tested in release.cc */
1241 }
1242
1243 /* A write by a non-owner should clear a file's SGID bit */
1244 TEST_F(Write, clear_sgid)
1245 {
1246         const char FULLPATH[] = "mountpoint/some_file.txt";
1247         const char RELPATH[] = "some_file.txt";
1248         struct stat sb;
1249         uint64_t ino = 42;
1250         mode_t oldmode = 02777;
1251         mode_t newmode = 0777;
1252         char wbuf[1] = {'x'};
1253         int fd;
1254
1255         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1256         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1257         expect_open(ino, 0, 1);
1258         expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1259         expect_chmod(ino, newmode, sizeof(wbuf));
1260
1261         fd = open(FULLPATH, O_WRONLY);
1262         ASSERT_LE(0, fd) << strerror(errno);
1263         ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1264         ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1265         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1266         /* Deliberately leak fd.  close(2) will be tested in release.cc */
1267 }
1268
1269 /* Regression test for a specific recurse-of-nonrecursive-lock panic
1270  *
1271  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
1272  * may panic.  That happens if the FUSE_SETATTR response indicates that the
1273  * file's size has changed since the write.
1274  */
1275 TEST_F(Write, recursion_panic_while_clearing_suid)
1276 {
1277         const char FULLPATH[] = "mountpoint/some_file.txt";
1278         const char RELPATH[] = "some_file.txt";
1279         uint64_t ino = 42;
1280         mode_t oldmode = 04777;
1281         mode_t newmode = 0777;
1282         char wbuf[1] = {'x'};
1283         int fd;
1284
1285         expect_getattr(1, S_IFDIR | 0755, UINT64_MAX, 1);
1286         expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1287         expect_open(ino, 0, 1);
1288         expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
1289         /* XXX Return a smaller file size than what we just wrote! */
1290         expect_chmod(ino, newmode, 0);
1291
1292         fd = open(FULLPATH, O_WRONLY);
1293         ASSERT_LE(0, fd) << strerror(errno);
1294         ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1295         /* Deliberately leak fd.  close(2) will be tested in release.cc */
1296 }
1297
1298