/* * e2fuzz.c -- Fuzz an ext4 image, for testing purposes. * * Copyright (C) 2014 Oracle. * * %Begin-Header% * This file may be redistributed under the terms of the GNU Library * General Public License, version 2. * %End-Header% */ #define _XOPEN_SOURCE 600 #define _FILE_OFFSET_BITS 64 #define _LARGEFILE64_SOURCE 1 #define _GNU_SOURCE 1 #include "config.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdint.h> #include <unistd.h> #ifdef HAVE_GETOPT_H #include <getopt.h> #endif #include "ext2fs/ext2_fs.h" #include "ext2fs/ext2fs.h" static int dryrun = 0; static int verbose = 0; static int metadata_only = 1; static unsigned long long user_corrupt_bytes = 0; static double user_corrupt_pct = 0.0; #if !defined HAVE_PWRITE64 && !defined HAVE_PWRITE static ssize_t my_pwrite(int fd, const void *buf, size_t count, off_t offset) { if (lseek(fd, offset, SEEK_SET) < 0) return 0; return write(fd, buf, count); } #endif /* !defined HAVE_PWRITE64 && !defined HAVE_PWRITE */ static int getseed(void) { int r; int fd; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) { perror("open"); exit(0); } if (read(fd, &r, sizeof(r)) != sizeof(r)) printf("Unable to read random seed!\n"); close(fd); return r; } struct find_block { ext2_ino_t ino; ext2fs_block_bitmap bmap; struct ext2_inode *inode; blk64_t corrupt_blocks; }; static int find_block_helper(ext2_filsys fs EXT2FS_ATTR((unused)), blk64_t *blocknr, e2_blkcnt_t blockcnt, blk64_t ref_blk EXT2FS_ATTR((unused)), int ref_offset EXT2FS_ATTR((unused)), void *priv_data) { struct find_block *fb = (struct find_block *)priv_data; if (S_ISDIR(fb->inode->i_mode) || !metadata_only || blockcnt < 0) { ext2fs_mark_block_bitmap2(fb->bmap, *blocknr); fb->corrupt_blocks++; } return 0; } static errcode_t find_metadata_blocks(ext2_filsys fs, ext2fs_block_bitmap bmap, off_t *corrupt_bytes) { dgrp_t i; blk64_t b, c; ext2_inode_scan scan; ext2_ino_t ino; struct ext2_inode inode; struct find_block fb; errcode_t retval; *corrupt_bytes = 0; fb.corrupt_blocks = 0; /* Construct bitmaps of super/descriptor blocks */ for (i = 0; i < fs->group_desc_count; i++) { ext2fs_reserve_super_and_bgd(fs, i, bmap); /* bitmaps and inode table */ b = ext2fs_block_bitmap_loc(fs, i); ext2fs_mark_block_bitmap2(bmap, b); fb.corrupt_blocks++; b = ext2fs_inode_bitmap_loc(fs, i); ext2fs_mark_block_bitmap2(bmap, b); fb.corrupt_blocks++; c = ext2fs_inode_table_loc(fs, i); ext2fs_mark_block_bitmap_range2(bmap, c, fs->inode_blocks_per_group); fb.corrupt_blocks += fs->inode_blocks_per_group; } /* Scan inodes */ fb.bmap = bmap; fb.inode = &inode; memset(&inode, 0, sizeof(inode)); retval = ext2fs_open_inode_scan(fs, 0, &scan); if (retval) goto out; retval = ext2fs_get_next_inode_full(scan, &ino, &inode, sizeof(inode)); if (retval) goto out2; while (ino) { if (inode.i_links_count == 0) goto next_loop; b = ext2fs_file_acl_block(fs, &inode); if (b) { ext2fs_mark_block_bitmap2(bmap, b); fb.corrupt_blocks++; } /* * Inline data, sockets, devices, and symlinks have * no blocks to iterate. */ if ((inode.i_flags & EXT4_INLINE_DATA_FL) || S_ISLNK(inode.i_mode) || S_ISFIFO(inode.i_mode) || S_ISCHR(inode.i_mode) || S_ISBLK(inode.i_mode) || S_ISSOCK(inode.i_mode)) goto next_loop; fb.ino = ino; retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY, NULL, find_block_helper, &fb); if (retval) goto out2; next_loop: retval = ext2fs_get_next_inode_full(scan, &ino, &inode, sizeof(inode)); if (retval) goto out2; } out2: ext2fs_close_inode_scan(scan); out: if (!retval) *corrupt_bytes = fb.corrupt_blocks * fs->blocksize; return retval; } static uint64_t rand_num(uint64_t min, uint64_t max) { uint64_t x; unsigned int i; uint8_t *px = (uint8_t *)&x; for (i = 0; i < sizeof(x); i++) px[i] = random(); return min + (uint64_t)((double)(max - min) * (x / (UINT64_MAX + 1.0))); } static int process_fs(const char *fsname) { errcode_t ret; int flags, fd; ext2_filsys fs = NULL; ext2fs_block_bitmap corrupt_map; off_t hsize, count, off, offset, corrupt_bytes; unsigned char c; off_t i; /* If mounted rw, force dryrun mode */ ret = ext2fs_check_if_mounted(fsname, &flags); if (ret) { fprintf(stderr, "%s: failed to determine filesystem mount " "state.\n", fsname); return 1; } if (!dryrun && (flags & EXT2_MF_MOUNTED) && !(flags & EXT2_MF_READONLY)) { fprintf(stderr, "%s: is mounted rw, performing dry run.\n", fsname); dryrun = 1; } /* Ensure the fs is clean and does not have errors */ ret = ext2fs_open(fsname, EXT2_FLAG_64BITS, 0, 0, unix_io_manager, &fs); if (ret) { fprintf(stderr, "%s: failed to open filesystem.\n", fsname); return 1; } if ((fs->super->s_state & EXT2_ERROR_FS)) { fprintf(stderr, "%s: errors detected, run fsck.\n", fsname); goto fail; } if (!dryrun && (fs->super->s_state & EXT2_VALID_FS) == 0) { fprintf(stderr, "%s: unclean shutdown, performing dry run.\n", fsname); dryrun = 1; } /* Construct a bitmap of whatever we're corrupting */ if (!metadata_only) { /* Load block bitmap */ ret = ext2fs_read_block_bitmap(fs); if (ret) { fprintf(stderr, "%s: error while reading block bitmap\n", fsname); goto fail; } corrupt_map = fs->block_map; corrupt_bytes = (ext2fs_blocks_count(fs->super) - ext2fs_free_blocks_count(fs->super)) * fs->blocksize; } else { ret = ext2fs_allocate_block_bitmap(fs, "metadata block map", &corrupt_map); if (ret) { fprintf(stderr, "%s: unable to create block bitmap\n", fsname); goto fail; } /* Iterate everything... */ ret = find_metadata_blocks(fs, corrupt_map, &corrupt_bytes); if (ret) { fprintf(stderr, "%s: while finding metadata\n", fsname); goto fail; } } /* Run around corrupting things */ fd = open(fsname, O_RDWR); if (fd < 0) { perror(fsname); goto fail; } srandom(getseed()); hsize = fs->blocksize * ext2fs_blocks_count(fs->super); if (user_corrupt_bytes > 0) count = user_corrupt_bytes; else if (user_corrupt_pct > 0.0) count = user_corrupt_pct * corrupt_bytes / 100; else count = rand_num(0, corrupt_bytes / 100); offset = 4096; /* never corrupt superblock */ for (i = 0; i < count; i++) { do off = rand_num(offset, hsize); while (!ext2fs_test_block_bitmap2(corrupt_map, off / fs->blocksize)); c = rand() % 256; if ((rand() % 2) && c < 128) c |= 0x80; if (verbose) printf("Corrupting byte %lld in block %lld to 0x%x\n", (long long) off % fs->blocksize, (long long) off / fs->blocksize, c); if (dryrun) continue; #ifdef HAVE_PWRITE64 if (pwrite64(fd, &c, sizeof(c), off) != sizeof(c)) { perror(fsname); goto fail3; } #elif HAVE_PWRITE if (pwrite(fd, &c, sizeof(c), off) != sizeof(c)) { perror(fsname); goto fail3; } #else if (my_pwrite(fd, &c, sizeof(c), off) != sizeof(c)) { perror(fsname); goto fail3; } #endif } close(fd); /* Clean up */ ret = ext2fs_close_free(&fs); if (ret) { fprintf(stderr, "%s: error while closing filesystem\n", fsname); return 1; } return 0; fail3: close(fd); if (corrupt_map != fs->block_map) ext2fs_free_block_bitmap(corrupt_map); fail: ext2fs_close_free(&fs); return 1; } static void print_help(const char *progname) { printf("Usage: %s OPTIONS device\n", progname); printf("-b: Corrupt this many bytes.\n"); printf("-d: Fuzz data blocks too.\n"); printf("-n: Dry run only.\n"); printf("-v: Verbose output.\n"); exit(0); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "b:dnv")) != -1) { switch (c) { case 'b': if (optarg[strlen(optarg) - 1] == '%') { user_corrupt_pct = strtod(optarg, NULL); if (user_corrupt_pct > 100 || user_corrupt_pct < 0) { fprintf(stderr, "%s: Invalid percentage.\n", optarg); return 1; } } else user_corrupt_bytes = strtoull(optarg, NULL, 0); if (errno) { perror(optarg); return 1; } break; case 'd': metadata_only = 0; break; case 'n': dryrun = 1; break; case 'v': verbose = 1; break; default: print_help(argv[0]); } } for (c = optind; c < argc; c++) if (process_fs(argv[c])) return 1; return 0; }