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
49 static char event_buf[BUF_SIZE];
50 static int fd_notify;
51
52 /* These expected FIDs are common to multiple tests */
53 static struct fanotify_fid_t null_fid;
54 static struct fanotify_fid_t bad_file_fid;
55 static struct fanotify_fid_t bad_link_fid;
56
trigger_fs_abort(void)57 static 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
do_debugfs_request(const char * dev,char * request)63 static 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
trigger_bad_file_lookup(void)70 static 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
trigger_bad_link_lookup(void)81 static 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
tcase3_trigger(void)93 static void tcase3_trigger(void)
94 {
95 trigger_bad_link_lookup();
96 trigger_bad_file_lookup();
97 }
98
tcase4_trigger(void)99 static void tcase4_trigger(void)
100 {
101 trigger_bad_file_lookup();
102 trigger_fs_abort();
103 }
104
105 static 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
check_error_event_info_fid(struct fanotify_event_info_fid * fid,const struct test_case * ex)142 static 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
check_error_event_info_error(struct fanotify_event_info_error * info_error,const struct test_case * ex)175 static 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
check_error_event_metadata(struct fanotify_event_metadata * event)195 static 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
check_event(char * buf,size_t len,const struct test_case * ex)213 static 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
do_test(unsigned int i)249 static 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
pre_corrupt_fs(void)270 static 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
init_null_fid(void)284 static 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
setup(void)295 static 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
cleanup(void)308 static void cleanup(void)
309 {
310 if (fd_notify > 0)
311 SAFE_CLOSE(fd_notify);
312 }
313
314 static 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