1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (c) 2021 Collabora Ltd.
4 *
5 * Author: Gabriel Krisman Bertazi <gabriel@krisman.be>
6 * Based on previous work by Amir Goldstein <amir73il@gmail.com>
7 */
8
9/*\
10 * [Description]
11 * Check fanotify FAN_ERROR_FS events triggered by intentionally
12 * corrupted filesystems:
13 *
14 * - Generate a broken filesystem
15 * - Start FAN_FS_ERROR monitoring group
16 * - Make the file system notice the error through ordinary operations
17 * - Observe the event generated
18 */
19
20#define _GNU_SOURCE
21#include "config.h"
22
23#include <stdio.h>
24#include <sys/types.h>
25#include <errno.h>
26#include <string.h>
27#include <sys/mount.h>
28#include <sys/syscall.h>
29#include "tst_test.h"
30#include <sys/fanotify.h>
31#include <sys/types.h>
32
33#ifdef HAVE_SYS_FANOTIFY_H
34#include "fanotify.h"
35
36#ifndef EFSCORRUPTED
37#define EFSCORRUPTED    EUCLEAN         /* Filesystem is corrupted */
38#endif
39
40#define BUF_SIZE 256
41
42#define MOUNT_PATH "test_mnt"
43#define BASE_DIR "internal_dir"
44#define BAD_DIR BASE_DIR"/bad_dir"
45#define BAD_LINK BASE_DIR"/bad_link"
46
47#ifdef HAVE_NAME_TO_HANDLE_AT
48
49static char event_buf[BUF_SIZE];
50static int fd_notify;
51
52/* These expected FIDs are common to multiple tests */
53static struct fanotify_fid_t null_fid;
54static struct fanotify_fid_t bad_file_fid;
55static struct fanotify_fid_t bad_link_fid;
56
57static void trigger_fs_abort(void)
58{
59	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type,
60		   MS_REMOUNT|MS_RDONLY, "abort");
61}
62
63static void do_debugfs_request(const char *dev, char *request)
64{
65	const char *const cmd[] = {"debugfs", "-w", dev, "-R", request, NULL};
66
67	SAFE_CMD(cmd, NULL, NULL);
68}
69
70static void trigger_bad_file_lookup(void)
71{
72	int ret;
73
74	/* SAFE_OPEN cannot be used here because we expect it to fail. */
75	ret = open(MOUNT_PATH"/"BAD_DIR, O_RDONLY, 0);
76	if (ret != -1 && errno != EUCLEAN)
77		tst_res(TFAIL, "Unexpected lookup result(%d) of %s (%d!=%d)",
78			ret, BAD_DIR, errno, EUCLEAN);
79}
80
81static void trigger_bad_link_lookup(void)
82{
83	int ret;
84
85	/* SAFE_OPEN cannot be used here because we expect it to fail. */
86	ret = open(MOUNT_PATH"/"BAD_LINK, O_RDONLY, 0);
87	if (ret != -1 && errno != EUCLEAN)
88		tst_res(TFAIL, "Unexpected open result(%d) of %s (%d!=%d)",
89			ret, BAD_LINK, errno, EUCLEAN);
90}
91
92
93static void tcase3_trigger(void)
94{
95	trigger_bad_link_lookup();
96	trigger_bad_file_lookup();
97}
98
99static void tcase4_trigger(void)
100{
101	trigger_bad_file_lookup();
102	trigger_fs_abort();
103}
104
105static struct test_case {
106	char *name;
107	int error;
108	unsigned int error_count;
109	struct fanotify_fid_t *fid;
110	void (*trigger_error)(void);
111} testcases[] = {
112	{
113		.name = "Trigger abort",
114		.trigger_error = &trigger_fs_abort,
115		.error_count = 1,
116		.error = ESHUTDOWN,
117		.fid = &null_fid,
118	},
119	{
120		.name = "Lookup of inode with invalid mode",
121		.trigger_error = &trigger_bad_file_lookup,
122		.error_count = 1,
123		.error = EFSCORRUPTED,
124		.fid = &bad_file_fid,
125	},
126	{
127		.name = "Multiple error submission",
128		.trigger_error = &tcase3_trigger,
129		.error_count = 2,
130		.error = EFSCORRUPTED,
131		.fid = &bad_link_fid,
132	},
133	{
134		.name = "Multiple error submission 2",
135		.trigger_error = &tcase4_trigger,
136		.error_count = 2,
137		.error = EFSCORRUPTED,
138		.fid = &bad_file_fid,
139	}
140};
141
142static int check_error_event_info_fid(struct fanotify_event_info_fid *fid,
143				 const struct test_case *ex)
144{
145	struct file_handle *fh = (struct file_handle *) &fid->handle;
146
147	if (memcmp(&fid->fsid, &ex->fid->fsid, sizeof(fid->fsid))) {
148		tst_res(TFAIL, "%s: Received bad FSID type (%x...!=%x...)",
149			ex->name, FSID_VAL_MEMBER(fid->fsid, 0),
150			ex->fid->fsid.val[0]);
151
152		return 1;
153	}
154	if (fh->handle_type != ex->fid->handle.handle_type) {
155		tst_res(TFAIL, "%s: Received bad file_handle type (%d!=%d)",
156			ex->name, fh->handle_type, ex->fid->handle.handle_type);
157		return 1;
158	}
159
160	if (fh->handle_bytes != ex->fid->handle.handle_bytes) {
161		tst_res(TFAIL, "%s: Received bad file_handle len (%d!=%d)",
162			ex->name, fh->handle_bytes, ex->fid->handle.handle_bytes);
163		return 1;
164	}
165
166	if (memcmp(fh->f_handle, ex->fid->handle.f_handle, fh->handle_bytes)) {
167		tst_res(TFAIL, "%s: Received wrong handle. "
168			"Expected (%x...) got (%x...) ", ex->name,
169			*(int *)ex->fid->handle.f_handle, *(int *)fh->f_handle);
170		return 1;
171	}
172	return 0;
173}
174
175static int check_error_event_info_error(struct fanotify_event_info_error *info_error,
176				 const struct test_case *ex)
177{
178	int fail = 0;
179
180	if (info_error->error_count != ex->error_count) {
181		tst_res(TFAIL, "%s: Unexpected error_count (%d!=%d)",
182			ex->name, info_error->error_count, ex->error_count);
183		fail++;
184	}
185
186	if (info_error->error != ex->error) {
187		tst_res(TFAIL, "%s: Unexpected error code value (%d!=%d)",
188			ex->name, info_error->error, ex->error);
189		fail++;
190	}
191
192	return fail;
193}
194
195static int check_error_event_metadata(struct fanotify_event_metadata *event)
196{
197	int fail = 0;
198
199	if (event->mask != FAN_FS_ERROR) {
200		fail++;
201		tst_res(TFAIL, "got unexpected event %llx",
202			(unsigned long long)event->mask);
203	}
204
205	if (event->fd != FAN_NOFD) {
206		fail++;
207		tst_res(TFAIL, "Weird FAN_FD %llx",
208			(unsigned long long)event->mask);
209	}
210	return fail;
211}
212
213static void check_event(char *buf, size_t len, const struct test_case *ex)
214{
215	struct fanotify_event_metadata *event =
216		(struct fanotify_event_metadata *) buf;
217	struct fanotify_event_info_error *info_error;
218	struct fanotify_event_info_fid *info_fid;
219	int fail = 0;
220
221	if (len < FAN_EVENT_METADATA_LEN) {
222		tst_res(TFAIL, "No event metadata found");
223		return;
224	}
225
226	if (check_error_event_metadata(event))
227		return;
228
229	info_error = get_event_info_error(event);
230	if (info_error)
231		fail += check_error_event_info_error(info_error, ex);
232	else {
233		tst_res(TFAIL, "Generic error record not found");
234		fail++;
235	}
236
237	info_fid = get_event_info_fid(event);
238	if (info_fid)
239		fail += check_error_event_info_fid(info_fid, ex);
240	else {
241		tst_res(TFAIL, "FID record not found");
242		fail++;
243	}
244
245	if (!fail)
246		tst_res(TPASS, "Successfully received: %s", ex->name);
247}
248
249static void do_test(unsigned int i)
250{
251	const struct test_case *tcase = &testcases[i];
252	size_t read_len;
253
254	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD|FAN_MARK_FILESYSTEM,
255			   FAN_FS_ERROR, AT_FDCWD, MOUNT_PATH);
256
257	tcase->trigger_error();
258
259	read_len = SAFE_READ(0, fd_notify, event_buf, BUF_SIZE);
260
261	SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_REMOVE|FAN_MARK_FILESYSTEM,
262			   FAN_FS_ERROR, AT_FDCWD, MOUNT_PATH);
263
264	check_event(event_buf, read_len, tcase);
265	/* Unmount and mount the filesystem to get it out of the error state */
266	SAFE_UMOUNT(MOUNT_PATH);
267	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type, 0, NULL);
268}
269
270static void pre_corrupt_fs(void)
271{
272	SAFE_MKDIR(MOUNT_PATH"/"BASE_DIR, 0777);
273	SAFE_MKDIR(MOUNT_PATH"/"BAD_DIR, 0777);
274
275	fanotify_save_fid(MOUNT_PATH"/"BAD_DIR, &bad_file_fid);
276	fanotify_save_fid(MOUNT_PATH"/"BASE_DIR, &bad_link_fid);
277
278	SAFE_UMOUNT(MOUNT_PATH);
279	do_debugfs_request(tst_device->dev, "sif " BAD_DIR " mode 0xff");
280	do_debugfs_request(tst_device->dev, "ln <1> " BAD_LINK);
281	SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type, 0, NULL);
282}
283
284static void init_null_fid(void)
285{
286	/* Use fanotify_save_fid to fill the fsid and overwrite the
287	 * file_handler to create a null_fid
288	 */
289	fanotify_save_fid(MOUNT_PATH, &null_fid);
290
291	null_fid.handle.handle_type = FILEID_INVALID;
292	null_fid.handle.handle_bytes = 0;
293}
294
295static void setup(void)
296{
297	REQUIRE_FANOTIFY_EVENTS_SUPPORTED_ON_FS(FAN_CLASS_NOTIF|FAN_REPORT_FID,
298						FAN_MARK_FILESYSTEM,
299						FAN_FS_ERROR, ".");
300	pre_corrupt_fs();
301
302	fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF|FAN_REPORT_FID,
303				       O_RDONLY);
304
305	init_null_fid();
306}
307
308static void cleanup(void)
309{
310	if (fd_notify > 0)
311		SAFE_CLOSE(fd_notify);
312}
313
314static struct tst_test test = {
315	.test = do_test,
316	.tcnt = ARRAY_SIZE(testcases),
317	.setup = setup,
318	.cleanup = cleanup,
319	.mount_device = 1,
320	.mntpoint = MOUNT_PATH,
321	.needs_root = 1,
322	.dev_fs_type = "ext4",
323	.tags = (const struct tst_tag[]) {
324		{"linux-git", "124e7c61deb2"},
325		{}
326	},
327	.needs_cmds = (const char *[]) {
328		"debugfs",
329		NULL
330	}
331};
332
333#else
334	TST_TEST_TCONF("system does not have required name_to_handle_at() support");
335#endif
336#else
337	TST_TEST_TCONF("system doesn't have required fanotify support");
338#endif
339