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 fan_report_target_fid_unsupported;
49 static int ignore_mark_unsupported;
50
51 struct test_case_flags_t {
52 unsigned long long flags;
53 const char *desc;
54 };
55
56 /*
57 * Each test case has been designed in a manner whereby the values defined
58 * within should result in the interface to return an error to the calling
59 * process.
60 */
61 static struct test_case_t {
62 struct test_case_flags_t init;
63 struct test_case_flags_t mark;
64 /* when mask.flags == 0, fanotify_init() is expected to fail */
65 struct test_case_flags_t mask;
66 int expected_errno;
67 int *pfd;
68 } test_cases[] = {
69 /* FAN_REPORT_FID without class FAN_CLASS_NOTIF is not valid */
70 {
71 .init = FLAGS_DESC(FAN_CLASS_CONTENT | FAN_REPORT_FID),
72 .expected_errno = EINVAL,
73 },
74
75 /* FAN_REPORT_FID without class FAN_CLASS_NOTIF is not valid */
76 {
77 .init = FLAGS_DESC(FAN_CLASS_PRE_CONTENT | FAN_REPORT_FID),
78 .expected_errno = EINVAL,
79 },
80
81 /* INODE_EVENTS in mask without class FAN_REPORT_FID are not valid */
82 {
83 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
84 .mark = FLAGS_DESC(FAN_MARK_INODE),
85 .mask = FLAGS_DESC(INODE_EVENTS),
86 .expected_errno = EINVAL,
87 },
88
89 /* INODE_EVENTS in mask with FAN_MARK_MOUNT are not valid */
90 {
91 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_FID),
92 .mark = FLAGS_DESC(FAN_MARK_MOUNT),
93 .mask = FLAGS_DESC(INODE_EVENTS),
94 .expected_errno = EINVAL,
95 },
96
97 /* FAN_REPORT_NAME without FAN_REPORT_DIR_FID is not valid */
98 {
99 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_NAME),
100 .expected_errno = EINVAL,
101 },
102
103 /* FAN_REPORT_NAME without FAN_REPORT_DIR_FID is not valid */
104 {
105 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_FID | FAN_REPORT_NAME),
106 .expected_errno = EINVAL,
107 },
108
109 /* FAN_REPORT_TARGET_FID without FAN_REPORT_FID is not valid */
110 {
111 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_TARGET_FID | FAN_REPORT_DFID_NAME),
112 .expected_errno = EINVAL,
113 },
114
115 /* FAN_REPORT_TARGET_FID without FAN_REPORT_NAME is not valid */
116 {
117 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_TARGET_FID | FAN_REPORT_DFID_FID),
118 .expected_errno = EINVAL,
119 },
120
121 /* FAN_RENAME without FAN_REPORT_NAME is not valid */
122 {
123 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_FID),
124 .mark = FLAGS_DESC(FAN_MARK_INODE),
125 .mask = FLAGS_DESC(FAN_RENAME),
126 .expected_errno = EINVAL,
127 },
128
129 /* With FAN_MARK_ONLYDIR on non-dir is not valid */
130 {
131 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
132 .mark = FLAGS_DESC(FAN_MARK_ONLYDIR),
133 .mask = FLAGS_DESC(FAN_OPEN),
134 .expected_errno = ENOTDIR,
135 },
136
137 /* With FAN_REPORT_TARGET_FID, FAN_DELETE on non-dir is not valid */
138 {
139 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
140 .mark = FLAGS_DESC(FAN_MARK_INODE),
141 .mask = FLAGS_DESC(FAN_DELETE),
142 .expected_errno = ENOTDIR,
143 },
144
145 /* With FAN_REPORT_TARGET_FID, FAN_RENAME on non-dir is not valid */
146 {
147 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
148 .mark = FLAGS_DESC(FAN_MARK_INODE),
149 .mask = FLAGS_DESC(FAN_RENAME),
150 .expected_errno = ENOTDIR,
151 },
152
153 /* With FAN_REPORT_TARGET_FID, FAN_ONDIR on non-dir is not valid */
154 {
155 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
156 .mark = FLAGS_DESC(FAN_MARK_INODE),
157 .mask = FLAGS_DESC(FAN_OPEN | FAN_ONDIR),
158 .expected_errno = ENOTDIR,
159 },
160
161 /* With FAN_REPORT_TARGET_FID, FAN_EVENT_ON_CHILD on non-dir is not valid */
162 {
163 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME_TARGET),
164 .mark = FLAGS_DESC(FAN_MARK_INODE),
165 .mask = FLAGS_DESC(FAN_OPEN | FAN_EVENT_ON_CHILD),
166 .expected_errno = ENOTDIR,
167 },
168
169 /* FAN_MARK_IGNORE_SURV with FAN_DELETE on non-dir is not valid */
170 {
171 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
172 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
173 .mask = FLAGS_DESC(FAN_DELETE),
174 .expected_errno = ENOTDIR,
175 },
176
177 /* FAN_MARK_IGNORE_SURV with FAN_RENAME on non-dir is not valid */
178 {
179 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
180 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
181 .mask = FLAGS_DESC(FAN_RENAME),
182 .expected_errno = ENOTDIR,
183 },
184
185 /* FAN_MARK_IGNORE_SURV with FAN_ONDIR on non-dir is not valid */
186 {
187 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
188 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
189 .mask = FLAGS_DESC(FAN_OPEN | FAN_ONDIR),
190 .expected_errno = ENOTDIR,
191 },
192
193 /* FAN_MARK_IGNORE_SURV with FAN_EVENT_ON_CHILD on non-dir is not valid */
194 {
195 .init = FLAGS_DESC(FAN_CLASS_NOTIF | FAN_REPORT_DFID_NAME),
196 .mark = FLAGS_DESC(FAN_MARK_IGNORE_SURV),
197 .mask = FLAGS_DESC(FAN_OPEN | FAN_EVENT_ON_CHILD),
198 .expected_errno = ENOTDIR,
199 },
200
201 /* FAN_MARK_IGNORE without FAN_MARK_IGNORED_SURV_MODIFY on directory is not valid */
202 {
203 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
204 .mark = FLAGS_DESC(FAN_MARK_IGNORE),
205 .mask = FLAGS_DESC(FAN_OPEN),
206 .expected_errno = EISDIR,
207 },
208
209 /* FAN_MARK_IGNORE without FAN_MARK_IGNORED_SURV_MODIFY on mount mark is not valid */
210 {
211 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
212 .mark = FLAGS_DESC(FAN_MARK_MOUNT | FAN_MARK_IGNORE),
213 .mask = FLAGS_DESC(FAN_OPEN),
214 .expected_errno = EINVAL,
215 },
216
217 /* FAN_MARK_IGNORE without FAN_MARK_IGNORED_SURV_MODIFY on filesystem mark is not valid */
218 {
219 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
220 .mark = FLAGS_DESC(FAN_MARK_FILESYSTEM | FAN_MARK_IGNORE),
221 .mask = FLAGS_DESC(FAN_OPEN),
222 .expected_errno = EINVAL,
223 },
224 /* mount mark on anonymous pipe is not valid */
225 {
226 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
227 .mark = FLAGS_DESC(FAN_MARK_MOUNT),
228 .mask = { FAN_ACCESS, "anonymous pipe"},
229 .pfd = pipes,
230 .expected_errno = EINVAL,
231 },
232 /* filesystem mark on anonymous pipe is not valid */
233 {
234 .init = FLAGS_DESC(FAN_CLASS_NOTIF),
235 .mark = FLAGS_DESC(FAN_MARK_FILESYSTEM),
236 .mask = { FAN_ACCESS, "anonymous pipe"},
237 .pfd = pipes,
238 .expected_errno = EINVAL,
239 },
240 };
241
do_test(unsigned int number)242 static void do_test(unsigned int number)
243 {
244 struct test_case_t *tc = &test_cases[number];
245
246 tst_res(TINFO, "Test case %d: fanotify_init(%s, O_RDONLY)", number,
247 tc->init.desc);
248
249 if (fan_report_target_fid_unsupported && tc->init.flags & FAN_REPORT_TARGET_FID) {
250 FANOTIFY_INIT_FLAGS_ERR_MSG(FAN_REPORT_TARGET_FID,
251 fan_report_target_fid_unsupported);
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)) {
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 /* Require FAN_REPORT_FID support for all tests to simplify per test case requirements */
321 REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, MNTPOINT);
322
323 fan_report_target_fid_unsupported =
324 fanotify_init_flags_supported_on_fs(FAN_REPORT_DFID_NAME_TARGET, MNTPOINT);
325 ignore_mark_unsupported = fanotify_mark_supported_by_kernel(FAN_MARK_IGNORE_SURV);
326
327 /* Create temporary test file to place marks on */
328 SAFE_FILE_PRINTF(FILE1, "0");
329 /* Create anonymous pipes to place marks on */
330 SAFE_PIPE2(pipes, O_CLOEXEC);
331 }
332
do_cleanup(void)333 static void do_cleanup(void)
334 {
335 if (fanotify_fd > 0)
336 SAFE_CLOSE(fanotify_fd);
337 if (pipes[0] != -1)
338 SAFE_CLOSE(pipes[0]);
339 if (pipes[1] != -1)
340 SAFE_CLOSE(pipes[1]);
341 }
342
343 static struct tst_test test = {
344 .needs_root = 1,
345 .test = do_test,
346 .tcnt = ARRAY_SIZE(test_cases),
347 .setup = do_setup,
348 .cleanup = do_cleanup,
349 .mount_device = 1,
350 .mntpoint = MNTPOINT,
351 .all_filesystems = 1,
352 .tags = (const struct tst_tag[]) {
353 {"linux-git", "ceaf69f8eadc"},
354 {"linux-git", "8698e3bab4dd"},
355 {"linux-git", "69562eb0bd3e"},
356 {}
357 }
358 };
359
360 #else
361 TST_TEST_TCONF("System does not have required fanotify support");
362 #endif
363