summaryrefslogtreecommitdiffstats
path: root/misc/e2fuzz.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc/e2fuzz.c')
-rw-r--r--misc/e2fuzz.c372
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;
+}