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
46 #ifdef HAVE_NAME_TO_HANDLE_AT
47
48 static char event_buf[BUF_SIZE];
49 int fd_notify;
50
51 /* These expected FIDs are common to multiple tests */
52 static struct fanotify_fid_t null_fid;
53 static struct fanotify_fid_t bad_file_fid;
54
trigger_fs_abort(void)55 static void trigger_fs_abort(void)
56 {
57 SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type,
58 MS_REMOUNT|MS_RDONLY, "abort");
59 }
60
do_debugfs_request(const char * dev,char * request)61 static void do_debugfs_request(const char *dev, char *request)
62 {
63 const char *const cmd[] = {"debugfs", "-w", dev, "-R", request, NULL};
64
65 SAFE_CMD(cmd, NULL, NULL);
66 }
67
tcase2_trigger_lookup(void)68 static void tcase2_trigger_lookup(void)
69 {
70 int ret;
71
72 /* SAFE_OPEN cannot be used here because we expect it to fail. */
73 ret = open(MOUNT_PATH"/"BAD_DIR, O_RDONLY, 0);
74 if (ret != -1 && errno != EUCLEAN)
75 tst_res(TFAIL, "Unexpected lookup result(%d) of %s (%d!=%d)",
76 ret, BAD_DIR, errno, EUCLEAN);
77 }
78
tcase3_trigger(void)79 static void tcase3_trigger(void)
80 {
81 trigger_fs_abort();
82 tcase2_trigger_lookup();
83 }
84
tcase4_trigger(void)85 static void tcase4_trigger(void)
86 {
87 tcase2_trigger_lookup();
88 trigger_fs_abort();
89 }
90
91 static struct test_case {
92 char *name;
93 int error;
94 unsigned int error_count;
95 struct fanotify_fid_t *fid;
96 void (*trigger_error)(void);
97 } testcases[] = {
98 {
99 .name = "Trigger abort",
100 .trigger_error = &trigger_fs_abort,
101 .error_count = 1,
102 .error = ESHUTDOWN,
103 .fid = &null_fid,
104 },
105 {
106 .name = "Lookup of inode with invalid mode",
107 .trigger_error = &tcase2_trigger_lookup,
108 .error_count = 1,
109 .error = EFSCORRUPTED,
110 .fid = &bad_file_fid,
111 },
112 {
113 .name = "Multiple error submission",
114 .trigger_error = &tcase3_trigger,
115 .error_count = 2,
116 .error = ESHUTDOWN,
117 .fid = &null_fid,
118 },
119 {
120 .name = "Multiple error submission 2",
121 .trigger_error = &tcase4_trigger,
122 .error_count = 2,
123 .error = EFSCORRUPTED,
124 .fid = &bad_file_fid,
125 }
126 };
127
check_error_event_info_fid(struct fanotify_event_info_fid * fid,const struct test_case * ex)128 int check_error_event_info_fid(struct fanotify_event_info_fid *fid,
129 const struct test_case *ex)
130 {
131 struct file_handle *fh = (struct file_handle *) &fid->handle;
132
133 if (memcmp(&fid->fsid, &ex->fid->fsid, sizeof(fid->fsid))) {
134 tst_res(TFAIL, "%s: Received bad FSID type (%x...!=%x...)",
135 ex->name, FSID_VAL_MEMBER(fid->fsid, 0),
136 ex->fid->fsid.val[0]);
137
138 return 1;
139 }
140 if (fh->handle_type != ex->fid->handle.handle_type) {
141 tst_res(TFAIL, "%s: Received bad file_handle type (%d!=%d)",
142 ex->name, fh->handle_type, ex->fid->handle.handle_type);
143 return 1;
144 }
145
146 if (fh->handle_bytes != ex->fid->handle.handle_bytes) {
147 tst_res(TFAIL, "%s: Received bad file_handle len (%d!=%d)",
148 ex->name, fh->handle_bytes, ex->fid->handle.handle_bytes);
149 return 1;
150 }
151
152 if (memcmp(fh->f_handle, ex->fid->handle.f_handle, fh->handle_bytes)) {
153 tst_res(TFAIL, "%s: Received wrong handle. "
154 "Expected (%x...) got (%x...) ", ex->name,
155 *(int *)ex->fid->handle.f_handle, *(int *)fh->f_handle);
156 return 1;
157 }
158 return 0;
159 }
160
check_error_event_info_error(struct fanotify_event_info_error * info_error,const struct test_case * ex)161 int check_error_event_info_error(struct fanotify_event_info_error *info_error,
162 const struct test_case *ex)
163 {
164 int fail = 0;
165
166 if (info_error->error_count != ex->error_count) {
167 tst_res(TFAIL, "%s: Unexpected error_count (%d!=%d)",
168 ex->name, info_error->error_count, ex->error_count);
169 fail++;
170 }
171
172 if (info_error->error != ex->error) {
173 tst_res(TFAIL, "%s: Unexpected error code value (%d!=%d)",
174 ex->name, info_error->error, ex->error);
175 fail++;
176 }
177
178 return fail;
179 }
180
check_error_event_metadata(struct fanotify_event_metadata * event)181 int check_error_event_metadata(struct fanotify_event_metadata *event)
182 {
183 int fail = 0;
184
185 if (event->mask != FAN_FS_ERROR) {
186 fail++;
187 tst_res(TFAIL, "got unexpected event %llx",
188 (unsigned long long)event->mask);
189 }
190
191 if (event->fd != FAN_NOFD) {
192 fail++;
193 tst_res(TFAIL, "Weird FAN_FD %llx",
194 (unsigned long long)event->mask);
195 }
196 return fail;
197 }
198
check_event(char * buf,size_t len,const struct test_case * ex)199 void check_event(char *buf, size_t len, const struct test_case *ex)
200 {
201 struct fanotify_event_metadata *event =
202 (struct fanotify_event_metadata *) buf;
203 struct fanotify_event_info_error *info_error;
204 struct fanotify_event_info_fid *info_fid;
205 int fail = 0;
206
207 if (len < FAN_EVENT_METADATA_LEN) {
208 tst_res(TFAIL, "No event metadata found");
209 return;
210 }
211
212 if (check_error_event_metadata(event))
213 return;
214
215 info_error = get_event_info_error(event);
216 if (info_error)
217 fail += check_error_event_info_error(info_error, ex);
218 else {
219 tst_res(TFAIL, "Generic error record not found");
220 fail++;
221 }
222
223 info_fid = get_event_info_fid(event);
224 if (info_fid)
225 fail += check_error_event_info_fid(info_fid, ex);
226 else {
227 tst_res(TFAIL, "FID record not found");
228 fail++;
229 }
230
231 if (!fail)
232 tst_res(TPASS, "Successfully received: %s", ex->name);
233 }
234
do_test(unsigned int i)235 static void do_test(unsigned int i)
236 {
237 const struct test_case *tcase = &testcases[i];
238 size_t read_len;
239
240 SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_ADD|FAN_MARK_FILESYSTEM,
241 FAN_FS_ERROR, AT_FDCWD, MOUNT_PATH);
242
243 tcase->trigger_error();
244
245 read_len = SAFE_READ(0, fd_notify, event_buf, BUF_SIZE);
246
247 SAFE_FANOTIFY_MARK(fd_notify, FAN_MARK_REMOVE|FAN_MARK_FILESYSTEM,
248 FAN_FS_ERROR, AT_FDCWD, MOUNT_PATH);
249
250 check_event(event_buf, read_len, tcase);
251 }
252
pre_corrupt_fs(void)253 static void pre_corrupt_fs(void)
254 {
255 SAFE_MKDIR(MOUNT_PATH"/"BASE_DIR, 0777);
256 SAFE_MKDIR(MOUNT_PATH"/"BAD_DIR, 0777);
257
258 fanotify_save_fid(MOUNT_PATH"/"BAD_DIR, &bad_file_fid);
259
260 SAFE_UMOUNT(MOUNT_PATH);
261 do_debugfs_request(tst_device->dev, "sif " BAD_DIR " mode 0xff");
262 SAFE_MOUNT(tst_device->dev, MOUNT_PATH, tst_device->fs_type, 0, NULL);
263 }
264
init_null_fid(void)265 static void init_null_fid(void)
266 {
267 /* Use fanotify_save_fid to fill the fsid and overwrite the
268 * file_handler to create a null_fid
269 */
270 fanotify_save_fid(MOUNT_PATH, &null_fid);
271
272 null_fid.handle.handle_type = FILEID_INVALID;
273 null_fid.handle.handle_bytes = 0;
274 }
275
setup(void)276 static void setup(void)
277 {
278 REQUIRE_FANOTIFY_EVENTS_SUPPORTED_ON_FS(FAN_CLASS_NOTIF|FAN_REPORT_FID,
279 FAN_MARK_FILESYSTEM,
280 FAN_FS_ERROR, ".");
281 pre_corrupt_fs();
282
283 fd_notify = SAFE_FANOTIFY_INIT(FAN_CLASS_NOTIF|FAN_REPORT_FID,
284 O_RDONLY);
285
286 init_null_fid();
287 }
288
cleanup(void)289 static void cleanup(void)
290 {
291 if (fd_notify > 0)
292 SAFE_CLOSE(fd_notify);
293 }
294
295 static struct tst_test test = {
296 .test = do_test,
297 .tcnt = ARRAY_SIZE(testcases),
298 .setup = setup,
299 .cleanup = cleanup,
300 .mount_device = 1,
301 .mntpoint = MOUNT_PATH,
302 .needs_root = 1,
303 .dev_fs_type = "ext4",
304 .tags = (const struct tst_tag[]) {
305 {"linux-git", "124e7c61deb2"},
306 {}
307 },
308 .needs_cmds = (const char *[]) {
309 "debugfs",
310 NULL
311 }
312 };
313
314 #else
315 TST_TEST_TCONF("system does not have required name_to_handle_at() support");
316 #endif
317 #else
318 TST_TEST_TCONF("system doesn't have required fanotify support");
319 #endif
320