Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/allow_other.cc
39536 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 "allow_other" mount option. They must be in their own
33
* file so they can be run as root
34
*/
35
36
extern "C" {
37
#include <sys/types.h>
38
#include <sys/extattr.h>
39
#include <sys/wait.h>
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
const static char FULLPATH[] = "mountpoint/some_file.txt";
50
const static char RELPATH[] = "some_file.txt";
51
52
class NoAllowOther: public FuseTest {
53
54
public:
55
virtual void SetUp() {
56
if (geteuid() != 0) {
57
GTEST_SKIP() << "This test must be run as root";
58
}
59
60
FuseTest::SetUp();
61
}
62
};
63
64
class AllowOther: public NoAllowOther {
65
66
public:
67
virtual void SetUp() {
68
m_allow_other = true;
69
NoAllowOther::SetUp();
70
}
71
};
72
73
TEST_F(AllowOther, allowed)
74
{
75
int status;
76
77
fork(true, &status, [&] {
78
uint64_t ino = 42;
79
80
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
81
expect_open(ino, 0, 1);
82
expect_flush(ino, 1, ReturnErrno(0));
83
expect_release(ino, FH);
84
}, []() {
85
int fd;
86
87
fd = open(FULLPATH, O_RDONLY);
88
if (fd < 0) {
89
perror("open");
90
return(1);
91
}
92
93
leak(fd);
94
return 0;
95
}
96
);
97
ASSERT_EQ(0, WEXITSTATUS(status));
98
}
99
100
/* Check that fusefs uses the correct credentials for FUSE operations */
101
TEST_F(AllowOther, creds)
102
{
103
int status;
104
uid_t uid;
105
gid_t gid;
106
107
get_unprivileged_id(&uid, &gid);
108
fork(true, &status, [=] {
109
EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) {
110
return (in.header.opcode == FUSE_LOOKUP &&
111
in.header.uid == uid &&
112
in.header.gid == gid);
113
}, Eq(true)),
114
_)
115
).Times(1)
116
.WillOnce(Invoke(ReturnErrno(ENOENT)));
117
}, []() {
118
eaccess(FULLPATH, F_OK);
119
return 0;
120
}
121
);
122
ASSERT_EQ(0, WEXITSTATUS(status));
123
}
124
125
/*
126
* A variation of the Open.multiple_creds test showing how the bug can lead to a
127
* privilege elevation. The first process is privileged and opens a file only
128
* visible to root. The second process is unprivileged and shouldn't be able
129
* to open the file, but does thanks to the bug
130
*/
131
TEST_F(AllowOther, privilege_escalation)
132
{
133
int fd1, status;
134
const static uint64_t ino = 42;
135
const static uint64_t fh = 100;
136
137
/* Fork a child to open the file with different credentials */
138
fork(true, &status, [&] {
139
140
expect_lookup(RELPATH, ino, S_IFREG | 0600, 0, 2);
141
EXPECT_CALL(*m_mock, process(
142
ResultOf([=](auto in) {
143
return (in.header.opcode == FUSE_OPEN &&
144
in.header.pid == (uint32_t)getpid() &&
145
in.header.uid == (uint32_t)geteuid() &&
146
in.header.nodeid == ino);
147
}, Eq(true)),
148
_)
149
).WillOnce(Invoke(
150
ReturnImmediate([](auto in __unused, auto& out) {
151
out.body.open.fh = fh;
152
out.header.len = sizeof(out.header);
153
SET_OUT_HEADER_LEN(out, open);
154
})));
155
156
EXPECT_CALL(*m_mock, process(
157
ResultOf([=](auto in) {
158
return (in.header.opcode == FUSE_OPEN &&
159
in.header.pid != (uint32_t)getpid() &&
160
in.header.uid != (uint32_t)geteuid() &&
161
in.header.nodeid == ino);
162
}, Eq(true)),
163
_)
164
).Times(AnyNumber())
165
.WillRepeatedly(Invoke(ReturnErrno(EPERM)));
166
167
fd1 = open(FULLPATH, O_RDONLY);
168
ASSERT_LE(0, fd1) << strerror(errno);
169
}, [] {
170
int fd0;
171
172
fd0 = open(FULLPATH, O_RDONLY);
173
if (fd0 >= 0) {
174
fprintf(stderr, "Privilege escalation!\n");
175
return 1;
176
}
177
if (errno != EPERM) {
178
fprintf(stderr, "Unexpected error %s\n",
179
strerror(errno));
180
return 1;
181
}
182
leak(fd0);
183
return 0;
184
}
185
);
186
ASSERT_EQ(0, WEXITSTATUS(status));
187
leak(fd1);
188
}
189
190
TEST_F(NoAllowOther, disallowed)
191
{
192
int status;
193
194
fork(true, &status, [] {
195
}, []() {
196
int fd;
197
198
fd = open(FULLPATH, O_RDONLY);
199
if (fd >= 0) {
200
fprintf(stderr, "open should've failed\n");
201
leak(fd);
202
return(1);
203
} else if (errno != EPERM) {
204
fprintf(stderr, "Unexpected error: %s\n",
205
strerror(errno));
206
return(1);
207
}
208
return 0;
209
}
210
);
211
ASSERT_EQ(0, WEXITSTATUS(status));
212
}
213
214
/*
215
* When -o allow_other is not used, users other than the owner aren't allowed
216
* to open anything inside of the mount point, not just the mountpoint itself
217
* This is a regression test for bug 237052
218
*/
219
TEST_F(NoAllowOther, disallowed_beneath_root)
220
{
221
const static char RELPATH2[] = "other_dir";
222
const static uint64_t ino = 42;
223
const static uint64_t ino2 = 43;
224
int dfd, status;
225
226
expect_lookup(RELPATH, ino, S_IFDIR | 0755, 0, 1);
227
EXPECT_LOOKUP(ino, RELPATH2)
228
.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
229
SET_OUT_HEADER_LEN(out, entry);
230
out.body.entry.attr.mode = S_IFREG | 0644;
231
out.body.entry.nodeid = ino2;
232
out.body.entry.attr.nlink = 1;
233
out.body.entry.attr_valid = UINT64_MAX;
234
})));
235
expect_opendir(ino);
236
dfd = open(FULLPATH, O_DIRECTORY);
237
ASSERT_LE(0, dfd) << strerror(errno);
238
239
fork(true, &status, [] {
240
}, [&]() {
241
int fd;
242
243
fd = openat(dfd, RELPATH2, O_RDONLY);
244
if (fd >= 0) {
245
fprintf(stderr, "openat should've failed\n");
246
leak(fd);
247
return(1);
248
} else if (errno != EPERM) {
249
fprintf(stderr, "Unexpected error: %s\n",
250
strerror(errno));
251
return(1);
252
}
253
return 0;
254
}
255
);
256
ASSERT_EQ(0, WEXITSTATUS(status));
257
258
leak(dfd);
259
}
260
261
/*
262
* Provide coverage for the extattr methods, which have a slightly different
263
* code path
264
*/
265
TEST_F(NoAllowOther, setextattr)
266
{
267
int ino = 42, status;
268
269
fork(true, &status, [&] {
270
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
271
.WillOnce(Invoke(
272
ReturnImmediate([=](auto in __unused, auto& out) {
273
SET_OUT_HEADER_LEN(out, entry);
274
out.body.entry.attr_valid = UINT64_MAX;
275
out.body.entry.entry_valid = UINT64_MAX;
276
out.body.entry.attr.mode = S_IFREG | 0644;
277
out.body.entry.nodeid = ino;
278
})));
279
280
/*
281
* lookup the file to get it into the cache.
282
* Otherwise, the unprivileged lookup will fail with
283
* EACCES
284
*/
285
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
286
}, [&]() {
287
const char value[] = "whatever";
288
ssize_t value_len = strlen(value) + 1;
289
int ns = EXTATTR_NAMESPACE_USER;
290
ssize_t r;
291
292
r = extattr_set_file(FULLPATH, ns, "foo",
293
(const void*)value, value_len);
294
if (r >= 0) {
295
fprintf(stderr, "should've failed\n");
296
return(1);
297
} else if (errno != EPERM) {
298
fprintf(stderr, "Unexpected error: %s\n",
299
strerror(errno));
300
return(1);
301
}
302
return 0;
303
}
304
);
305
ASSERT_EQ(0, WEXITSTATUS(status));
306
}
307
308