You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
8.3 KiB
372 lines
8.3 KiB
/*
|
|
* 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;
|
|
}
|