• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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