1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2018 CTERA Networks. All Rights Reserved.
4 *
5 * Started by Amir Goldstein <amir73il@gmail.com>
6 */
7
8 /*\
9 * [Description]
10 * Check that fanotify handles events on children correctly when both parent and
11 * subdir or mountpoint marks exist.
12 */
13
14 /*
15 * This is a regression test for commit:
16 *
17 * 54a307ba8d3c fanotify: fix logic of events on child
18 *
19 * Test case #1 is a regression test for commit:
20 *
21 * b469e7e47c8a fanotify: fix handling of events on child sub-directory
22 *
23 * Test case #2 is a regression test for commit:
24 *
25 * 55bf882c7f13 fanotify: fix merging marks masks with FAN_ONDIR
26 *
27 * Test case #5 is a regression test for commit:
28 *
29 * 7372e79c9eb9 fanotify: fix logic of reporting name info with watched parent
30 *
31 * Test cases #6-#7 are regression tests for commit:
32 * (from v5.19, unlikely to be backported thus not in .tags):
33 *
34 * e730558adffb fanotify: consistent behavior for parent not watching children
35 */
36
37 #define _GNU_SOURCE
38 #include "config.h"
39
40 #include <stdio.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <sys/mount.h>
46 #include <sys/syscall.h>
47 #include <stdint.h>
48 #include "tst_test.h"
49
50 #ifdef HAVE_SYS_FANOTIFY_H
51 #include "fanotify.h"
52
53 #define EVENT_MAX 1024
54 /* size of the event structure, not counting name */
55 #define EVENT_SIZE (sizeof(struct fanotify_event_metadata))
56 /* reasonable guess as to size of 1024 events */
57 #define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE)
58
59 #define NUM_GROUPS 3
60
61 #define BUF_SIZE 256
62 static char fname[BUF_SIZE];
63 static char symlnk[BUF_SIZE];
64 static char fdpath[BUF_SIZE];
65 static int fd_notify[NUM_GROUPS];
66
67 static char event_buf[EVENT_BUF_LEN];
68
69 #define MOUNT_PATH "fs_mnt"
70 #define MOUNT_NAME "mntpoint"
71 #define DIR_NAME "testdir"
72 #define FILE2_NAME "testfile"
73 static int mount_created;
74
75 static int fan_report_dfid_unsupported;
76 static int ignore_mark_unsupported;
77
78 static struct tcase {
79 const char *tname;
80 struct fanotify_mark_type mark;
81 unsigned int ondir;
82 unsigned int ignore;
83 unsigned int ignore_flags;
84 unsigned int report_name;
85 const char *event_path;
86 int nevents;
87 unsigned int nonfirst_event;
88 } tcases[] = {
89 {
90 .tname = "Events on non-dir child with both parent and mount marks",
91 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
92 .event_path = DIR_NAME,
93 .nevents = 1,
94 },
95 {
96 .tname = "Events on non-dir child and subdir with both parent and mount marks",
97 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
98 .ondir = FAN_ONDIR,
99 .event_path = DIR_NAME,
100 .nevents = 2,
101 },
102 {
103 .tname = "Events on non-dir child and parent with both parent and mount marks",
104 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
105 .ondir = FAN_ONDIR,
106 .event_path = ".",
107 .nevents = 2,
108 },
109 {
110 .tname = "Events on non-dir child and subdir with both parent and subdir marks",
111 .mark = INIT_FANOTIFY_MARK_TYPE(INODE),
112 .ondir = FAN_ONDIR,
113 .event_path = DIR_NAME,
114 .nevents = 2,
115 },
116 {
117 .tname = "Events on non-dir children with both parent and mount marks",
118 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
119 .event_path = FILE2_NAME,
120 .nevents = 2,
121 .nonfirst_event = FAN_CLOSE_NOWRITE,
122 },
123 {
124 .tname = "Events on non-dir child with both parent and mount marks and filename info",
125 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
126 .report_name = FAN_REPORT_DFID_NAME,
127 .event_path = FILE2_NAME,
128 .nevents = 2,
129 .nonfirst_event = FAN_CLOSE_NOWRITE,
130 },
131 {
132 .tname = "Events on non-dir child with ignore mask on parent",
133 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
134 .ignore = FAN_MARK_IGNORED_MASK,
135 .event_path = DIR_NAME,
136 .nevents = 1,
137 },
138 {
139 .tname = "Events on non-dir children with surviving ignore mask on parent",
140 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
141 .ignore = FAN_MARK_IGNORED_MASK | FAN_MARK_IGNORED_SURV_MODIFY,
142 .event_path = FILE2_NAME,
143 .nevents = 2,
144 .nonfirst_event = FAN_CLOSE_NOWRITE,
145 },
146 /* FAN_MARK_IGNORE test cases: */
147 {
148 .tname = "Events on dir with ignore mask that does not apply to dirs",
149 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
150 .ondir = FAN_ONDIR,
151 .ignore = FAN_MARK_IGNORE_SURV,
152 .event_path = ".",
153 .nevents = 2,
154 .nonfirst_event = FAN_CLOSE_NOWRITE,
155 },
156 {
157 .tname = "Events on dir with ignore mask that does apply to dirs",
158 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
159 .ondir = FAN_ONDIR,
160 .ignore = FAN_MARK_IGNORE_SURV,
161 .ignore_flags = FAN_ONDIR,
162 .event_path = ".",
163 .nevents = 2,
164 },
165 {
166 .tname = "Events on child with ignore mask on parent that does not apply to children",
167 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
168 .ignore = FAN_MARK_IGNORE_SURV,
169 .event_path = FILE2_NAME,
170 .nevents = 2,
171 .nonfirst_event = FAN_CLOSE_NOWRITE,
172 },
173 {
174 .tname = "Events on child with ignore mask on parent that does apply to children",
175 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
176 .ignore = FAN_MARK_IGNORE_SURV,
177 .ignore_flags = FAN_EVENT_ON_CHILD,
178 .event_path = FILE2_NAME,
179 .nevents = 2,
180 },
181 {
182 .tname = "Events on subdir with ignore mask on parent that does not apply to children",
183 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
184 .ondir = FAN_ONDIR,
185 .ignore = FAN_MARK_IGNORE_SURV,
186 .ignore_flags = FAN_ONDIR,
187 .event_path = DIR_NAME,
188 .nevents = 2,
189 .nonfirst_event = FAN_CLOSE_NOWRITE,
190 },
191 {
192 .tname = "Events on subdir with ignore mask on parent that does not apply to dirs",
193 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
194 .ondir = FAN_ONDIR,
195 .ignore = FAN_MARK_IGNORE_SURV,
196 .ignore_flags = FAN_EVENT_ON_CHILD,
197 .event_path = DIR_NAME,
198 .nevents = 2,
199 .nonfirst_event = FAN_CLOSE_NOWRITE,
200 },
201 {
202 .tname = "Events on subdir with ignore mask on parent that does apply to subdirs",
203 .mark = INIT_FANOTIFY_MARK_TYPE(MOUNT),
204 .ondir = FAN_ONDIR,
205 .ignore = FAN_MARK_IGNORE_SURV,
206 .ignore_flags = FAN_EVENT_ON_CHILD | FAN_ONDIR,
207 .event_path = DIR_NAME,
208 .nevents = 2,
209 },
210 };
211
create_fanotify_groups(struct tcase * tc)212 static void create_fanotify_groups(struct tcase *tc)
213 {
214 struct fanotify_mark_type *mark = &tc->mark;
215 int i;
216
217 for (i = 0; i < NUM_GROUPS; i++) {
218 /*
219 * The first group may request events with filename info and
220 * events on subdirs and always request events on children.
221 */
222 unsigned int report_name = tc->report_name;
223 unsigned int mask_flags = tc->ondir | FAN_EVENT_ON_CHILD;
224 unsigned int parent_mask, ignore_mask, ignore = 0;
225
226 /*
227 * The non-first groups may request events on children and
228 * subdirs only when setting an ignore mask on parent dir.
229 * The parent ignore mask may request to ignore events on
230 * children or subdirs.
231 */
232 if (i > 0) {
233 ignore = tc->ignore;
234 report_name = 0;
235 if (!ignore)
236 mask_flags = 0;
237 }
238
239 fd_notify[i] = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | report_name |
240 FAN_NONBLOCK, O_RDONLY);
241
242 /*
243 * Add subdir or mount mark for each group with CLOSE event,
244 * but only the first group requests events on dir.
245 */
246 SAFE_FANOTIFY_MARK(fd_notify[i],
247 FAN_MARK_ADD | mark->flag,
248 FAN_CLOSE_NOWRITE | mask_flags,
249 AT_FDCWD, tc->event_path);
250
251 /*
252 * Add inode mark on parent for each group with MODIFY event,
253 * but only the first group requests events on child.
254 * The one mark with FAN_EVENT_ON_CHILD is needed for
255 * setting the DCACHE_FSNOTIFY_PARENT_WATCHED dentry flag.
256 *
257 * The inode mark on non-first group is either with FAN_MODIFY
258 * in mask or FAN_CLOSE_NOWRITE in ignore mask. In either case,
259 * it is not expected to get the modify event on a child, nor
260 * the close event on dir.
261 */
262 parent_mask = FAN_MODIFY | tc->ondir | mask_flags;
263 ignore_mask = FAN_CLOSE_NOWRITE | tc->ignore_flags;
264 SAFE_FANOTIFY_MARK(fd_notify[i], FAN_MARK_ADD | ignore,
265 ignore ? ignore_mask : parent_mask,
266 AT_FDCWD, ".");
267 }
268 }
269
cleanup_fanotify_groups(void)270 static void cleanup_fanotify_groups(void)
271 {
272 unsigned int i;
273
274 for (i = 0; i < NUM_GROUPS; i++) {
275 if (fd_notify[i] > 0)
276 SAFE_CLOSE(fd_notify[i]);
277 }
278 }
279
check_ignore_mask(int fd)280 static void check_ignore_mask(int fd)
281 {
282 unsigned int ignored_mask, mflags;
283 char procfdinfo[100];
284
285 sprintf(procfdinfo, "/proc/%d/fdinfo/%d", (int)getpid(), fd);
286 if (FILE_LINES_SCANF(procfdinfo, "fanotify ino:%*x sdev:%*x mflags: %x mask:0 ignored_mask:%x",
287 &mflags, &ignored_mask) || !ignored_mask) {
288 tst_res(TFAIL, "The ignore mask did not survive");
289 } else {
290 tst_res(TPASS, "Found mark with ignore mask (ignored_mask=%x, mflags=%x) in %s",
291 ignored_mask, mflags, procfdinfo);
292 }
293 }
294
event_res(int ttype,int group,struct fanotify_event_metadata * event,const char * filename)295 static void event_res(int ttype, int group,
296 struct fanotify_event_metadata *event,
297 const char *filename)
298 {
299 if (event->fd != FAN_NOFD) {
300 int len = 0;
301
302 sprintf(symlnk, "/proc/self/fd/%d", event->fd);
303 len = readlink(symlnk, fdpath, sizeof(fdpath));
304 if (len < 0)
305 len = 0;
306 fdpath[len] = 0;
307 filename = fdpath;
308 }
309
310 tst_res(ttype, "group %d got event: mask %llx pid=%u fd=%d filename=%s",
311 group, (unsigned long long)event->mask,
312 (unsigned int)event->pid, event->fd, filename);
313 }
314
event_filename(struct fanotify_event_metadata * event)315 static const char *event_filename(struct fanotify_event_metadata *event)
316 {
317 struct fanotify_event_info_fid *event_fid;
318 struct file_handle *file_handle;
319 const char *filename, *end;
320
321 if (event->event_len <= FAN_EVENT_METADATA_LEN)
322 return "";
323
324 event_fid = (struct fanotify_event_info_fid *)(event + 1);
325 file_handle = (struct file_handle *)event_fid->handle;
326 filename = (char *)file_handle->f_handle + file_handle->handle_bytes;
327 end = (char *)event_fid + event_fid->hdr.len;
328
329 /* End of event_fid could have name, zero padding, both or none */
330 return (filename == end) ? "" : filename;
331 }
332
verify_event(int group,struct fanotify_event_metadata * event,uint32_t expect,const char * expect_filename)333 static void verify_event(int group, struct fanotify_event_metadata *event,
334 uint32_t expect, const char *expect_filename)
335 {
336 const char *filename = event_filename(event);
337
338 if (event->mask != expect) {
339 tst_res(TFAIL, "group %d got event: mask %llx (expected %llx) "
340 "pid=%u fd=%d filename=%s", group, (unsigned long long)event->mask,
341 (unsigned long long)expect,
342 (unsigned int)event->pid, event->fd, filename);
343 } else if (event->pid != getpid()) {
344 tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
345 "(expected %u) fd=%d filename=%s", group,
346 (unsigned long long)event->mask, (unsigned int)event->pid,
347 (unsigned int)getpid(), event->fd, filename);
348 } else if (strcmp(filename, expect_filename)) {
349 tst_res(TFAIL, "group %d got event: mask %llx pid=%u "
350 "fd=%d filename='%s' (expected '%s')", group,
351 (unsigned long long)event->mask, (unsigned int)event->pid,
352 event->fd, filename, expect_filename);
353 } else {
354 event_res(TPASS, group, event, filename);
355 }
356 if (event->fd != FAN_NOFD)
357 SAFE_CLOSE(event->fd);
358 }
359
close_event_fds(struct fanotify_event_metadata * event,int buflen)360 static void close_event_fds(struct fanotify_event_metadata *event, int buflen)
361 {
362 /* Close all file descriptors of read events */
363 for (; FAN_EVENT_OK(event, buflen); FAN_EVENT_NEXT(event, buflen)) {
364 if (event->fd != FAN_NOFD)
365 SAFE_CLOSE(event->fd);
366 }
367 }
368
test_fanotify(unsigned int n)369 static void test_fanotify(unsigned int n)
370 {
371 int ret, dirfd;
372 unsigned int i;
373 struct fanotify_event_metadata *event;
374 struct tcase *tc = &tcases[n];
375
376 tst_res(TINFO, "Test #%d: %s", n, tc->tname);
377
378 if (fan_report_dfid_unsupported && tc->report_name) {
379 FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_DFID_NAME, fan_report_dfid_unsupported);
380 return;
381 }
382
383 if (tc->ignore && tst_kvercmp(5, 19, 0) < 0) {
384 tst_res(TCONF, "ignored mask on parent dir has undefined "
385 "behavior on kernel < 5.19");
386 return;
387 }
388
389 if (ignore_mark_unsupported && tc->ignore & FAN_MARK_IGNORE) {
390 tst_res(TCONF, "FAN_MARK_IGNORE not supported in kernel?");
391 return;
392 }
393
394 create_fanotify_groups(tc);
395
396 /*
397 * generate MODIFY event and no FAN_CLOSE_NOWRITE event.
398 */
399 SAFE_FILE_PRINTF(fname, "1");
400 /*
401 * generate FAN_CLOSE_NOWRITE event on a child, subdir or "."
402 */
403 dirfd = SAFE_OPEN(tc->event_path, O_RDONLY);
404 SAFE_CLOSE(dirfd);
405
406 /*
407 * First verify the first group got the file MODIFY event and got just
408 * one FAN_CLOSE_NOWRITE event.
409 */
410 ret = read(fd_notify[0], event_buf, EVENT_BUF_LEN);
411 if (ret < 0) {
412 if (errno == EAGAIN) {
413 tst_res(TFAIL, "first group did not get event");
414 } else {
415 tst_brk(TBROK | TERRNO,
416 "reading fanotify events failed");
417 }
418 }
419 event = (struct fanotify_event_metadata *)event_buf;
420 if (ret < tc->nevents * (int)FAN_EVENT_METADATA_LEN) {
421 tst_res(TFAIL,
422 "short read when reading fanotify events (%d < %d)",
423 ret, tc->nevents * (int)FAN_EVENT_METADATA_LEN);
424 }
425 if (FAN_EVENT_OK(event, ret)) {
426 verify_event(0, event, FAN_MODIFY, tc->report_name ? fname : "");
427 event = FAN_EVENT_NEXT(event, ret);
428 }
429 if (tc->nevents > 1 && FAN_EVENT_OK(event, ret)) {
430 verify_event(0, event, FAN_CLOSE_NOWRITE,
431 tc->report_name ? (tc->ondir ? "." : tc->event_path) : "");
432 event = FAN_EVENT_NEXT(event, ret);
433 }
434 if (ret > 0) {
435 tst_res(TFAIL,
436 "first group got more than %d events (%d bytes)",
437 tc->nevents, ret);
438 }
439 close_event_fds(event, ret);
440
441 /*
442 * Then verify the rest of the groups did not get the MODIFY event and
443 * got the FAN_CLOSE_NOWRITE event only on a non-directory.
444 */
445 for (i = 1; i < NUM_GROUPS; i++) {
446 /*
447 * Verify that ignore mask survived the modify event on child,
448 * which was not supposed to be sent to this group.
449 */
450 if (tc->ignore)
451 check_ignore_mask(fd_notify[i]);
452
453 ret = read(fd_notify[i], event_buf, EVENT_BUF_LEN);
454 if (ret > 0) {
455 event = (struct fanotify_event_metadata *)event_buf;
456 verify_event(i, event, tc->nonfirst_event, "");
457 event = FAN_EVENT_NEXT(event, ret);
458
459 close_event_fds(event, ret);
460 continue;
461 }
462
463 if (ret == 0) {
464 tst_res(TFAIL, "group %d zero length read from fanotify fd", i);
465 continue;
466 }
467
468 if (errno != EAGAIN) {
469 tst_brk(TBROK | TERRNO,
470 "reading fanotify events failed");
471 }
472
473 if (tc->nonfirst_event)
474 tst_res(TFAIL, "group %d expected and got no event", i);
475 else
476 tst_res(TPASS, "group %d got no event as expected", i);
477 }
478 cleanup_fanotify_groups();
479 }
480
setup(void)481 static void setup(void)
482 {
483 fan_report_dfid_unsupported = fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME,
484 MOUNT_PATH);
485 ignore_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_IGNORE_SURV);
486
487 SAFE_MKDIR(MOUNT_NAME, 0755);
488 SAFE_MOUNT(MOUNT_PATH, MOUNT_NAME, "none", MS_BIND, NULL);
489 mount_created = 1;
490 SAFE_CHDIR(MOUNT_NAME);
491 SAFE_MKDIR(DIR_NAME, 0755);
492
493 sprintf(fname, "tfile_%d", getpid());
494 SAFE_FILE_PRINTF(fname, "1");
495 SAFE_FILE_PRINTF(FILE2_NAME, "1");
496 }
497
cleanup(void)498 static void cleanup(void)
499 {
500 cleanup_fanotify_groups();
501
502 SAFE_CHDIR("../");
503
504 if (mount_created)
505 SAFE_UMOUNT(MOUNT_NAME);
506 }
507
508 static struct tst_test test = {
509 .test = test_fanotify,
510 .tcnt = ARRAY_SIZE(tcases),
511 .setup = setup,
512 .cleanup = cleanup,
513 .mount_device = 1,
514 .mntpoint = MOUNT_PATH,
515 .needs_root = 1,
516 .tags = (const struct tst_tag[]) {
517 {"linux-git", "54a307ba8d3c"},
518 {"linux-git", "b469e7e47c8a"},
519 {"linux-git", "55bf882c7f13"},
520 {"linux-git", "7372e79c9eb9"},
521 {}
522 }
523 };
524
525 #else
526 TST_TEST_TCONF("system doesn't have required fanotify support");
527 #endif
528