1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2018 Matthew Bobrowski. All Rights Reserved.
4 *
5 * Started by Matthew Bobrowski <mbobrowski@mbobrowski.org>
6 */
7
8/*\
9 * [Description]
10 * This set of tests is to ensure that the unprivileged listener feature of
11 * fanotify is functioning as expected. The objective of this test file is
12 * to generate a sequence of events and ensure that the returned events
13 * contain the limited values that an unprivileged listener is expected
14 * to receive.
15 */
16
17#define _GNU_SOURCE
18#include "config.h"
19
20#include <pwd.h>
21#include <stdio.h>
22#include <errno.h>
23#include <stdlib.h>
24#include <sys/wait.h>
25
26#include "tst_test.h"
27
28#ifdef HAVE_SYS_FANOTIFY_H
29#include "fanotify.h"
30
31#define EVENT_MAX 1024
32#define EVENT_SIZE (sizeof(struct fanotify_event_metadata))
33#define EVENT_BUF_LEN (EVENT_MAX * EVENT_SIZE)
34#define EVENT_SET_MAX 48
35
36#define BUF_SIZE 256
37
38#define MOUNT_PATH	"fs_mnt"
39#define TEST_FILE	MOUNT_PATH "/testfile"
40
41static uid_t euid;
42static int fd_notify;
43static char buf[BUF_SIZE];
44static struct fanotify_event_metadata event_buf[EVENT_BUF_LEN];
45
46static struct test_case_t {
47	const char *name;
48	unsigned int fork;
49	unsigned int elevate;
50	unsigned int event_count;
51	unsigned long long event_set[EVENT_SET_MAX];
52} test_cases[] = {
53	{
54		"unprivileged listener - events by self",
55		0,
56		0,
57		4,
58		{
59			FAN_OPEN,
60			FAN_ACCESS,
61			FAN_MODIFY,
62			FAN_CLOSE,
63		}
64	},
65	{
66		"unprivileged lisneter - events by child",
67		1,
68		0,
69		4,
70		{
71			FAN_OPEN,
72			FAN_ACCESS,
73			FAN_MODIFY,
74			FAN_CLOSE,
75		}
76	},
77	{
78		"unprivileged listener, privileged reader - events by self",
79		0,
80		1,
81		4,
82		{
83			FAN_OPEN,
84			FAN_ACCESS,
85			FAN_MODIFY,
86			FAN_CLOSE,
87		}
88	},
89	{
90		"unprivileged lisneter, privileged reader - events by child",
91		1,
92		1,
93		4,
94		{
95			FAN_OPEN,
96			FAN_ACCESS,
97			FAN_MODIFY,
98			FAN_CLOSE,
99		}
100	},
101};
102
103static void generate_events(void)
104{
105	int fd;
106
107	/* FAN_OPEN */
108	fd = SAFE_OPEN(TEST_FILE, O_RDWR);
109
110	/* FAN_ACCESS */
111	SAFE_READ(0, fd, buf, BUF_SIZE);
112
113	/* FAN_MODIFY */
114	SAFE_WRITE(SAFE_WRITE_ALL, fd, TEST_FILE, 1);
115
116	/* FAN_CLOSE */
117	SAFE_CLOSE(fd);
118}
119
120static void do_fork(void)
121{
122	int status;
123	pid_t child;
124
125	child = SAFE_FORK();
126
127	if (child == 0) {
128		SAFE_CLOSE(fd_notify);
129		generate_events();
130		exit(0);
131	}
132
133	SAFE_WAITPID(child, &status, 0);
134
135	if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
136		tst_brk(TBROK, "Child process terminated incorrectly. Aborting");
137}
138
139static void test_fanotify(unsigned int n)
140{
141	int len = 0;
142	pid_t pid = getpid();
143	unsigned int test_number = 0;
144	struct fanotify_event_metadata *event;
145	struct test_case_t *tc = &test_cases[n];
146	struct passwd *nobody;
147
148	tst_res(TINFO, "Test #%d %s", n, tc->name);
149
150	/* Relinquish privileged user */
151	if (euid == 0) {
152		tst_res(TINFO, "Running as privileged user, revoking");
153		nobody = SAFE_GETPWNAM("nobody");
154		SAFE_SETEUID(nobody->pw_uid);
155	}
156
157	/* Initialize fanotify */
158	fd_notify = fanotify_init(FANOTIFY_REQUIRED_USER_INIT_FLAGS, O_RDONLY);
159
160	if (fd_notify < 0) {
161		if (errno == EPERM || errno == EINVAL) {
162			tst_res(TCONF,
163				"unprivileged fanotify not supported by kernel?");
164			return;
165		}
166
167		tst_brk(TBROK | TERRNO,
168			"fanotify_init(FAN_CLASS_NOTIF, O_RDONLY) failed");
169	}
170
171	/* Place mark on object */
172	if (fanotify_mark(fd_notify, FAN_MARK_ADD, FAN_ALL_EVENTS,
173				AT_FDCWD, TEST_FILE) < 0) {
174		tst_brk(TBROK | TERRNO,
175			"fanotify_mark(%d, FAN_MARK_ADD, %d, "
176			"AT_FDCWD, %s) failed",
177			fd_notify,
178			FAN_ALL_EVENTS,
179			TEST_FILE);
180	}
181
182	/* Generate events in either child or listening process */
183	if (tc->fork)
184		do_fork();
185	else
186		generate_events();
187
188	/* Restore privileges */
189	if (euid == 0 && tc->elevate) {
190		tst_res(TINFO, "Restoring privileged user");
191		SAFE_SETEUID(0);
192	}
193
194	/* Read events from queue */
195	len = SAFE_READ(0, fd_notify, event_buf + len, EVENT_BUF_LEN - len);
196
197	event = event_buf;
198
199	/* Iterate over and validate events against expected result set */
200	while (FAN_EVENT_OK(event, len) && test_number < tc->event_count) {
201		if (!(event->mask & tc->event_set[test_number])) {
202			tst_res(TFAIL,
203				"Received unexpected event mask: mask=%llx "
204				"pid=%u fd=%d",
205				(unsigned long long) event->mask,
206				(unsigned int) event->pid,
207				event->fd);
208		} else if ((!tc->fork && event->pid != pid) ||
209			   (tc->fork && event->pid != 0)) {
210			tst_res(TFAIL,
211				"Received unexpected pid in event: "
212				"mask=%llx pid=%u (expected %u) fd=%d",
213				(unsigned long long) event->mask,
214				(unsigned int) event->pid,
215				(tc->fork ? 0 : pid),
216				event->fd);
217		} else if (event->fd != FAN_NOFD) {
218			tst_res(TFAIL,
219				"Received unexpected file descriptor: "
220				"mask=%llx pid=%u fd=%d (expected %d)",
221				(unsigned long long) event->pid,
222				(unsigned int) event->pid,
223				event->fd,
224				FAN_NOFD);
225			SAFE_CLOSE(event->fd);
226		} else {
227			tst_res(TPASS,
228				"Received event: mask=%llx, pid=%u fd=%d",
229				(unsigned long long) event->mask,
230				(unsigned int) event->pid,
231				event->fd);
232		}
233
234		/* Non-permission events can be merged into a single event. */
235		event->mask &= ~tc->event_set[test_number];
236
237		if (event->mask == 0)
238			event = FAN_EVENT_NEXT(event, len);
239		test_number++;
240	}
241
242	/*
243	 * Determine whether there is still unprocessed events remaining in the
244	 * buffer. This is to cover the basis whereby the event processing loop
245	 * terminates prematurely. In that case, we need to ensure that any
246	 * event file descriptor that is open is closed so that the temporary
247	 * filesystem can be unmounted.
248	 */
249	if (FAN_EVENT_OK(event, len)) {
250		tst_res(TFAIL,
251			"Event processing loop exited prematurely. Did NOT "
252			"finish processing events in buffer. Cleaning up.");
253		while (FAN_EVENT_OK(event, len)) {
254			if (event->fd != FAN_NOFD)
255				SAFE_CLOSE(event->fd);
256			event = FAN_EVENT_NEXT(event, len);
257		}
258	}
259
260	SAFE_CLOSE(fd_notify);
261}
262
263static void setup(void)
264{
265	SAFE_FILE_PRINTF(TEST_FILE, "1");
266	SAFE_CHMOD(TEST_FILE, 0666);
267
268	/* Check for kernel fanotify support */
269	REQUIRE_FANOTIFY_INIT_FLAGS_SUPPORTED_ON_FS(FAN_REPORT_FID, TEST_FILE);
270
271	euid = geteuid();
272}
273
274static void cleanup(void)
275{
276	if (fd_notify > 0)
277		SAFE_CLOSE(fd_notify);
278}
279
280static struct tst_test test = {
281	.test = test_fanotify,
282	.tcnt = ARRAY_SIZE(test_cases),
283	.setup = setup,
284	.cleanup = cleanup,
285	.forks_child = 1,
286	.needs_root = 1,
287	.mount_device = 1,
288	.mntpoint = MOUNT_PATH,
289	.tags = (const struct tst_tag[]) {
290		{"linux-git", "a8b98c808eab"},
291		{}
292	}
293};
294
295#else
296	TST_TEST_TCONF("system doesn't have required fanotify support");
297#endif
298