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