Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/default_permissions.cc
39537 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2019 The FreeBSD Foundation
5
*
6
* This software was developed by BFF Storage Systems, LLC under sponsorship
7
* from the FreeBSD Foundation.
8
*
9
* Redistribution and use in source and binary forms, with or without
10
* modification, are permitted provided that the following conditions
11
* are met:
12
* 1. Redistributions of source code must retain the above copyright
13
* notice, this list of conditions and the following disclaimer.
14
* 2. Redistributions in binary form must reproduce the above copyright
15
* notice, this list of conditions and the following disclaimer in the
16
* documentation and/or other materials provided with the distribution.
17
*
18
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
* SUCH DAMAGE.
29
*/
30
31
/*
32
* Tests for the "default_permissions" mount option. They must be in their own
33
* file so they can be run as an unprivileged user.
34
*/
35
36
extern "C" {
37
#include <sys/types.h>
38
#include <sys/extattr.h>
39
40
#include <fcntl.h>
41
#include <semaphore.h>
42
#include <unistd.h>
43
}
44
45
#include "mockfs.hh"
46
#include "utils.hh"
47
48
using namespace testing;
49
50
class DefaultPermissions: public FuseTest {
51
52
virtual void SetUp() {
53
m_default_permissions = true;
54
FuseTest::SetUp();
55
if (HasFatalFailure() || IsSkipped())
56
return;
57
58
if (geteuid() == 0) {
59
GTEST_SKIP() << "This test requires an unprivileged user";
60
}
61
62
/* With -o default_permissions, FUSE_ACCESS should never be called */
63
EXPECT_CALL(*m_mock, process(
64
ResultOf([=](auto in) {
65
return (in.header.opcode == FUSE_ACCESS);
66
}, Eq(true)),
67
_)
68
).Times(0);
69
}
70
71
public:
72
void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
73
{
74
EXPECT_CALL(*m_mock, process(
75
ResultOf([=](auto in) {
76
return (in.header.opcode == FUSE_SETATTR &&
77
in.header.nodeid == ino &&
78
in.body.setattr.valid == FATTR_MODE &&
79
in.body.setattr.mode == mode);
80
}, Eq(true)),
81
_)
82
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
83
SET_OUT_HEADER_LEN(out, attr);
84
out.body.attr.attr.ino = ino; // Must match nodeid
85
out.body.attr.attr.mode = S_IFREG | mode;
86
out.body.attr.attr.size = size;
87
out.body.attr.attr_valid = UINT64_MAX;
88
})));
89
}
90
91
void expect_create(const char *relpath, uint64_t ino)
92
{
93
EXPECT_CALL(*m_mock, process(
94
ResultOf([=](auto in) {
95
const char *name = (const char*)in.body.bytes +
96
sizeof(fuse_create_in);
97
return (in.header.opcode == FUSE_CREATE &&
98
(0 == strcmp(relpath, name)));
99
}, Eq(true)),
100
_)
101
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
102
SET_OUT_HEADER_LEN(out, create);
103
out.body.create.entry.attr.mode = S_IFREG | 0644;
104
out.body.create.entry.nodeid = ino;
105
out.body.create.entry.entry_valid = UINT64_MAX;
106
out.body.create.entry.attr_valid = UINT64_MAX;
107
})));
108
}
109
110
void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
111
uint64_t off_out, uint64_t len)
112
{
113
EXPECT_CALL(*m_mock, process(
114
ResultOf([=](auto in) {
115
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
116
in.header.nodeid == ino_in &&
117
in.body.copy_file_range.off_in == off_in &&
118
in.body.copy_file_range.nodeid_out == ino_out &&
119
in.body.copy_file_range.off_out == off_out &&
120
in.body.copy_file_range.len == len);
121
}, Eq(true)),
122
_)
123
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
124
SET_OUT_HEADER_LEN(out, write);
125
out.body.write.size = len;
126
})));
127
}
128
129
void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
130
uid_t uid = 0, gid_t gid = 0)
131
{
132
EXPECT_CALL(*m_mock, process(
133
ResultOf([=](auto in) {
134
return (in.header.opcode == FUSE_GETATTR &&
135
in.header.nodeid == ino);
136
}, Eq(true)),
137
_)
138
).Times(times)
139
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
140
SET_OUT_HEADER_LEN(out, attr);
141
out.body.attr.attr.ino = ino; // Must match nodeid
142
out.body.attr.attr.mode = mode;
143
out.body.attr.attr.size = 0;
144
out.body.attr.attr.uid = uid;
145
out.body.attr.attr.gid = gid;
146
out.body.attr.attr_valid = attr_valid;
147
})));
148
}
149
150
void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
151
uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
152
{
153
FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
154
}
155
156
};
157
158
class Access: public DefaultPermissions {};
159
class Chown: public DefaultPermissions {};
160
class Chgrp: public DefaultPermissions {};
161
class CopyFileRange: public DefaultPermissions {};
162
class Fspacectl: public DefaultPermissions {};
163
class Lookup: public DefaultPermissions {};
164
class Open: public DefaultPermissions {};
165
class PosixFallocate: public DefaultPermissions {};
166
class 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:
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:
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:
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
*/
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:
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 */
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
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
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
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 */
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
*/
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 */
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
*/
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 */
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
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 */
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 */
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
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
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
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
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 */
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 */
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, &times[0], 0))
693
<< strerror(errno);
694
}
695
696
/* Anybody can set both timestamps to 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, &times[0], 0))
714
<< strerror(errno);
715
}
716
717
/* Deleting user attributes merely requires WRITE privilege */
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
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
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 */
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
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
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 */
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 */
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 */
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
*/
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 */
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
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
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 */
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 */
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
*/
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
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
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
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
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
*/
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
*/
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
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 */
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
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.
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
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
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
*/
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
*/
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 */
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
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
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
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
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
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
*/
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
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
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 */
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 */
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
*/
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
1646