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