1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2014 SUSE. All Rights Reserved.
4 * Copyright (c) 2018 CTERA Networks. All Rights Reserved.
5 *
6 * Started by Jan Kara <jack@suse.cz>
7 * Forked from fanotify06.c by Amir Goldstein <amir73il@gmail.com>
8 */
9
10 /*\
11 * [Description]
12 * Check that fanotify properly merges ignore mask of a mount mark
13 * with a mask of an inode mark on the same group. Unlike the
14 * prototype test fanotify06, do not use FAN_MODIFY event for the
15 * test mask, because it hides the bug.
16 */
17
18 /*
19 * This is a regression test for commit:
20 *
21 * 9bdda4e9cf2d fsnotify: fix ignore mask logic in fsnotify()
22 *
23 * Test case #16 is a regression test for commit:
24 *
25 * 2f02fd3fa13e fanotify: fix ignore mask logic for events on child...
26 *
27 * Test cases with 'ignored_onchild' are regression tests for commit
28 * (from v5.9, unlikely to be backported thus not in .tags):
29 *
30 * 497b0c5a7c06 fsnotify: send event to parent and child with single...
31 */
32
33 #define _GNU_SOURCE
34 #include "config.h"
35
36 #include <stdio.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 #include <errno.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <sys/mount.h>
44 #include <sys/syscall.h>
45 #include "tst_test.h"
46
47 #ifdef HAVE_SYS_FANOTIFY_H
48 #include "fanotify.h"
49
50 #define EVENT_MAX 1024
51 /* size of the event structure, not counting name */
52 #define EVENT_SIZE (sizeof (struct fanotify_event_metadata))
53 /* reasonable guess as to size of 1024 events */
54 #define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE)
55
56 static unsigned int fanotify_class[] = {
57 FAN_CLASS_PRE_CONTENT,
58 FAN_CLASS_CONTENT,
59 FAN_CLASS_NOTIF,
60 /* Reporting dfid+name+fid merges events similar to reporting fd */
61 FAN_REPORT_DFID_NAME_FID,
62 };
63 #define NUM_CLASSES ARRAY_SIZE(fanotify_class)
64 #define NUM_PRIORITIES 3
65
66 #define GROUPS_PER_PRIO 3
67
68 static int fd_notify[NUM_CLASSES][GROUPS_PER_PRIO];
69
70 static char event_buf[EVENT_BUF_LEN];
71 static int exec_events_unsupported;
72 static int fan_report_dfid_unsupported;
73 static int filesystem_mark_unsupported;
74
75 #define MOUNT_PATH "fs_mnt"
76 #define MNT2_PATH "mntpoint"
77 #define FILE_NAME "testfile"
78 #define FILE2_NAME "testfile2"
79 #define TEST_APP "fanotify_child"
80 #define TEST_APP2 "fanotify_child2"
81 #define FILE_PATH MOUNT_PATH"/"FILE_NAME
82 #define FILE2_PATH MOUNT_PATH"/"FILE2_NAME
83 #define FILE_EXEC_PATH MOUNT_PATH"/"TEST_APP
84 #define FILE2_EXEC_PATH MOUNT_PATH"/"TEST_APP2
85 #define FILE_MNT2 MNT2_PATH"/"FILE_NAME
86 #define FILE2_MNT2 MNT2_PATH"/"FILE2_NAME
87 #define FILE_EXEC_PATH2 MNT2_PATH"/"TEST_APP
88 #define FILE2_EXEC_PATH2 MNT2_PATH"/"TEST_APP2
89
90 static pid_t child_pid;
91 static int bind_mount_created;
92 static unsigned int num_classes = NUM_CLASSES;
93
94 enum {
95 FANOTIFY_INODE,
96 FANOTIFY_MOUNT,
97 FANOTIFY_FILESYSTEM,
98 };
99
100 static struct fanotify_mark_type fanotify_mark_types[] = {
101 INIT_FANOTIFY_MARK_TYPE(INODE),
102 INIT_FANOTIFY_MARK_TYPE(MOUNT),
103 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
104 };
105
106 static struct tcase {
107 const char *tname;
108 const char *mark_path;
109 int mark_type;
110 const char *ignore_path;
111 int ignore_mark_type;
112 unsigned int ignored_onchild;
113 const char *event_path;
114 unsigned long long expected_mask_with_ignore;
115 unsigned long long expected_mask_without_ignore;
116 } tcases[] = {
117 {
118 "ignore mount events created on a specific file",
119 MOUNT_PATH, FANOTIFY_MOUNT,
120 FILE_MNT2, FANOTIFY_INODE,
121 0,
122 FILE_PATH, 0, FAN_OPEN
123 },
124 {
125 "ignore exec mount events created on a specific file",
126 MOUNT_PATH, FANOTIFY_MOUNT,
127 FILE_EXEC_PATH2, FANOTIFY_INODE,
128 0,
129 FILE_EXEC_PATH, FAN_OPEN_EXEC, FAN_OPEN | FAN_OPEN_EXEC
130 },
131 {
132 "don't ignore mount events created on another file",
133 MOUNT_PATH, FANOTIFY_MOUNT,
134 FILE_PATH, FANOTIFY_INODE,
135 0,
136 FILE2_PATH, FAN_OPEN, FAN_OPEN
137 },
138 {
139 "don't ignore exec mount events created on another file",
140 MOUNT_PATH, FANOTIFY_MOUNT,
141 FILE_EXEC_PATH, FANOTIFY_INODE,
142 0,
143 FILE2_EXEC_PATH, FAN_OPEN | FAN_OPEN_EXEC,
144 FAN_OPEN | FAN_OPEN_EXEC
145 },
146 {
147 "ignore inode events created on a specific mount point",
148 FILE_PATH, FANOTIFY_INODE,
149 MNT2_PATH, FANOTIFY_MOUNT,
150 0,
151 FILE_MNT2, 0, FAN_OPEN
152 },
153 {
154 "ignore exec inode events created on a specific mount point",
155 FILE_EXEC_PATH, FANOTIFY_INODE,
156 MNT2_PATH, FANOTIFY_MOUNT,
157 0,
158 FILE_EXEC_PATH2, FAN_OPEN_EXEC, FAN_OPEN | FAN_OPEN_EXEC
159 },
160 {
161 "don't ignore inode events created on another mount point",
162 FILE_MNT2, FANOTIFY_INODE,
163 MNT2_PATH, FANOTIFY_MOUNT,
164 0,
165 FILE_PATH, FAN_OPEN, FAN_OPEN
166 },
167 {
168 "don't ignore exec inode events created on another mount point",
169 FILE_EXEC_PATH2, FANOTIFY_INODE,
170 MNT2_PATH, FANOTIFY_MOUNT,
171 0,
172 FILE_EXEC_PATH, FAN_OPEN | FAN_OPEN_EXEC,
173 FAN_OPEN | FAN_OPEN_EXEC
174 },
175 {
176 "ignore fs events created on a specific file",
177 MOUNT_PATH, FANOTIFY_FILESYSTEM,
178 FILE_PATH, FANOTIFY_INODE,
179 0,
180 FILE_PATH, 0, FAN_OPEN
181 },
182 {
183 "ignore exec fs events created on a specific file",
184 MOUNT_PATH, FANOTIFY_FILESYSTEM,
185 FILE_EXEC_PATH, FANOTIFY_INODE,
186 0,
187 FILE_EXEC_PATH, FAN_OPEN_EXEC, FAN_OPEN | FAN_OPEN_EXEC
188 },
189 {
190 "don't ignore mount events created on another file",
191 MOUNT_PATH, FANOTIFY_FILESYSTEM,
192 FILE_PATH, FANOTIFY_INODE,
193 0,
194 FILE2_PATH, FAN_OPEN, FAN_OPEN
195 },
196 {
197 "don't ignore exec mount events created on another file",
198 MOUNT_PATH, FANOTIFY_FILESYSTEM,
199 FILE_EXEC_PATH, FANOTIFY_INODE,
200 0,
201 FILE2_EXEC_PATH, FAN_OPEN | FAN_OPEN_EXEC,
202 FAN_OPEN | FAN_OPEN_EXEC
203 },
204 {
205 "ignore fs events created on a specific mount point",
206 MOUNT_PATH, FANOTIFY_FILESYSTEM,
207 MNT2_PATH, FANOTIFY_MOUNT,
208 0,
209 FILE_MNT2, 0, FAN_OPEN
210 },
211 {
212 "ignore exec fs events created on a specific mount point",
213 MOUNT_PATH, FANOTIFY_FILESYSTEM,
214 MNT2_PATH, FANOTIFY_MOUNT,
215 0,
216 FILE_EXEC_PATH2, FAN_OPEN_EXEC, FAN_OPEN | FAN_OPEN_EXEC
217 },
218 {
219 "don't ignore fs events created on another mount point",
220 MOUNT_PATH, FANOTIFY_FILESYSTEM,
221 MNT2_PATH, FANOTIFY_MOUNT,
222 0,
223 FILE_PATH, FAN_OPEN, FAN_OPEN
224 },
225 {
226 "don't ignore exec fs events created on another mount point",
227 MOUNT_PATH, FANOTIFY_FILESYSTEM,
228 MNT2_PATH, FANOTIFY_MOUNT,
229 0,
230 FILE_EXEC_PATH, FAN_OPEN | FAN_OPEN_EXEC,
231 FAN_OPEN | FAN_OPEN_EXEC
232 },
233 {
234 "ignore child exec events created on a specific mount point",
235 MOUNT_PATH, FANOTIFY_INODE,
236 MOUNT_PATH, FANOTIFY_MOUNT,
237 0,
238 FILE_EXEC_PATH, FAN_OPEN_EXEC, FAN_OPEN | FAN_OPEN_EXEC
239 },
240 {
241 "ignore events on children of directory created on a specific file",
242 MNT2_PATH, FANOTIFY_INODE,
243 FILE_PATH, FANOTIFY_INODE,
244 FAN_EVENT_ON_CHILD,
245 FILE_PATH, 0, FAN_OPEN
246 },
247 {
248 "ignore events on file created inside a parent watching children",
249 FILE_PATH, FANOTIFY_INODE,
250 MNT2_PATH, FANOTIFY_INODE,
251 FAN_EVENT_ON_CHILD,
252 FILE_PATH, 0, FAN_OPEN
253 },
254 {
255 "don't ignore events on file created inside a parent not watching children",
256 FILE_PATH, FANOTIFY_INODE,
257 MNT2_PATH, FANOTIFY_INODE,
258 0,
259 FILE_PATH, FAN_OPEN, FAN_OPEN
260 },
261 {
262 "ignore mount events created inside a parent watching children",
263 FILE_PATH, FANOTIFY_MOUNT,
264 MNT2_PATH, FANOTIFY_INODE,
265 FAN_EVENT_ON_CHILD,
266 FILE_PATH, 0, FAN_OPEN
267 },
268 {
269 "don't ignore mount events created inside a parent not watching children",
270 FILE_PATH, FANOTIFY_MOUNT,
271 MNT2_PATH, FANOTIFY_INODE,
272 0,
273 FILE_PATH, FAN_OPEN, FAN_OPEN
274 },
275 {
276 "ignore fs events created inside a parent watching children",
277 FILE_PATH, FANOTIFY_FILESYSTEM,
278 MNT2_PATH, FANOTIFY_INODE,
279 FAN_EVENT_ON_CHILD,
280 FILE_PATH, 0, FAN_OPEN
281 },
282 {
283 "don't ignore fs events created inside a parent not watching children",
284 FILE_PATH, FANOTIFY_FILESYSTEM,
285 MNT2_PATH, FANOTIFY_INODE,
286 0,
287 FILE_PATH, FAN_OPEN, FAN_OPEN
288 },
289 };
290
create_fanotify_groups(unsigned int n)291 static int create_fanotify_groups(unsigned int n)
292 {
293 struct tcase *tc = &tcases[n];
294 struct fanotify_mark_type *mark, *ignore_mark;
295 unsigned int mark_ignored, mask;
296 unsigned int p, i;
297
298 mark = &fanotify_mark_types[tc->mark_type];
299 ignore_mark = &fanotify_mark_types[tc->ignore_mark_type];
300
301 for (p = 0; p < num_classes; p++) {
302 for (i = 0; i < GROUPS_PER_PRIO; i++) {
303 fd_notify[p][i] = SAFE_FANOTIFY_INIT(fanotify_class[p] |
304 FAN_NONBLOCK, O_RDONLY);
305
306 /*
307 * Add mark for each group.
308 *
309 * FAN_EVENT_ON_CHILD has no effect on filesystem/mount
310 * or inode mark on non-directory.
311 */
312 SAFE_FANOTIFY_MARK(fd_notify[p][i],
313 FAN_MARK_ADD | mark->flag,
314 tc->expected_mask_without_ignore |
315 FAN_EVENT_ON_CHILD,
316 AT_FDCWD, tc->mark_path);
317
318 /* Add ignore mark for groups with higher priority */
319 if (p == 0)
320 continue;
321
322 mask = FAN_OPEN;
323 mark_ignored = FAN_MARK_IGNORED_MASK |
324 FAN_MARK_IGNORED_SURV_MODIFY;
325 add_mark:
326 SAFE_FANOTIFY_MARK(fd_notify[p][i],
327 FAN_MARK_ADD | ignore_mark->flag | mark_ignored,
328 mask, AT_FDCWD, tc->ignore_path);
329
330 /*
331 * If ignored mask is on a parent watching children,
332 * also set the flag FAN_EVENT_ON_CHILD in mark mask.
333 * This is needed to indicate that parent ignored mask
334 * should be applied to events on children.
335 */
336 if (tc->ignored_onchild && mark_ignored) {
337 mask = tc->ignored_onchild;
338 /* XXX: temporary hack may be removed in the future */
339 mask |= FAN_OPEN;
340 mark_ignored = 0;
341 goto add_mark;
342 }
343 }
344 }
345 return 0;
346 }
347
cleanup_fanotify_groups(void)348 static void cleanup_fanotify_groups(void)
349 {
350 unsigned int i, p;
351
352 for (p = 0; p < num_classes; p++) {
353 for (i = 0; i < GROUPS_PER_PRIO; i++) {
354 if (fd_notify[p][i] > 0)
355 SAFE_CLOSE(fd_notify[p][i]);
356 }
357 }
358 }
359
verify_event(int p,int group,struct fanotify_event_metadata * event,unsigned long long expected_mask)360 static void verify_event(int p, int group, struct fanotify_event_metadata *event,
361 unsigned long long expected_mask)
362 {
363 if (event->mask != expected_mask) {
364 tst_res(TFAIL, "group %d (%x) got event: mask %llx (expected %llx) "
365 "pid=%u fd=%u", group, fanotify_class[p],
366 (unsigned long long) event->mask,
367 (unsigned long long) expected_mask,
368 (unsigned)event->pid, event->fd);
369 } else if (event->pid != child_pid) {
370 tst_res(TFAIL, "group %d (%x) got event: mask %llx pid=%u "
371 "(expected %u) fd=%u", group, fanotify_class[p],
372 (unsigned long long)event->mask, (unsigned)event->pid,
373 (unsigned)getpid(), event->fd);
374 } else {
375 tst_res(TPASS, "group %d (%x) got event: mask %llx pid=%u fd=%u",
376 group, fanotify_class[p], (unsigned long long)event->mask,
377 (unsigned)event->pid, event->fd);
378 }
379 }
380
generate_event(const char * event_path,unsigned long long expected_mask)381 static int generate_event(const char *event_path,
382 unsigned long long expected_mask)
383 {
384 int fd, status;
385
386 child_pid = SAFE_FORK();
387
388 if (child_pid == 0) {
389 if (expected_mask & FAN_OPEN_EXEC) {
390 SAFE_EXECL(event_path, event_path, NULL);
391 } else {
392 fd = SAFE_OPEN(event_path, O_RDONLY);
393
394 if (fd > 0)
395 SAFE_CLOSE(fd);
396 }
397
398 exit(0);
399 }
400
401 SAFE_WAITPID(child_pid, &status, 0);
402
403 if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
404 return 1;
405 return 0;
406 }
407
test_fanotify(unsigned int n)408 static void test_fanotify(unsigned int n)
409 {
410 struct tcase *tc = &tcases[n];
411 struct fanotify_mark_type *mark, *ignore_mark;
412 int ret;
413 unsigned int p, i;
414 struct fanotify_event_metadata *event;
415
416 tst_res(TINFO, "Test #%d: %s", n, tc->tname);
417
418 if (exec_events_unsupported && tc->expected_mask_with_ignore & FAN_OPEN_EXEC) {
419 tst_res(TCONF, "FAN_OPEN_EXEC not supported in kernel?");
420 return;
421 }
422
423 if (filesystem_mark_unsupported && tc->mark_type == FANOTIFY_FILESYSTEM) {
424 tst_res(TCONF, "FAN_MARK_FILESYSTEM not supported in kernel?");
425 return;
426 }
427
428 if (tc->ignored_onchild && tst_kvercmp(5, 9, 0) < 0) {
429 tst_res(TCONF, "ignored mask in combination with flag FAN_EVENT_ON_CHILD"
430 " has undefined behavior on kernel < 5.9");
431 return;
432 }
433
434 if (create_fanotify_groups(n) != 0)
435 goto cleanup;
436
437 mark = &fanotify_mark_types[tc->mark_type];
438 ignore_mark = &fanotify_mark_types[tc->ignore_mark_type];
439
440 /* Generate event in child process */
441 if (!generate_event(tc->event_path, tc->expected_mask_with_ignore))
442 tst_brk(TBROK, "Child process terminated incorrectly");
443
444 /* First verify all groups without matching ignore mask got the event */
445 for (p = 0; p < num_classes; p++) {
446 if (p > 0 && !tc->expected_mask_with_ignore)
447 break;
448
449 for (i = 0; i < GROUPS_PER_PRIO; i++) {
450 ret = read(fd_notify[p][i], event_buf, EVENT_BUF_LEN);
451 if (ret < 0) {
452 if (errno == EAGAIN) {
453 tst_res(TFAIL, "group %d (%x) "
454 "with %s did not get event",
455 i, fanotify_class[p], mark->name);
456 continue;
457 }
458 tst_brk(TBROK | TERRNO,
459 "reading fanotify events failed");
460 }
461 if (ret < (int)FAN_EVENT_METADATA_LEN) {
462 tst_brk(TBROK,
463 "short read when reading fanotify "
464 "events (%d < %d)", ret,
465 (int)EVENT_BUF_LEN);
466 }
467 event = (struct fanotify_event_metadata *)event_buf;
468 if (ret > (int)event->event_len) {
469 tst_res(TFAIL, "group %d (%x) with %s "
470 "got more than one event (%d > %d)",
471 i, fanotify_class[p], mark->name, ret,
472 event->event_len);
473 } else {
474 verify_event(p, i, event, p == 0 ?
475 tc->expected_mask_without_ignore :
476 tc->expected_mask_with_ignore);
477 }
478 if (event->fd != FAN_NOFD)
479 SAFE_CLOSE(event->fd);
480 }
481 }
482 /* Then verify all groups with matching ignore mask did got the event */
483 for (p = 1; p < num_classes && !tc->expected_mask_with_ignore; p++) {
484 for (i = 0; i < GROUPS_PER_PRIO; i++) {
485 ret = read(fd_notify[p][i], event_buf, EVENT_BUF_LEN);
486 if (ret == 0) {
487 tst_brk(TBROK,
488 "zero length read from fanotify fd");
489 }
490 if (ret > 0) {
491 tst_res(TFAIL, "group %d (%x) with %s and "
492 "%s ignore mask got event",
493 i, fanotify_class[p], mark->name, ignore_mark->name);
494 if (event->fd != FAN_NOFD)
495 SAFE_CLOSE(event->fd);
496 } else if (errno == EAGAIN) {
497 tst_res(TPASS, "group %d (%x) with %s and "
498 "%s ignore mask got no event",
499 i, fanotify_class[p], mark->name, ignore_mark->name);
500 } else {
501 tst_brk(TBROK | TERRNO,
502 "reading fanotify events failed");
503 }
504 }
505 }
506 cleanup:
507 cleanup_fanotify_groups();
508 }
509
setup(void)510 static void setup(void)
511 {
512 exec_events_unsupported = fanotify_events_supported_by_kernel(FAN_OPEN_EXEC,
513 FAN_CLASS_CONTENT, 0);
514 filesystem_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_FILESYSTEM);
515 fan_report_dfid_unsupported = fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME,
516 MOUNT_PATH);
517 if (fan_report_dfid_unsupported) {
518 FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_DFID_NAME, fan_report_dfid_unsupported);
519 /* Limit tests to legacy priority classes */
520 num_classes = NUM_PRIORITIES;
521 }
522
523 /* Create another bind mount at another path for generating events */
524 SAFE_MKDIR(MNT2_PATH, 0755);
525 SAFE_MOUNT(MOUNT_PATH, MNT2_PATH, "none", MS_BIND, NULL);
526 bind_mount_created = 1;
527
528 SAFE_FILE_PRINTF(FILE_PATH, "1");
529 SAFE_FILE_PRINTF(FILE2_PATH, "1");
530
531 SAFE_CP(TEST_APP, FILE_EXEC_PATH);
532 SAFE_CP(TEST_APP, FILE2_EXEC_PATH);
533 }
534
cleanup(void)535 static void cleanup(void)
536 {
537 cleanup_fanotify_groups();
538
539 if (bind_mount_created && tst_umount(MNT2_PATH) < 0)
540 tst_brk(TBROK | TERRNO, "bind umount failed");
541 }
542
543 static const char *const resource_files[] = {
544 TEST_APP,
545 NULL
546 };
547
548 static struct tst_test test = {
549 .test = test_fanotify,
550 .tcnt = ARRAY_SIZE(tcases),
551 .setup = setup,
552 .cleanup = cleanup,
553 .mount_device = 1,
554 .mntpoint = MOUNT_PATH,
555 .needs_root = 1,
556 .forks_child = 1,
557 .resource_files = resource_files,
558 .tags = (const struct tst_tag[]) {
559 {"linux-git", "9bdda4e9cf2d"},
560 {"linux-git", "2f02fd3fa13e"},
561 {}
562 }
563 };
564
565 #else
566 TST_TEST_TCONF("system doesn't have required fanotify support");
567 #endif
568