1 /* 2 * e2fuzz.c -- Fuzz an ext4 image, for testing purposes. 3 * 4 * Copyright (C) 2014 Oracle. 5 * 6 * %Begin-Header% 7 * This file may be redistributed under the terms of the GNU Library 8 * General Public License, version 2. 9 * %End-Header% 10 */ 11 #define _XOPEN_SOURCE 600 12 #define _FILE_OFFSET_BITS 64 13 #define _LARGEFILE64_SOURCE 1 14 #define _GNU_SOURCE 1 15 16 #include "config.h" 17 #include <sys/types.h> 18 #include <sys/stat.h> 19 #include <fcntl.h> 20 #include <stdint.h> 21 #include <unistd.h> 22 #ifdef HAVE_GETOPT_H 23 #include <getopt.h> 24 #endif 25 26 #include "ext2fs/ext2_fs.h" 27 #include "ext2fs/ext2fs.h" 28 29 static int dryrun = 0; 30 static int verbose = 0; 31 static int metadata_only = 1; 32 static unsigned long long user_corrupt_bytes = 0; 33 static double user_corrupt_pct = 0.0; 34 35 #if !defined HAVE_PWRITE64 && !defined HAVE_PWRITE 36 static ssize_t my_pwrite(int fd, const void *buf, size_t count, off_t offset) 37 { 38 if (lseek(fd, offset, SEEK_SET) < 0) 39 return 0; 40 41 return write(fd, buf, count); 42 } 43 #endif /* !defined HAVE_PWRITE64 && !defined HAVE_PWRITE */ 44 45 static int getseed(void) 46 { 47 int r; 48 int fd; 49 50 fd = open("/dev/urandom", O_RDONLY); 51 if (fd < 0) { 52 perror("open"); 53 exit(0); 54 } 55 if (read(fd, &r, sizeof(r)) != sizeof(r)) 56 printf("Unable to read random seed!\n"); 57 close(fd); 58 return r; 59 } 60 61 struct find_block { 62 ext2_ino_t ino; 63 ext2fs_block_bitmap bmap; 64 struct ext2_inode *inode; 65 blk64_t corrupt_blocks; 66 }; 67 68 static int find_block_helper(ext2_filsys fs EXT2FS_ATTR((unused)), 69 blk64_t *blocknr, e2_blkcnt_t blockcnt, 70 blk64_t ref_blk EXT2FS_ATTR((unused)), 71 int ref_offset EXT2FS_ATTR((unused)), 72 void *priv_data) 73 { 74 struct find_block *fb = (struct find_block *)priv_data; 75 76 if (S_ISDIR(fb->inode->i_mode) || !metadata_only || blockcnt < 0) { 77 ext2fs_mark_block_bitmap2(fb->bmap, *blocknr); 78 fb->corrupt_blocks++; 79 } 80 81 return 0; 82 } 83 84 static errcode_t find_metadata_blocks(ext2_filsys fs, ext2fs_block_bitmap bmap, 85 off_t *corrupt_bytes) 86 { 87 dgrp_t i; 88 blk64_t b, c; 89 ext2_inode_scan scan; 90 ext2_ino_t ino; 91 struct ext2_inode inode; 92 struct find_block fb; 93 errcode_t retval; 94 95 *corrupt_bytes = 0; 96 fb.corrupt_blocks = 0; 97 98 /* Construct bitmaps of super/descriptor blocks */ 99 for (i = 0; i < fs->group_desc_count; i++) { 100 ext2fs_reserve_super_and_bgd(fs, i, bmap); 101 102 /* bitmaps and inode table */ 103 b = ext2fs_block_bitmap_loc(fs, i); 104 ext2fs_mark_block_bitmap2(bmap, b); 105 fb.corrupt_blocks++; 106 107 b = ext2fs_inode_bitmap_loc(fs, i); 108 ext2fs_mark_block_bitmap2(bmap, b); 109 fb.corrupt_blocks++; 110 111 c = ext2fs_inode_table_loc(fs, i); 112 ext2fs_mark_block_bitmap_range2(bmap, c, 113 fs->inode_blocks_per_group); 114 fb.corrupt_blocks += fs->inode_blocks_per_group; 115 } 116 117 /* Scan inodes */ 118 fb.bmap = bmap; 119 fb.inode = &inode; 120 memset(&inode, 0, sizeof(inode)); 121 retval = ext2fs_open_inode_scan(fs, 0, &scan); 122 if (retval) 123 goto out; 124 125 retval = ext2fs_get_next_inode_full(scan, &ino, &inode, sizeof(inode)); 126 if (retval) 127 goto out2; 128 while (ino) { 129 if (inode.i_links_count == 0) 130 goto next_loop; 131 132 b = ext2fs_file_acl_block(fs, &inode); 133 if (b) { 134 ext2fs_mark_block_bitmap2(bmap, b); 135 fb.corrupt_blocks++; 136 } 137 138 /* 139 * Inline data, sockets, devices, and symlinks have 140 * no blocks to iterate. 141 */ 142 if ((inode.i_flags & EXT4_INLINE_DATA_FL) || 143 S_ISLNK(inode.i_mode) || S_ISFIFO(inode.i_mode) || 144 S_ISCHR(inode.i_mode) || S_ISBLK(inode.i_mode) || 145 S_ISSOCK(inode.i_mode)) 146 goto next_loop; 147 fb.ino = ino; 148 retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY, 149 NULL, find_block_helper, &fb); 150 if (retval) 151 goto out2; 152 next_loop: 153 retval = ext2fs_get_next_inode_full(scan, &ino, &inode, 154 sizeof(inode)); 155 if (retval) 156 goto out2; 157 } 158 out2: 159 ext2fs_close_inode_scan(scan); 160 out: 161 if (!retval) 162 *corrupt_bytes = fb.corrupt_blocks * fs->blocksize; 163 return retval; 164 } 165 166 static uint64_t rand_num(uint64_t min, uint64_t max) 167 { 168 uint64_t x; 169 unsigned int i; 170 uint8_t *px = (uint8_t *)&x; 171 172 for (i = 0; i < sizeof(x); i++) 173 px[i] = random(); 174 175 return min + (uint64_t)((double)(max - min) * (x / (UINT64_MAX + 1.0))); 176 } 177 178 static int process_fs(const char *fsname) 179 { 180 errcode_t ret; 181 int flags, fd; 182 ext2_filsys fs = NULL; 183 ext2fs_block_bitmap corrupt_map; 184 off_t hsize, count, off, offset, corrupt_bytes; 185 unsigned char c; 186 off_t i; 187 188 /* If mounted rw, force dryrun mode */ 189 ret = ext2fs_check_if_mounted(fsname, &flags); 190 if (ret) { 191 fprintf(stderr, "%s: failed to determine filesystem mount " 192 "state.\n", fsname); 193 return 1; 194 } 195 196 if (!dryrun && (flags & EXT2_MF_MOUNTED) && 197 !(flags & EXT2_MF_READONLY)) { 198 fprintf(stderr, "%s: is mounted rw, performing dry run.\n", 199 fsname); 200 dryrun = 1; 201 } 202 203 /* Ensure the fs is clean and does not have errors */ 204 ret = ext2fs_open(fsname, EXT2_FLAG_64BITS, 0, 0, unix_io_manager, 205 &fs); 206 if (ret) { 207 fprintf(stderr, "%s: failed to open filesystem.\n", 208 fsname); 209 return 1; 210 } 211 212 if ((fs->super->s_state & EXT2_ERROR_FS)) { 213 fprintf(stderr, "%s: errors detected, run fsck.\n", 214 fsname); 215 goto fail; 216 } 217 218 if (!dryrun && (fs->super->s_state & EXT2_VALID_FS) == 0) { 219 fprintf(stderr, "%s: unclean shutdown, performing dry run.\n", 220 fsname); 221 dryrun = 1; 222 } 223 224 /* Construct a bitmap of whatever we're corrupting */ 225 if (!metadata_only) { 226 /* Load block bitmap */ 227 ret = ext2fs_read_block_bitmap(fs); 228 if (ret) { 229 fprintf(stderr, "%s: error while reading block bitmap\n", 230 fsname); 231 goto fail; 232 } 233 corrupt_map = fs->block_map; 234 corrupt_bytes = (ext2fs_blocks_count(fs->super) - 235 ext2fs_free_blocks_count(fs->super)) * 236 fs->blocksize; 237 } else { 238 ret = ext2fs_allocate_block_bitmap(fs, "metadata block map", 239 &corrupt_map); 240 if (ret) { 241 fprintf(stderr, "%s: unable to create block bitmap\n", 242 fsname); 243 goto fail; 244 } 245 246 /* Iterate everything... */ 247 ret = find_metadata_blocks(fs, corrupt_map, &corrupt_bytes); 248 if (ret) { 249 fprintf(stderr, "%s: while finding metadata\n", 250 fsname); 251 goto fail; 252 } 253 } 254 255 /* Run around corrupting things */ 256 fd = open(fsname, O_RDWR); 257 if (fd < 0) { 258 perror(fsname); 259 goto fail; 260 } 261 srandom(getseed()); 262 hsize = fs->blocksize * ext2fs_blocks_count(fs->super); 263 if (user_corrupt_bytes > 0) 264 count = user_corrupt_bytes; 265 else if (user_corrupt_pct > 0.0) 266 count = user_corrupt_pct * corrupt_bytes / 100; 267 else 268 count = rand_num(0, corrupt_bytes / 100); 269 offset = 4096; /* never corrupt superblock */ 270 for (i = 0; i < count; i++) { 271 do 272 off = rand_num(offset, hsize); 273 while (!ext2fs_test_block_bitmap2(corrupt_map, 274 off / fs->blocksize)); 275 c = rand() % 256; 276 if ((rand() % 2) && c < 128) 277 c |= 0x80; 278 if (verbose) 279 printf("Corrupting byte %lld in block %lld to 0x%x\n", 280 (long long) off % fs->blocksize, 281 (long long) off / fs->blocksize, c); 282 if (dryrun) 283 continue; 284 #ifdef HAVE_PWRITE64 285 if (pwrite64(fd, &c, sizeof(c), off) != sizeof(c)) { 286 perror(fsname); 287 goto fail3; 288 } 289 #elif HAVE_PWRITE 290 if (pwrite(fd, &c, sizeof(c), off) != sizeof(c)) { 291 perror(fsname); 292 goto fail3; 293 } 294 #else 295 if (my_pwrite(fd, &c, sizeof(c), off) != sizeof(c)) { 296 perror(fsname); 297 goto fail3; 298 } 299 #endif 300 } 301 close(fd); 302 303 /* Clean up */ 304 ret = ext2fs_close_free(&fs); 305 if (ret) { 306 fprintf(stderr, "%s: error while closing filesystem\n", 307 fsname); 308 return 1; 309 } 310 311 return 0; 312 fail3: 313 close(fd); 314 if (corrupt_map != fs->block_map) 315 ext2fs_free_block_bitmap(corrupt_map); 316 fail: 317 ext2fs_close_free(&fs); 318 return 1; 319 } 320 321 static void print_help(const char *progname) 322 { 323 printf("Usage: %s OPTIONS device\n", progname); 324 printf("-b: Corrupt this many bytes.\n"); 325 printf("-d: Fuzz data blocks too.\n"); 326 printf("-n: Dry run only.\n"); 327 printf("-v: Verbose output.\n"); 328 exit(0); 329 } 330 331 int main(int argc, char *argv[]) 332 { 333 int c; 334 335 while ((c = getopt(argc, argv, "b:dnv")) != -1) { 336 switch (c) { 337 case 'b': 338 if (optarg[strlen(optarg) - 1] == '%') { 339 user_corrupt_pct = strtod(optarg, NULL); 340 if (user_corrupt_pct > 100 || 341 user_corrupt_pct < 0) { 342 fprintf(stderr, "%s: Invalid percentage.\n", 343 optarg); 344 return 1; 345 } 346 } else 347 user_corrupt_bytes = strtoull(optarg, NULL, 0); 348 if (errno) { 349 perror(optarg); 350 return 1; 351 } 352 break; 353 case 'd': 354 metadata_only = 0; 355 break; 356 case 'n': 357 dryrun = 1; 358 break; 359 case 'v': 360 verbose = 1; 361 break; 362 default: 363 print_help(argv[0]); 364 } 365 } 366 367 for (c = optind; c < argc; c++) 368 if (process_fs(argv[c])) 369 return 1; 370 return 0; 371 } 372