1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
4 * Copyright (c) Linux Test Project, 2020-2022
5 *
6 * Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
7 */
8
9 /*\
10 * [Description]
11 * This test file has been designed to ensure that the fanotify
12 * system calls fanotify_init(2) and fanotify_mark(2) return the
13 * correct error code to the calling process when an invalid flag or
14 * mask value has been specified in conjunction with FAN_REPORT_FID.
15 */
16
17 /*
18 * The ENOTDIR test cases are regression tests for commits:
19 *
20 * ceaf69f8eadc fanotify: do not allow setting dirent events in mask of non-dir
21 * 8698e3bab4dd fanotify: refine the validation checks on non-dir inode mask
22 *
23 * The pipes test cases are regression tests for commit:
24 * 69562eb0bd3e fanotify: disallow mount/sb marks on kernel internal pseudo fs
25 */
26
27 #define _GNU_SOURCE
28 #include "tst_test.h"
29 #include <errno.h>
30
31 #ifdef HAVE_SYS_FANOTIFY_H
32 #include "fanotify.h"
33
34 #define MNTPOINT "mntpoint"
35 #define FILE1 MNTPOINT"/file1"
36
37 /*
38 * List of inode events that are only available when notification group is
39 * set to report fid.
40 */
41 #define INODE_EVENTS (FAN_ATTRIB | FAN_CREATE | FAN_DELETE | FAN_MOVE | \
42 FAN_DELETE_SELF | FAN_MOVE_SELF)
43
44 #define FLAGS_DESC(flags) {(flags), (#flags)}
45
46 static int pipes[2] = {-1, -1};
47 static int fanotify_fd;
48 static int ignore_mark_unsupported;
49 static int filesystem_mark_unsupported;
50 static unsigned int supported_init_flags;
51
52 struct test_case_flags_t {
53 unsigned long long flags;
54 const char *desc;
55 };
56
57 /*
58 * Each test case has been designed in a manner whereby the values defined
59 * within should result in the interface to return an error to the calling
60 * process.
61 */
62 static struct test_case_t {
63 struct test_case_flags_t init;
64 struct test_case_flags_t mark;
65 /* when mask.flags == 0, fanotify_init() is expected to fail */
66 struct test_case_flags_t mask;
67 int expected_errno;
68 int *pfd;
69 } test_cases[] = {
70 /* FAN_REPORT_FID without class FAN_CLASS_NOTIF is not valid */
71 {
72 .init = FLAGS_DESC(FAN_CLASS_CONTENT | FAN_REPORT_FID),
73 .expected_errno = EINVAL,
74 },
75
76 /* FAN_REPORT_FID without class FAN_CLASS_NOTIF is not valid */
77 {
78 .init = FLAGS_DESC(FAN_CLASS_PRE_CONTENT | FAN_REPORT_FID),
79 .expected_errno = EINVAL,
80 },
81
82 /* INODE_EVENTS in mask without class FAN_REPORT_FID are not valid */
83 {
84 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
85 .mark = FLAGS_DESC(FAN_MARK_INODE),
86 .mask = FLAGS_DESC(INODE_EVENTS),
87 .expected_errno = EINVAL,
88 },
89
90 /* INODE_EVENTS in mask with FAN_MARK_MOUNT are not valid */
91 {
92 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_FID),
93 .mark = FLAGS_DESC(FAN_MARK_MOUNT),
94 .mask = FLAGS_DESC(INODE_EVENTS),
95 .expected_errno = EINVAL,
96 },
97
98 /* FAN_REPORT_NAME without FAN_REPORT_DIR_FID is not valid */
99 {
100 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_NAME),
101 .expected_errno = EINVAL,
102 },
103
104 /* FAN_REPORT_NAME without FAN_REPORT_DIR_FID is not valid */
105 {
106 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_FID | FAN_REPORT_NAME),
107 .expected_errno = EINVAL,
108 },
109
110 /* FAN_REPORT_TARGET_FID without FAN_REPORT_FID is not valid */
111 {
112 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_TARGET_FID | FAN_REPORT_DFID_NAME),
113 .expected_errno = EINVAL,
114 },
115
116 /* FAN_REPORT_TARGET_FID without FAN_REPORT_NAME is not valid */
117 {
118 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_TARGET_FID | FAN_REPORT_DFID_FID),
119 .expected_errno = EINVAL,
120 },
121
122 /* FAN_RENAME without FAN_REPORT_NAME is not valid */
123 {
124 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_FID),
125 .mark = FLAGS_DESC(FAN_MARK_INODE),
126 .mask = FLAGS_DESC(FAN_RENAME),
127 .expected_errno = EINVAL,
128 },
129
130 /* With FAN_MARK_ONLYDIR on non-dir is not valid */
131 {
132 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
133 .mark = FLAGS_DESC(FAN_MARK_ONLYDIR),
134 .mask = FLAGS_DESC(FAN_OPEN),
135 .expected_errno = ENOTDIR,
136 },
137
138 /* With FAN_REPORT_TARGET_FID, FAN_DELETE on non-dir is not valid */
139 {
140 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
141 .mark = FLAGS_DESC(FAN_MARK_INODE),
142 .mask = FLAGS_DESC(FAN_DELETE),
143 .expected_errno = ENOTDIR,
144 },
145
146 /* With FAN_REPORT_TARGET_FID, FAN_RENAME on non-dir is not valid */
147 {
148 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
149 .mark = FLAGS_DESC(FAN_MARK_INODE),
150 .mask = FLAGS_DESC(FAN_RENAME),
151 .expected_errno = ENOTDIR,
152 },
153
154 /* With FAN_REPORT_TARGET_FID, FAN_ONDIR on non-dir is not valid */
155 {
156 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
157 .mark = FLAGS_DESC(FAN_MARK_INODE),
158 .mask = FLAGS_DESC(FAN_OPEN | FAN_ONDIR),
159 .expected_errno = ENOTDIR,
160 },
161
162 /* With FAN_REPORT_TARGET_FID, FAN_EVENT_ON_CHILD on non-dir is not valid */
163 {
164 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
165 .mark = FLAGS_DESC(FAN_MARK_INODE),
166 .mask = FLAGS_DESC(FAN_OPEN | FAN_EVENT_ON_CHILD),
167 .expected_errno = ENOTDIR,
168 },
169
170 /* FAN_MARK_IGNORE_SURV with FAN_DELETE on non-dir is not valid */
171 {
172 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
173 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
174 .mask = FLAGS_DESC(FAN_DELETE),
175 .expected_errno = ENOTDIR,
176 },
177
178 /* FAN_MARK_IGNORE_SURV with FAN_RENAME on non-dir is not valid */
179 {
180 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
181 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
182 .mask = FLAGS_DESC(FAN_RENAME),
183 .expected_errno = ENOTDIR,
184 },
185
186 /* FAN_MARK_IGNORE_SURV with FAN_ONDIR on non-dir is not valid */
187 {
188 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
189 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
190 .mask = FLAGS_DESC(FAN_OPEN | FAN_ONDIR),
191 .expected_errno = ENOTDIR,
192 },
193
194 /* FAN_MARK_IGNORE_SURV with FAN_EVENT_ON_CHILD on non-dir is not valid */
195 {
196 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
197 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
198 .mask = FLAGS_DESC(FAN_OPEN | FAN_EVENT_ON_CHILD),
199 .expected_errno = ENOTDIR,
200 },
201
202 /* FAN_MARK_IGNORE without FAN_MARK_IGNORED_SURV_MODIFY on directory is not valid */
203 {
204 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
205 .mark = FLAGS_DESC(FAN_MARK_IGNORE),
206 .mask = FLAGS_DESC(FAN_OPEN),
207 .expected_errno = EISDIR,
208 },
209
210 /* FAN_MARK_IGNORE without FAN_MARK_IGNORED_SURV_MODIFY on mount mark is not valid */
211 {
212 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
213 .mark = FLAGS_DESC(FAN_MARK_MOUNT | FAN_MARK_IGNORE),
214 .mask = FLAGS_DESC(FAN_OPEN),
215 .expected_errno = EINVAL,
216 },
217
218 /* FAN_MARK_IGNORE without FAN_MARK_IGNORED_SURV_MODIFY on filesystem mark is not valid */
219 {
220 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
221 .mark = FLAGS_DESC(FAN_MARK_FILESYSTEM | FAN_MARK_IGNORE),
222 .mask = FLAGS_DESC(FAN_OPEN),
223 .expected_errno = EINVAL,
224 },
225 /* mount mark on anonymous pipe is not valid */
226 {
227 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
228 .mark = FLAGS_DESC(FAN_MARK_MOUNT),
229 .mask = { FAN_ACCESS, "anonymous pipe"},
230 .pfd = pipes,
231 .expected_errno = EINVAL,
232 },
233 /* filesystem mark on anonymous pipe is not valid */
234 {
235 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
236 .mark = FLAGS_DESC(FAN_MARK_FILESYSTEM),
237 .mask = { FAN_ACCESS, "anonymous pipe"},
238 .pfd = pipes,
239 .expected_errno = EINVAL,
240 },
241 };
242
do_test(unsigned int number)243 static void do_test(unsigned int number)
244 {
245 struct test_case_t *tc = &test_cases[number];
246
247 tst_res(TINFO, "Test case %d: fanotify_init(%s, O_RDONLY)", number,
248 tc->init.desc);
249
250 if (tc->init.flags & ~supported_init_flags) {
251 tst_res(TCONF, "Unsupported init flags");
252 return;
253 }
254
255 if (ignore_mark_unsupported && tc->mark.flags & FAN_MARK_IGNORE) {
256 tst_res(TCONF, "FAN_MARK_IGNORE not supported in kernel?");
257 return;
258 }
259
260 if (!tc->mask.flags && tc->expected_errno) {
261 TST_EXP_FAIL(fanotify_init(tc->init.flags, O_RDONLY),
262 tc->expected_errno);
263 } else {
264 TST_EXP_FD(fanotify_init(tc->init.flags, O_RDONLY));
265 }
266
267 fanotify_fd = TST_RET;
268
269 if (fanotify_fd < 0)
270 return;
271
272 if (!tc->mask.flags)
273 goto out;
274
275 /* Set mark on non-dir only when expecting error ENOTDIR */
276 const char *path = tc->expected_errno == ENOTDIR ? FILE1 : MNTPOINT;
277 int dirfd = AT_FDCWD;
278
279 if (tc->pfd) {
280 dirfd = tc->pfd[0];
281 path = NULL;
282 }
283
284 tst_res(TINFO, "Testing %s with %s",
285 tc->mark.desc, tc->mask.desc);
286 TST_EXP_FD_OR_FAIL(fanotify_mark(fanotify_fd, FAN_MARK_ADD | tc->mark.flags,
287 tc->mask.flags, dirfd, path),
288 tc->expected_errno);
289
290 /*
291 * ENOTDIR are errors for events/flags not allowed on a non-dir inode.
292 * Try to set an inode mark on a directory and it should succeed.
293 * Try to set directory events in filesystem mark mask on non-dir
294 * and it should succeed.
295 */
296 if (TST_PASS && tc->expected_errno == ENOTDIR) {
297 SAFE_FANOTIFY_MARK(fanotify_fd, FAN_MARK_ADD | tc->mark.flags,
298 tc->mask.flags, AT_FDCWD, MNTPOINT);
299 tst_res(TPASS,
300 "Adding an inode mark on directory did not fail with "
301 "ENOTDIR error as on non-dir inode");
302
303 if (!(tc->mark.flags & FAN_MARK_ONLYDIR) && !filesystem_mark_unsupported) {
304 SAFE_FANOTIFY_MARK(fanotify_fd, FAN_MARK_ADD | tc->mark.flags |
305 FAN_MARK_FILESYSTEM, tc->mask.flags,
306 AT_FDCWD, FILE1);
307 tst_res(TPASS,
308 "Adding a filesystem mark on non-dir did not fail with "
309 "ENOTDIR error as with an inode mark");
310 }
311 }
312
313 out:
314 if (fanotify_fd > 0)
315 SAFE_CLOSE(fanotify_fd);
316 }
317
do_setup(void)318 static void do_setup(void)
319 {
320 unsigned int all_init_flags = FAN_REPORT_DFID_NAME_TARGET |
321 FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | FAN_CLASS_PRE_CONTENT;
322
323 /* Require FAN_REPORT_FID support for all tests to simplify per test case requirements */
324 REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, MNTPOINT);
325 supported_init_flags = fanotify_get_supported_init_flags(all_init_flags, MNTPOINT);
326
327 ignore_mark_unsupported = fanotify_mark_supported_on_fs(FAN_MARK_IGNORE_SURV,
328 MNTPOINT);
329 filesystem_mark_unsupported =
330 fanotify_flags_supported_on_fs(FAN_REPORT_FID, FAN_MARK_FILESYSTEM, FAN_OPEN,
331 MNTPOINT);
332
333 /* Create temporary test file to place marks on */
334 SAFE_FILE_PRINTF(FILE1, "0");
335 /* Create anonymous pipes to place marks on */
336 SAFE_PIPE2(pipes, O_CLOEXEC);
337 }
338
do_cleanup(void)339 static void do_cleanup(void)
340 {
341 if (fanotify_fd > 0)
342 SAFE_CLOSE(fanotify_fd);
343 if (pipes[0] != -1)
344 SAFE_CLOSE(pipes[0]);
345 if (pipes[1] != -1)
346 SAFE_CLOSE(pipes[1]);
347 }
348
349 static struct tst_test test = {
350 .needs_root = 1,
351 .test = do_test,
352 .tcnt = ARRAY_SIZE(test_cases),
353 .setup = do_setup,
354 .cleanup = do_cleanup,
355 .mount_device = 1,
356 .mntpoint = MNTPOINT,
357 .all_filesystems = 1,
358 .tags = (const struct tst_tag[]) {
359 {"linux-git", "ceaf69f8eadc"},
360 {"linux-git", "8698e3bab4dd"},
361 {"linux-git", "69562eb0bd3e"},
362 {}
363 }
364 };
365
366 #else
367 TST_TEST_TCONF("System does not have required fanotify support");
368 #endif
369