2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2021 Alan Somers
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 #include <sys/param.h>
32 #include <sys/mount.h>
37 #include <semaphore.h>
43 using namespace testing;
46 * "Last Local Modify" bugs
48 * This file tests a class of race conditions caused by one thread fetching a
49 * file's size with FUSE_LOOKUP while another thread simultaneously modifies it
50 * with FUSE_SETATTR, FUSE_WRITE, FUSE_COPY_FILE_RANGE or similar. It's
51 * possible for the second thread to start later yet finish first. If that
52 * happens, the first thread must not override the size set by the second
55 * FUSE_GETATTR is not vulnerable to the same race, because it is always called
56 * with the vnode lock held.
58 * A few other operations like FUSE_LINK can also trigger the same race but
59 * with the file's ctime instead of size. However, the consequences of an
60 * incorrect ctime are much less disastrous than an incorrect size, so fusefs
61 * does not attempt to prevent such races.
72 * Translate a poll method's string representation to the enum value.
73 * Using strings with ::testing::Values gives better output with
76 enum Mutator writer_from_str(const char* s) {
77 if (0 == strcmp("VOP_ALLOCATE", s))
79 else if (0 == strcmp("VOP_COPY_FILE_RANGE", s))
80 return VOP_COPY_FILE_RANGE;
81 else if (0 == strcmp("VOP_SETATTR", s))
87 uint32_t fuse_op_from_mutator(enum Mutator mutator) {
90 return(FUSE_FALLOCATE);
91 case VOP_COPY_FILE_RANGE:
92 return(FUSE_COPY_FILE_RANGE);
100 class LastLocalModify: public FuseTest, public WithParamInterface<const char*> {
102 virtual void SetUp() {
103 m_init_flags = FUSE_EXPORT_SUPPORT;
109 static void* allocate_th(void* arg) {
112 sem_t *sem = (sem_t*) arg;
117 fd = open("mountpoint/some_file.txt", O_RDWR);
119 return (void*)(intptr_t)errno;
121 r = posix_fallocate(fd, 0, 15);
122 LastLocalModify::leak(fd);
126 return (void*)(intptr_t)errno;
129 static void* copy_file_range_th(void* arg) {
132 sem_t *sem = (sem_t*) arg;
139 fd = open("mountpoint/some_file.txt", O_RDWR);
141 return (void*)(intptr_t)errno;
143 r = copy_file_range(fd, &off_in, fd, &off_out, len, 0);
145 LastLocalModify::leak(fd);
148 return (void*)(intptr_t)errno;
151 static void* setattr_th(void* arg) {
154 sem_t *sem = (sem_t*) arg;
159 fd = open("mountpoint/some_file.txt", O_RDWR);
161 return (void*)(intptr_t)errno;
163 r = ftruncate(fd, 15);
164 LastLocalModify::leak(fd);
168 return (void*)(intptr_t)errno;
171 static void* write_th(void* arg) {
174 sem_t *sem = (sem_t*) arg;
175 const char BUF[] = "abcdefghijklmn";
179 fd = open("mountpoint/some_file.txt", O_RDWR);
181 return (void*)(intptr_t)errno;
183 r = write(fd, BUF, sizeof(BUF));
185 LastLocalModify::leak(fd);
188 return (void*)(intptr_t)errno;
192 * VOP_LOOKUP should discard attributes returned by the server if they were
193 * modified by another VOP while the VOP_LOOKUP was in progress.
195 * Sequence of operations:
196 * * Thread 1 calls a mutator like ftruncate, which acquires the vnode lock
198 * * Thread 2 calls stat, which does VOP_LOOKUP, which sends FUSE_LOOKUP to the
199 * server. The server replies with the old file length. Thread 2 blocks
200 * waiting for the vnode lock.
201 * * Thread 1 sends the mutator operation like FUSE_SETATTR that changes the
202 * file's size and updates the attribute cache. Then it releases the vnode
204 * * Thread 2 acquires the vnode lock. At this point it must not add the
205 * now-stale file size to the attribute cache.
207 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071
209 TEST_P(LastLocalModify, lookup)
211 const char FULLPATH[] = "mountpoint/some_file.txt";
212 const char RELPATH[] = "some_file.txt";
215 uint64_t mutator_unique;
216 const uint64_t oldsize = 10;
217 const uint64_t newsize = 15;
226 mutator = writer_from_str(GetParam());
227 mutator_op = fuse_op_from_mutator(mutator);
229 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
231 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
233 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
234 /* Called by the mutator, caches attributes but not entries */
235 SET_OUT_HEADER_LEN(out, entry);
236 out.body.entry.nodeid = ino;
237 out.body.entry.attr.size = oldsize;
238 out.body.entry.nodeid = ino;
239 out.body.entry.attr_valid_nsec = NAP_NS / 2;
240 out.body.entry.attr.ino = ino;
241 out.body.entry.attr.mode = S_IFREG | 0644;
243 expect_open(ino, 0, 1);
244 EXPECT_CALL(*m_mock, process(
245 ResultOf([=](auto in) {
246 return (in.header.opcode == mutator_op &&
247 in.header.nodeid == ino);
251 .WillOnce(Invoke([&](auto in, auto &out __unused) {
253 * The mutator changes the file size, but in order to simulate
254 * a race, don't reply. Instead, just save the unique for
257 mutator_unique = in.header.unique;
260 mutator_size = in.body.write.size;
262 case VOP_COPY_FILE_RANGE:
263 mutator_size = in.body.copy_file_range.len;
268 /* Allow the lookup thread to proceed */
271 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
273 .WillOnce(Invoke([&](auto in __unused, auto& out) {
274 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
275 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
277 /* First complete the lookup request, returning the old size */
278 out0->header.unique = in.header.unique;
279 SET_OUT_HEADER_LEN(*out0, entry);
280 out0->body.entry.attr.mode = S_IFREG | 0644;
281 out0->body.entry.nodeid = ino;
282 out0->body.entry.entry_valid = UINT64_MAX;
283 out0->body.entry.attr_valid = UINT64_MAX;
284 out0->body.entry.attr.size = oldsize;
285 out.push_back(std::move(out0));
287 /* Then, respond to the mutator request */
288 out1->header.unique = mutator_unique;
291 out1->header.error = 0;
292 out1->header.len = sizeof(out1->header);
294 case VOP_COPY_FILE_RANGE:
295 SET_OUT_HEADER_LEN(*out1, write);
296 out1->body.write.size = mutator_size;
299 SET_OUT_HEADER_LEN(*out1, attr);
300 out1->body.attr.attr.ino = ino;
301 out1->body.attr.attr.mode = S_IFREG | 0644;
302 out1->body.attr.attr.size = newsize; // Changed size
303 out1->body.attr.attr_valid = UINT64_MAX;
306 SET_OUT_HEADER_LEN(*out1, write);
307 out1->body.write.size = mutator_size;
310 out.push_back(std::move(out1));
313 /* Start the mutator thread */
316 ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
317 NULL)) << strerror(errno);
319 case VOP_COPY_FILE_RANGE:
320 ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
321 NULL)) << strerror(errno);
324 ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL))
328 ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL))
334 /* Wait for FUSE_SETATTR to be sent */
337 /* Lookup again, which will race with setattr */
338 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
339 ASSERT_EQ((off_t)newsize, sb.st_size);
341 /* ftruncate should've completed without error */
342 pthread_join(th0, &thr0_value);
343 EXPECT_EQ(0, (intptr_t)thr0_value);
347 * VFS_VGET should discard attributes returned by the server if they were
348 * modified by another VOP while the VFS_VGET was in progress.
350 * Sequence of operations:
351 * * Thread 1 calls fhstat, entering VFS_VGET, and issues FUSE_LOOKUP
352 * * Thread 2 calls a mutator like ftruncate, which acquires the vnode lock
353 * exclusively and issues a FUSE op like FUSE_SETATTR.
354 * * Thread 1's FUSE_LOOKUP returns with the old size, but the thread blocks
355 * waiting for the vnode lock.
356 * * Thread 2's FUSE op returns, and that thread sets the file's new size
357 * in the attribute cache. Finally it releases the vnode lock.
358 * * The vnode lock acquired, thread 1 must not overwrite the attr cache's size
359 * with the old value.
361 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071
363 TEST_P(LastLocalModify, vfs_vget)
365 const char FULLPATH[] = "mountpoint/some_file.txt";
366 const char RELPATH[] = "some_file.txt";
369 uint64_t lookup_unique;
370 const uint64_t oldsize = 10;
371 const uint64_t newsize = 15;
381 GTEST_SKIP() << "This test requires a privileged user";
383 mutator = writer_from_str(GetParam());
384 mutator_op = fuse_op_from_mutator(mutator);
386 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
388 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
391 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
393 /* Called by getfh, caches attributes but not entries */
394 SET_OUT_HEADER_LEN(out, entry);
395 out.body.entry.nodeid = ino;
396 out.body.entry.attr.size = oldsize;
397 out.body.entry.nodeid = ino;
398 out.body.entry.attr_valid_nsec = NAP_NS / 2;
399 out.body.entry.attr.ino = ino;
400 out.body.entry.attr.mode = S_IFREG | 0644;
402 EXPECT_LOOKUP(ino, ".")
404 .WillOnce(Invoke([&](auto in, auto &out __unused) {
405 /* Called by fhstat. Block to simulate a race */
406 lookup_unique = in.header.unique;
410 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
413 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
415 /* Called by VOP_SETATTR, caches attributes but not entries */
416 SET_OUT_HEADER_LEN(out, entry);
417 out.body.entry.nodeid = ino;
418 out.body.entry.attr.size = oldsize;
419 out.body.entry.nodeid = ino;
420 out.body.entry.attr_valid_nsec = NAP_NS / 2;
421 out.body.entry.attr.ino = ino;
422 out.body.entry.attr.mode = S_IFREG | 0644;
425 /* Called by the mutator thread */
426 expect_open(ino, 0, 1);
428 EXPECT_CALL(*m_mock, process(
429 ResultOf([=](auto in) {
430 return (in.header.opcode == mutator_op &&
431 in.header.nodeid == ino);
435 .WillOnce(Invoke([&](auto in __unused, auto& out) {
436 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
437 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
439 /* First complete the lookup request, returning the old size */
440 out0->header.unique = lookup_unique;
441 SET_OUT_HEADER_LEN(*out0, entry);
442 out0->body.entry.attr.mode = S_IFREG | 0644;
443 out0->body.entry.nodeid = ino;
444 out0->body.entry.entry_valid = UINT64_MAX;
445 out0->body.entry.attr_valid = UINT64_MAX;
446 out0->body.entry.attr.size = oldsize;
447 out.push_back(std::move(out0));
449 /* Then, respond to the mutator request */
450 out1->header.unique = in.header.unique;
453 out1->header.error = 0;
454 out1->header.len = sizeof(out1->header);
456 case VOP_COPY_FILE_RANGE:
457 SET_OUT_HEADER_LEN(*out1, write);
458 out1->body.write.size = in.body.copy_file_range.len;
461 SET_OUT_HEADER_LEN(*out1, attr);
462 out1->body.attr.attr.ino = ino;
463 out1->body.attr.attr.mode = S_IFREG | 0644;
464 out1->body.attr.attr.size = newsize; // Changed size
465 out1->body.attr.attr_valid = UINT64_MAX;
468 SET_OUT_HEADER_LEN(*out1, write);
469 out1->body.write.size = in.body.write.size;
472 out.push_back(std::move(out1));
475 /* First get a file handle */
476 ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
478 /* Start the mutator thread */
481 ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
482 (void*)&sem)) << strerror(errno);
484 case VOP_COPY_FILE_RANGE:
485 ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
486 (void*)&sem)) << strerror(errno);
489 ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th,
490 (void*)&sem)) << strerror(errno);
493 ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem))
498 /* Lookup again, which will race with setattr */
499 ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
501 ASSERT_EQ((off_t)newsize, sb.st_size);
503 /* mutator should've completed without error */
504 pthread_join(th0, &thr0_value);
505 EXPECT_EQ(0, (intptr_t)thr0_value);
509 INSTANTIATE_TEST_CASE_P(LLM, LastLocalModify,
512 "VOP_COPY_FILE_RANGE",