diff options
Diffstat (limited to 'misc/e2fuzz.c')
-rw-r--r-- | misc/e2fuzz.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/misc/e2fuzz.c b/misc/e2fuzz.c new file mode 100644 index 0000000..0ceece9 --- /dev/null +++ b/misc/e2fuzz.c @@ -0,0 +1,372 @@ +/* + * 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, + ext2_loff_t offset) +{ + if (ext2fs_llseek(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, + ext2_loff_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 / ((double) 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; + ext2_loff_t hsize, count, off, offset, corrupt_bytes, i; + unsigned char c; + + /* 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 | EXT2_FLAG_THREADS, + 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", + off % fs->blocksize, + 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; +} |