• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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