• 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 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