1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
4 *
5 * Started by Matthew Bobrowski <[email protected]>
6 */
7
8 /*\
9 * [Description]
10 * Validate that the values returned within an event when FAN_REPORT_FID is
11 * specified matches those that are obtained via explicit invocation to system
12 * calls statfs(2) and name_to_handle_at(2).
13 */
14
15 /*
16 * This is also regression test for:
17 * c285a2f01d69 ("fanotify: update connector fsid cache on add mark")
18 */
19
20 #define _GNU_SOURCE
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/statfs.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/mount.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include "tst_test.h"
32
33 #ifdef HAVE_SYS_FANOTIFY_H
34 #include "fanotify.h"
35
36 #define PATH_LEN 128
37 #define BUF_SIZE 256
38 #define DIR_ONE "dir_one"
39 #define FILE_ONE "file_one"
40 #define FILE_TWO "file_two"
41 #define MOUNT_PATH "tstmnt"
42 #define EVENT_MAX ARRAY_SIZE(objects)
43 #define DIR_PATH_ONE MOUNT_PATH"/"DIR_ONE
44 #define FILE_PATH_ONE MOUNT_PATH"/"FILE_ONE
45 #define FILE_PATH_TWO MOUNT_PATH"/"FILE_TWO
46
47 #if defined(HAVE_NAME_TO_HANDLE_AT)
48 struct event_t {
49 unsigned long long expected_mask;
50 };
51
52 static struct object_t {
53 const char *path;
54 int is_dir;
55 struct fanotify_fid_t fid;
56 } objects[] = {
57 {FILE_PATH_ONE, 0, {}},
58 {FILE_PATH_TWO, 0, {}},
59 {DIR_PATH_ONE, 1, {}}
60 };
61
62 static struct test_case_t {
63 struct fanotify_mark_type mark;
64 unsigned long long mask;
65 } test_cases[] = {
66 {
67 INIT_FANOTIFY_MARK_TYPE(INODE),
68 FAN_OPEN | FAN_CLOSE_NOWRITE
69 },
70 {
71 INIT_FANOTIFY_MARK_TYPE(INODE),
72 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
73 },
74 {
75 INIT_FANOTIFY_MARK_TYPE(MOUNT),
76 FAN_OPEN | FAN_CLOSE_NOWRITE
77 },
78 {
79 INIT_FANOTIFY_MARK_TYPE(MOUNT),
80 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
81 },
82 {
83 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
84 FAN_OPEN | FAN_CLOSE_NOWRITE
85 },
86 {
87 INIT_FANOTIFY_MARK_TYPE(FILESYSTEM),
88 FAN_OPEN | FAN_CLOSE_NOWRITE | FAN_ONDIR
89 }
90 };
91
92 static int ovl_mounted;
93 static int bind_mounted;
94 static int ovl_bind_mounted;
95 static int nofid_fd;
96 static int fanotify_fd;
97 static int at_handle_fid;
98 static int filesystem_mark_unsupported;
99 static char events_buf[BUF_SIZE];
100 static struct event_t event_set[EVENT_MAX];
101
create_objects(void)102 static void create_objects(void)
103 {
104 unsigned int i;
105
106 for (i = 0; i < ARRAY_SIZE(objects); i++) {
107 if (objects[i].is_dir)
108 SAFE_MKDIR(objects[i].path, 0755);
109 else
110 SAFE_FILE_PRINTF(objects[i].path, "0");
111 }
112 }
113
get_object_stats(void)114 static void get_object_stats(void)
115 {
116 unsigned int i;
117
118 for (i = 0; i < ARRAY_SIZE(objects); i++) {
119 at_handle_fid |=
120 fanotify_save_fid(objects[i].path, &objects[i].fid);
121 }
122 }
123
setup_marks(unsigned int fd,struct test_case_t * tc)124 static int setup_marks(unsigned int fd, struct test_case_t *tc)
125 {
126 unsigned int i;
127 struct fanotify_mark_type *mark = &tc->mark;
128
129 for (i = 0; i < ARRAY_SIZE(objects); i++) {
130 SAFE_FANOTIFY_MARK(fd, FAN_MARK_ADD | mark->flag, tc->mask,
131 AT_FDCWD, objects[i].path);
132
133 /* Setup the expected mask for each generated event */
134 event_set[i].expected_mask = tc->mask;
135 if (!objects[i].is_dir)
136 event_set[i].expected_mask &= ~FAN_ONDIR;
137 }
138 return 0;
139 }
140
do_test(unsigned int number)141 static void do_test(unsigned int number)
142 {
143 unsigned int i;
144 int len, fds[ARRAY_SIZE(objects)];
145
146 struct file_handle *event_file_handle;
147 struct fanotify_event_metadata *metadata;
148 struct fanotify_event_info_fid *event_fid;
149 struct test_case_t *tc = &test_cases[number];
150 struct fanotify_mark_type *mark = &tc->mark;
151
152 tst_res(TINFO,
153 "Test #%d.%d: FAN_REPORT_FID with mark flag: %s",
154 number, tst_variant, mark->name);
155
156 if (tst_variant && !ovl_mounted) {
157 tst_res(TCONF, "overlayfs not supported on %s", tst_device->fs_type);
158 return;
159 }
160
161 if (filesystem_mark_unsupported && mark->flag != FAN_MARK_INODE) {
162 FANOTIFY_MARK_FLAGS_ERR_MSG(mark, filesystem_mark_unsupported);
163 return;
164 }
165
166 fanotify_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY);
167
168 /*
169 * Place marks on a set of objects and setup the expected masks
170 * for each event that is expected to be generated.
171 */
172 if (setup_marks(fanotify_fd, tc) != 0)
173 goto out;
174
175 /* Watching base fs - open files on overlayfs */
176 if (tst_variant && !ovl_bind_mounted) {
177 if (mark->flag & FAN_MARK_MOUNT) {
178 tst_res(TCONF, "overlayfs base fs cannot be watched with mount mark");
179 goto out;
180 }
181 SAFE_MOUNT(OVL_MNT, MOUNT_PATH, "none", MS_BIND, NULL);
182 }
183
184 /* Generate sequence of FAN_OPEN events on objects */
185 for (i = 0; i < ARRAY_SIZE(objects); i++)
186 fds[i] = SAFE_OPEN(objects[i].path, O_RDONLY);
187
188 /*
189 * Generate sequence of FAN_CLOSE_NOWRITE events on objects. Each
190 * FAN_CLOSE_NOWRITE event is expected to be merged with its
191 * respective FAN_OPEN event that was performed on the same object.
192 */
193 for (i = 0; i < ARRAY_SIZE(objects); i++) {
194 if (fds[i] > 0)
195 SAFE_CLOSE(fds[i]);
196 }
197
198 if (tst_variant && !ovl_bind_mounted)
199 SAFE_UMOUNT(MOUNT_PATH);
200
201 /* Read events from event queue */
202 len = SAFE_READ(0, fanotify_fd, events_buf, BUF_SIZE);
203
204 /* Iterate over event queue */
205 for (i = 0, metadata = (struct fanotify_event_metadata *) events_buf;
206 FAN_EVENT_OK(metadata, len);
207 metadata = FAN_EVENT_NEXT(metadata, len), i++) {
208 struct fanotify_fid_t *expected_fid = &objects[i].fid;
209
210 event_fid = (struct fanotify_event_info_fid *) (metadata + 1);
211 event_file_handle = (struct file_handle *) event_fid->handle;
212
213 /* File descriptor is redundant with FAN_REPORT_FID */
214 if (metadata->fd != FAN_NOFD)
215 tst_res(TFAIL,
216 "Unexpectedly received file descriptor %d in "
217 "event. Expected to get FAN_NOFD(%d)",
218 metadata->fd, FAN_NOFD);
219
220 /* Ensure that the correct mask has been reported in event */
221 if (metadata->mask != event_set[i].expected_mask)
222 tst_res(TFAIL,
223 "Unexpected mask received: %llx (expected: "
224 "%llx) in event",
225 metadata->mask,
226 event_set[i].expected_mask);
227
228 /* Verify handle_bytes returned in event */
229 if (event_file_handle->handle_bytes !=
230 expected_fid->handle.handle_bytes) {
231 tst_res(TFAIL,
232 "handle_bytes (%x) returned in event does not "
233 "equal to handle_bytes (%x) returned in "
234 "name_to_handle_at(2)",
235 event_file_handle->handle_bytes,
236 expected_fid->handle.handle_bytes);
237 continue;
238 }
239
240 /* Verify handle_type returned in event */
241 if (event_file_handle->handle_type !=
242 expected_fid->handle.handle_type) {
243 tst_res(TFAIL,
244 "handle_type (%x) returned in event does not "
245 "equal to handle_type (%x) returned in "
246 "name_to_handle_at(2)",
247 event_file_handle->handle_type,
248 expected_fid->handle.handle_type);
249 continue;
250 }
251
252 /* Verify file identifier f_handle returned in event */
253 if (memcmp(event_file_handle->f_handle,
254 expected_fid->handle.f_handle,
255 expected_fid->handle.handle_bytes) != 0) {
256 tst_res(TFAIL,
257 "file_handle returned in event does not match "
258 "the file_handle returned in "
259 "name_to_handle_at(2)");
260 continue;
261 }
262
263 /* Verify filesystem ID fsid returned in event */
264 if (memcmp(&event_fid->fsid, &expected_fid->fsid,
265 sizeof(expected_fid->fsid)) != 0) {
266 tst_res(TFAIL,
267 "event_fid.fsid != stat.f_fsid that was "
268 "obtained via statfs(2)");
269 continue;
270 }
271
272 tst_res(TPASS,
273 "got event: mask=%llx, pid=%d, fid=%x.%x.%lx values "
274 "returned in event match those returned in statfs(2) "
275 "and name_to_handle_at(2)",
276 metadata->mask,
277 getpid(),
278 FSID_VAL_MEMBER(event_fid->fsid, 0),
279 FSID_VAL_MEMBER(event_fid->fsid, 1),
280 *(unsigned long *) event_file_handle->f_handle);
281 }
282 out:
283 SAFE_CLOSE(fanotify_fd);
284 }
285
do_setup(void)286 static void do_setup(void)
287 {
288 const char *mnt;
289
290 /*
291 * Bind mount to either base fs or to overlayfs over base fs:
292 * Variant #0: watch base fs - open files on base fs
293 * Variant #1: watch lower fs - open lower files on overlayfs
294 * Variant #2: watch upper fs - open upper files on overlayfs
295 * Variant #3: watch overlayfs - open lower files on overlayfs
296 * Variant #4: watch overlayfs - open upper files on overlayfs
297 *
298 * Variants 1,2 test a bug whose fix bc2473c90fca ("ovl: enable fsnotify
299 * events on underlying real files") in kernel 6.5 is not likely to be
300 * backported to older kernels.
301 * To avoid waiting for events that won't arrive when testing old kernels,
302 * require that kernel supports encoding fid with new flag AT_HANDLE_FID,
303 * also merged to 6.5 and not likely to be backported to older kernels.
304 * Variants 3,4 test overlayfs watch with FAN_REPORT_FID, which also
305 * requires kernel with support for AT_HANDLE_FID.
306 */
307 if (tst_variant) {
308 REQUIRE_HANDLE_TYPE_SUPPORTED_BY_KERNEL(AT_HANDLE_FID);
309 ovl_mounted = TST_MOUNT_OVERLAY();
310 if (!ovl_mounted)
311 return;
312
313 mnt = tst_variant & 1 ? OVL_LOWER : OVL_UPPER;
314 } else {
315 mnt = OVL_BASE_MNTPOINT;
316
317 }
318 REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, mnt);
319 SAFE_MKDIR(MOUNT_PATH, 0755);
320 SAFE_MOUNT(mnt, MOUNT_PATH, "none", MS_BIND, NULL);
321 bind_mounted = 1;
322
323 nofid_fd = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF, O_RDONLY);
324
325 /* Create file and directory objects for testing on base fs */
326 create_objects();
327
328 if (tst_variant > 2) {
329 /* Setup watches on overlayfs */
330 SAFE_MOUNT(OVL_MNT, MOUNT_PATH, "none", MS_BIND, NULL);
331 ovl_bind_mounted = 1;
332 }
333
334 filesystem_mark_unsupported =
335 fanotify_flags_supported_on_fs(FAN_REPORT_FID, FAN_MARK_FILESYSTEM, FAN_OPEN,
336 ovl_bind_mounted ? OVL_MNT : MOUNT_PATH);
337
338 /*
339 * Create a mark on first inode without FAN_REPORT_FID, to test
340 * uninitialized connector->fsid cache. This mark remains for all test
341 * cases and is not expected to get any events (no writes in this test).
342 */
343 SAFE_FANOTIFY_MARK(nofid_fd, FAN_MARK_ADD, FAN_CLOSE_WRITE, AT_FDCWD,
344 FILE_PATH_ONE);
345
346 /* Get the filesystem fsid and file handle for each created object */
347 get_object_stats();
348 }
349
do_cleanup(void)350 static void do_cleanup(void)
351 {
352 if (nofid_fd > 0)
353 SAFE_CLOSE(nofid_fd);
354 if (fanotify_fd > 0)
355 SAFE_CLOSE(fanotify_fd);
356 if (ovl_bind_mounted)
357 SAFE_UMOUNT(MOUNT_PATH);
358 if (bind_mounted) {
359 SAFE_UMOUNT(MOUNT_PATH);
360 SAFE_RMDIR(MOUNT_PATH);
361 }
362 if (ovl_mounted)
363 SAFE_UMOUNT(OVL_MNT);
364 }
365
366 static struct tst_test test = {
367 .test = do_test,
368 .tcnt = ARRAY_SIZE(test_cases),
369 .test_variants = 5,
370 .setup = do_setup,
371 .cleanup = do_cleanup,
372 .needs_root = 1,
373 .mount_device = 1,
374 .mntpoint = OVL_BASE_MNTPOINT,
375 .all_filesystems = 1,
376 .tags = (const struct tst_tag[]) {
377 {"linux-git", "c285a2f01d69"},
378 {"linux-git", "bc2473c90fca"},
379 {}
380 }
381 };
382
383 #else
384 TST_TEST_TCONF("System does not have required name_to_handle_at() support");
385 #endif
386 #else
387 TST_TEST_TCONF("System does not have required fanotify support");
388 #endif
389