]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/last_local_modify.cc
MFV: expat 2.5.0
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / last_local_modify.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2021 Alan Somers
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
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.
14  *
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
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29
30 extern "C" {
31 #include <sys/param.h>
32 #include <sys/mount.h>
33 #include <sys/stat.h>
34
35 #include <fcntl.h>
36 #include <pthread.h>
37 #include <semaphore.h>
38 }
39
40 #include "mockfs.hh"
41 #include "utils.hh"
42
43 using namespace testing;
44
45 /*
46  * "Last Local Modify" bugs
47  *
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
53  * thread.
54  *
55  * FUSE_GETATTR is not vulnerable to the same race, because it is always called
56  * with the vnode lock held.
57  *
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.
62  */
63
64 enum Mutator {
65         VOP_ALLOCATE,
66         VOP_COPY_FILE_RANGE,
67         VOP_SETATTR,
68         VOP_WRITE,
69 };
70
71 /*
72  * Translate a poll method's string representation to the enum value.
73  * Using strings with ::testing::Values gives better output with
74  * --gtest_list_tests
75  */
76 enum Mutator writer_from_str(const char* s) {
77         if (0 == strcmp("VOP_ALLOCATE", s))
78                 return VOP_ALLOCATE;
79         else if (0 == strcmp("VOP_COPY_FILE_RANGE", s))
80                 return VOP_COPY_FILE_RANGE;
81         else if (0 == strcmp("VOP_SETATTR", s))
82                 return VOP_SETATTR;
83         else
84                 return VOP_WRITE;
85 }
86
87 uint32_t fuse_op_from_mutator(enum Mutator mutator) {
88         switch(mutator) {
89         case VOP_ALLOCATE:
90                 return(FUSE_FALLOCATE);
91         case VOP_COPY_FILE_RANGE:
92                 return(FUSE_COPY_FILE_RANGE);
93         case VOP_SETATTR:
94                 return(FUSE_SETATTR);
95         case VOP_WRITE:
96                 return(FUSE_WRITE);
97         }
98 }
99
100 class LastLocalModify: public FuseTest, public WithParamInterface<const char*> {
101 public:
102 virtual void SetUp() {
103         m_init_flags = FUSE_EXPORT_SUPPORT;
104
105         FuseTest::SetUp();
106 }
107 };
108
109 static void* allocate_th(void* arg) {
110         int fd;
111         ssize_t r;
112         sem_t *sem = (sem_t*) arg;
113
114         if (sem)
115                 sem_wait(sem);
116
117         fd = open("mountpoint/some_file.txt", O_RDWR);
118         if (fd < 0)
119                 return (void*)(intptr_t)errno;
120
121         r = posix_fallocate(fd, 0, 15);
122         LastLocalModify::leak(fd);
123         if (r >= 0)
124                 return 0;
125         else
126                 return (void*)(intptr_t)errno;
127 }
128
129 static void* copy_file_range_th(void* arg) {
130         ssize_t r;
131         int fd;
132         sem_t *sem = (sem_t*) arg;
133         off_t off_in = 0;
134         off_t off_out = 10;
135         ssize_t len = 5;
136
137         if (sem)
138                 sem_wait(sem);
139         fd = open("mountpoint/some_file.txt", O_RDWR);
140         if (fd < 0)
141                 return (void*)(intptr_t)errno;
142
143         r = copy_file_range(fd, &off_in, fd, &off_out, len, 0);
144         if (r >= 0) {
145                 LastLocalModify::leak(fd);
146                 return 0;
147         } else
148                 return (void*)(intptr_t)errno;
149 }
150
151 static void* setattr_th(void* arg) {
152         int fd;
153         ssize_t r;
154         sem_t *sem = (sem_t*) arg;
155
156         if (sem)
157                 sem_wait(sem);
158
159         fd = open("mountpoint/some_file.txt", O_RDWR);
160         if (fd < 0)
161                 return (void*)(intptr_t)errno;
162
163         r = ftruncate(fd, 15);
164         LastLocalModify::leak(fd);
165         if (r >= 0)
166                 return 0;
167         else
168                 return (void*)(intptr_t)errno;
169 }
170
171 static void* write_th(void* arg) {
172         ssize_t r;
173         int fd;
174         sem_t *sem = (sem_t*) arg;
175         const char BUF[] = "abcdefghijklmn";
176
177         if (sem)
178                 sem_wait(sem);
179         fd = open("mountpoint/some_file.txt", O_RDWR);
180         if (fd < 0)
181                 return (void*)(intptr_t)errno;
182
183         r = write(fd, BUF, sizeof(BUF));
184         if (r >= 0) {
185                 LastLocalModify::leak(fd);
186                 return 0;
187         } else
188                 return (void*)(intptr_t)errno;
189 }
190
191 /*
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.
194  *
195  * Sequence of operations:
196  * * Thread 1 calls a mutator like ftruncate, which acquires the vnode lock
197  *   exclusively.
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
203  *   lock.
204  * * Thread 2 acquires the vnode lock.  At this point it must not add the
205  *   now-stale file size to the attribute cache.
206  *
207  * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071
208  */
209 TEST_P(LastLocalModify, lookup)
210 {
211         const char FULLPATH[] = "mountpoint/some_file.txt";
212         const char RELPATH[] = "some_file.txt";
213         Sequence seq;
214         uint64_t ino = 3;
215         uint64_t mutator_unique;
216         const uint64_t oldsize = 10;
217         const uint64_t newsize = 15;
218         pthread_t th0;
219         void *thr0_value;
220         struct stat sb;
221         static sem_t sem;
222         Mutator mutator;
223         uint32_t mutator_op;
224         size_t mutator_size;
225
226         mutator = writer_from_str(GetParam());
227         mutator_op = fuse_op_from_mutator(mutator);
228
229         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
230
231         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
232         .InSequence(seq)
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;
242         })));
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);
248                 }, Eq(true)),
249                 _)
250         ).InSequence(seq)
251         .WillOnce(Invoke([&](auto in, auto &out __unused) {
252                 /*
253                  * The mutator changes the file size, but in order to simulate
254                  * a race, don't reply.  Instead, just save the unique for
255                  * later.
256                  */
257                 mutator_unique = in.header.unique;
258                 switch(mutator) {
259                 case VOP_WRITE:
260                         mutator_size = in.body.write.size;
261                         break;
262                 case VOP_COPY_FILE_RANGE:
263                         mutator_size = in.body.copy_file_range.len;
264                         break;
265                 default:
266                         break;
267                 }
268                 /* Allow the lookup thread to proceed */
269                 sem_post(&sem);
270         }));
271         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
272         .InSequence(seq)
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);
276
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));
286
287                 /* Then, respond to the mutator request */
288                 out1->header.unique = mutator_unique;
289                 switch(mutator) {
290                 case VOP_ALLOCATE:
291                         out1->header.error = 0;
292                         out1->header.len = sizeof(out1->header);
293                         break;
294                 case VOP_COPY_FILE_RANGE:
295                         SET_OUT_HEADER_LEN(*out1, write);
296                         out1->body.write.size = mutator_size;
297                         break;
298                 case VOP_SETATTR:
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;
304                         break;
305                 case VOP_WRITE:
306                         SET_OUT_HEADER_LEN(*out1, write);
307                         out1->body.write.size = mutator_size;
308                         break;
309                 }
310                 out.push_back(std::move(out1));
311         }));
312
313         /* Start the mutator thread */
314         switch(mutator) {
315         case VOP_ALLOCATE:
316                 ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
317                         NULL)) << strerror(errno);
318                 break;
319         case VOP_COPY_FILE_RANGE:
320                 ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
321                         NULL)) << strerror(errno);
322                 break;
323         case VOP_SETATTR:
324                 ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th, NULL))
325                         << strerror(errno);
326                 break;
327         case VOP_WRITE:
328                 ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, NULL))
329                         << strerror(errno);
330                 break;
331         }
332
333
334         /* Wait for FUSE_SETATTR to be sent */
335         sem_wait(&sem);
336
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);
340
341         /* ftruncate should've completed without error */
342         pthread_join(th0, &thr0_value);
343         EXPECT_EQ(0, (intptr_t)thr0_value);
344 }
345
346 /*
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.
349  *
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.
360  *
361  * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259071
362  */
363 TEST_P(LastLocalModify, vfs_vget)
364 {
365         const char FULLPATH[] = "mountpoint/some_file.txt";
366         const char RELPATH[] = "some_file.txt";
367         Sequence seq;
368         uint64_t ino = 3;
369         uint64_t lookup_unique;
370         const uint64_t oldsize = 10;
371         const uint64_t newsize = 15;
372         pthread_t th0;
373         void *thr0_value;
374         struct stat sb;
375         static sem_t sem;
376         fhandle_t fhp;
377         Mutator mutator;
378         uint32_t mutator_op;
379
380         if (geteuid() != 0)
381                 GTEST_SKIP() << "This test requires a privileged user";
382
383         mutator = writer_from_str(GetParam());
384         mutator_op = fuse_op_from_mutator(mutator);
385
386         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
387
388         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
389         .Times(1)
390         .InSequence(seq)
391         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
392         {
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;
401         })));
402         EXPECT_LOOKUP(ino, ".")
403         .InSequence(seq)
404         .WillOnce(Invoke([&](auto in, auto &out __unused) {
405                 /* Called by fhstat.  Block to simulate a race */
406                 lookup_unique = in.header.unique;
407                 sem_post(&sem);
408         }));
409
410         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
411         .Times(1)
412         .InSequence(seq)
413         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
414         {
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;
423         })));
424
425         /* Called by the mutator thread */
426         expect_open(ino, 0, 1);
427
428         EXPECT_CALL(*m_mock, process(
429                 ResultOf([=](auto in) {
430                         return (in.header.opcode == mutator_op &&
431                                 in.header.nodeid == ino);
432                 }, Eq(true)),
433                 _)
434         ).InSequence(seq)
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);
438
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));
448
449                 /* Then, respond to the mutator request */
450                 out1->header.unique = in.header.unique;
451                 switch(mutator) {
452                 case VOP_ALLOCATE:
453                         out1->header.error = 0;
454                         out1->header.len = sizeof(out1->header);
455                         break;
456                 case VOP_COPY_FILE_RANGE:
457                         SET_OUT_HEADER_LEN(*out1, write);
458                         out1->body.write.size = in.body.copy_file_range.len;
459                         break;
460                 case VOP_SETATTR:
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;
466                         break;
467                 case VOP_WRITE:
468                         SET_OUT_HEADER_LEN(*out1, write);
469                         out1->body.write.size = in.body.write.size;
470                         break;
471                 }
472                 out.push_back(std::move(out1));
473         }));
474
475         /* First get a file handle */
476         ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
477
478         /* Start the mutator thread */
479         switch(mutator) {
480         case VOP_ALLOCATE:
481                 ASSERT_EQ(0, pthread_create(&th0, NULL, allocate_th,
482                         (void*)&sem)) << strerror(errno);
483                 break;
484         case VOP_COPY_FILE_RANGE:
485                 ASSERT_EQ(0, pthread_create(&th0, NULL, copy_file_range_th,
486                         (void*)&sem)) << strerror(errno);
487                 break;
488         case VOP_SETATTR:
489                 ASSERT_EQ(0, pthread_create(&th0, NULL, setattr_th,
490                         (void*)&sem)) << strerror(errno);
491                 break;
492         case VOP_WRITE:
493                 ASSERT_EQ(0, pthread_create(&th0, NULL, write_th, (void*)&sem))
494                         << strerror(errno);
495                 break;
496         }
497
498         /* Lookup again, which will race with setattr */
499         ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
500
501         ASSERT_EQ((off_t)newsize, sb.st_size);
502
503         /* mutator should've completed without error */
504         pthread_join(th0, &thr0_value);
505         EXPECT_EQ(0, (intptr_t)thr0_value);
506 }
507
508
509 INSTANTIATE_TEST_CASE_P(LLM, LastLocalModify,
510         Values(
511                 "VOP_ALLOCATE",
512                 "VOP_COPY_FILE_RANGE",
513                 "VOP_SETATTR",
514                 "VOP_WRITE"
515         )
516 );