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