Home | History | Annotate | Download | only in misc
      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