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