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