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
my_pwrite(int fd,const void * buf,size_t count,ext2_loff_t offset)36 static ssize_t my_pwrite(int fd, const void *buf, size_t count,
37 			 ext2_loff_t offset)
38 {
39 	if (ext2fs_llseek(fd, offset, SEEK_SET) < 0)
40 		return 0;
41 
42 	return write(fd, buf, count);
43 }
44 #endif /* !defined HAVE_PWRITE64 && !defined HAVE_PWRITE */
45 
getseed(void)46 static int getseed(void)
47 {
48 	int r;
49 	int fd;
50 
51 	fd = open("/dev/urandom", O_RDONLY);
52 	if (fd < 0) {
53 		perror("open");
54 		exit(0);
55 	}
56 	if (read(fd, &r, sizeof(r)) != sizeof(r))
57 		printf("Unable to read random seed!\n");
58 	close(fd);
59 	return r;
60 }
61 
62 struct find_block {
63 	ext2_ino_t ino;
64 	ext2fs_block_bitmap bmap;
65 	struct ext2_inode *inode;
66 	blk64_t corrupt_blocks;
67 };
68 
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)69 static int find_block_helper(ext2_filsys fs EXT2FS_ATTR((unused)),
70 			     blk64_t *blocknr, e2_blkcnt_t blockcnt,
71 			     blk64_t ref_blk EXT2FS_ATTR((unused)),
72 			     int ref_offset EXT2FS_ATTR((unused)),
73 			     void *priv_data)
74 {
75 	struct find_block *fb = (struct find_block *)priv_data;
76 
77 	if (S_ISDIR(fb->inode->i_mode) || !metadata_only || blockcnt < 0) {
78 		ext2fs_mark_block_bitmap2(fb->bmap, *blocknr);
79 		fb->corrupt_blocks++;
80 	}
81 
82 	return 0;
83 }
84 
find_metadata_blocks(ext2_filsys fs,ext2fs_block_bitmap bmap,ext2_loff_t * corrupt_bytes)85 static errcode_t find_metadata_blocks(ext2_filsys fs, ext2fs_block_bitmap bmap,
86 				      ext2_loff_t *corrupt_bytes)
87 {
88 	dgrp_t i;
89 	blk64_t b, c;
90 	ext2_inode_scan scan;
91 	ext2_ino_t ino;
92 	struct ext2_inode inode;
93 	struct find_block fb;
94 	errcode_t retval;
95 
96 	*corrupt_bytes = 0;
97 	fb.corrupt_blocks = 0;
98 
99 	/* Construct bitmaps of super/descriptor blocks */
100 	for (i = 0; i < fs->group_desc_count; i++) {
101 		ext2fs_reserve_super_and_bgd(fs, i, bmap);
102 
103 		/* bitmaps and inode table */
104 		b = ext2fs_block_bitmap_loc(fs, i);
105 		ext2fs_mark_block_bitmap2(bmap, b);
106 		fb.corrupt_blocks++;
107 
108 		b = ext2fs_inode_bitmap_loc(fs, i);
109 		ext2fs_mark_block_bitmap2(bmap, b);
110 		fb.corrupt_blocks++;
111 
112 		c = ext2fs_inode_table_loc(fs, i);
113 		ext2fs_mark_block_bitmap_range2(bmap, c,
114 						fs->inode_blocks_per_group);
115 		fb.corrupt_blocks += fs->inode_blocks_per_group;
116 	}
117 
118 	/* Scan inodes */
119 	fb.bmap = bmap;
120 	fb.inode = &inode;
121 	memset(&inode, 0, sizeof(inode));
122 	retval = ext2fs_open_inode_scan(fs, 0, &scan);
123 	if (retval)
124 		goto out;
125 
126 	retval = ext2fs_get_next_inode_full(scan, &ino, &inode, sizeof(inode));
127 	if (retval)
128 		goto out2;
129 	while (ino) {
130 		if (inode.i_links_count == 0)
131 			goto next_loop;
132 
133 		b = ext2fs_file_acl_block(fs, &inode);
134 		if (b) {
135 			ext2fs_mark_block_bitmap2(bmap, b);
136 			fb.corrupt_blocks++;
137 		}
138 
139 		/*
140 		 * Inline data, sockets, devices, and symlinks have
141 		 * no blocks to iterate.
142 		 */
143 		if ((inode.i_flags & EXT4_INLINE_DATA_FL) ||
144 		    S_ISLNK(inode.i_mode) || S_ISFIFO(inode.i_mode) ||
145 		    S_ISCHR(inode.i_mode) || S_ISBLK(inode.i_mode) ||
146 		    S_ISSOCK(inode.i_mode))
147 			goto next_loop;
148 		fb.ino = ino;
149 		retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY,
150 					       NULL, find_block_helper, &fb);
151 		if (retval)
152 			goto out2;
153 next_loop:
154 		retval = ext2fs_get_next_inode_full(scan, &ino, &inode,
155 						    sizeof(inode));
156 		if (retval)
157 			goto out2;
158 	}
159 out2:
160 	ext2fs_close_inode_scan(scan);
161 out:
162 	if (!retval)
163 		*corrupt_bytes = fb.corrupt_blocks * fs->blocksize;
164 	return retval;
165 }
166 
rand_num(uint64_t min,uint64_t max)167 static uint64_t rand_num(uint64_t min, uint64_t max)
168 {
169 	uint64_t x;
170 	unsigned int i;
171 	uint8_t *px = (uint8_t *)&x;
172 
173 	for (i = 0; i < sizeof(x); i++)
174 		px[i] = random();
175 
176 	return min + (uint64_t)((double)(max - min) *
177 				(x / ((double) UINT64_MAX + 1.0)));
178 }
179 
process_fs(const char * fsname)180 static int process_fs(const char *fsname)
181 {
182 	errcode_t ret;
183 	int flags, fd;
184 	ext2_filsys fs = NULL;
185 	ext2fs_block_bitmap corrupt_map;
186 	ext2_loff_t hsize, count, off, offset, corrupt_bytes, i;
187 	unsigned char c;
188 
189 	/* If mounted rw, force dryrun mode */
190 	ret = ext2fs_check_if_mounted(fsname, &flags);
191 	if (ret) {
192 		fprintf(stderr, "%s: failed to determine filesystem mount "
193 			"state.\n", fsname);
194 		return 1;
195 	}
196 
197 	if (!dryrun && (flags & EXT2_MF_MOUNTED) &&
198 	    !(flags & EXT2_MF_READONLY)) {
199 		fprintf(stderr, "%s: is mounted rw, performing dry run.\n",
200 			fsname);
201 		dryrun = 1;
202 	}
203 
204 	/* Ensure the fs is clean and does not have errors */
205 	ret = ext2fs_open(fsname, EXT2_FLAG_64BITS | EXT2_FLAG_THREADS,
206 			  0, 0, unix_io_manager, &fs);
207 	if (ret) {
208 		fprintf(stderr, "%s: failed to open filesystem.\n",
209 			fsname);
210 		return 1;
211 	}
212 
213 	if ((fs->super->s_state & EXT2_ERROR_FS)) {
214 		fprintf(stderr, "%s: errors detected, run fsck.\n",
215 			fsname);
216 		goto fail;
217 	}
218 
219 	if (!dryrun && (fs->super->s_state & EXT2_VALID_FS) == 0) {
220 		fprintf(stderr, "%s: unclean shutdown, performing dry run.\n",
221 			fsname);
222 		dryrun = 1;
223 	}
224 
225 	/* Construct a bitmap of whatever we're corrupting */
226 	if (!metadata_only) {
227 		/* Load block bitmap */
228 		ret = ext2fs_read_block_bitmap(fs);
229 		if (ret) {
230 			fprintf(stderr, "%s: error while reading block bitmap\n",
231 				fsname);
232 			goto fail;
233 		}
234 		corrupt_map = fs->block_map;
235 		corrupt_bytes = (ext2fs_blocks_count(fs->super) -
236 				 ext2fs_free_blocks_count(fs->super)) *
237 				fs->blocksize;
238 	} else {
239 		ret = ext2fs_allocate_block_bitmap(fs, "metadata block map",
240 						   &corrupt_map);
241 		if (ret) {
242 			fprintf(stderr, "%s: unable to create block bitmap\n",
243 				fsname);
244 			goto fail;
245 		}
246 
247 		/* Iterate everything... */
248 		ret = find_metadata_blocks(fs, corrupt_map, &corrupt_bytes);
249 		if (ret) {
250 			fprintf(stderr, "%s: while finding metadata\n",
251 				fsname);
252 			goto fail;
253 		}
254 	}
255 
256 	/* Run around corrupting things */
257 	fd = open(fsname, O_RDWR);
258 	if (fd < 0) {
259 		perror(fsname);
260 		goto fail;
261 	}
262 	srandom(getseed());
263 	hsize = fs->blocksize * ext2fs_blocks_count(fs->super);
264 	if (user_corrupt_bytes > 0)
265 		count = user_corrupt_bytes;
266 	else if (user_corrupt_pct > 0.0)
267 		count = user_corrupt_pct * corrupt_bytes / 100;
268 	else
269 		count = rand_num(0, corrupt_bytes / 100);
270 	offset = 4096; /* never corrupt superblock */
271 	for (i = 0; i < count; i++) {
272 		do
273 			off = rand_num(offset, hsize);
274 		while (!ext2fs_test_block_bitmap2(corrupt_map,
275 						    off / fs->blocksize));
276 		c = rand() % 256;
277 		if ((rand() % 2) && c < 128)
278 			c |= 0x80;
279 		if (verbose)
280 			printf("Corrupting byte %lld in block %lld to 0x%x\n",
281 			       (long long)off % fs->blocksize,
282 			       (long long)off / fs->blocksize, c);
283 		if (dryrun)
284 			continue;
285 #ifdef HAVE_PWRITE64
286 		if (pwrite64(fd, &c, sizeof(c), off) != sizeof(c)) {
287 			perror(fsname);
288 			goto fail3;
289 		}
290 #elif HAVE_PWRITE
291 		if (pwrite(fd, &c, sizeof(c), off) != sizeof(c)) {
292 			perror(fsname);
293 			goto fail3;
294 		}
295 #else
296 		if (my_pwrite(fd, &c, sizeof(c), off) != sizeof(c)) {
297 			perror(fsname);
298 			goto fail3;
299 		}
300 #endif
301 	}
302 	close(fd);
303 
304 	/* Clean up */
305 	ret = ext2fs_close_free(&fs);
306 	if (ret) {
307 		fprintf(stderr, "%s: error while closing filesystem\n",
308 			fsname);
309 		return 1;
310 	}
311 
312 	return 0;
313 fail3:
314 	close(fd);
315 	if (corrupt_map != fs->block_map)
316 		ext2fs_free_block_bitmap(corrupt_map);
317 fail:
318 	ext2fs_close_free(&fs);
319 	return 1;
320 }
321 
print_help(const char * progname)322 static void print_help(const char *progname)
323 {
324 	printf("Usage: %s OPTIONS device\n", progname);
325 	printf("-b:	Corrupt this many bytes.\n");
326 	printf("-d:	Fuzz data blocks too.\n");
327 	printf("-n:	Dry run only.\n");
328 	printf("-v:	Verbose output.\n");
329 	exit(0);
330 }
331 
main(int argc,char * argv[])332 int main(int argc, char *argv[])
333 {
334 	int c;
335 
336 	while ((c = getopt(argc, argv, "b:dnv")) != -1) {
337 		switch (c) {
338 		case 'b':
339 			if (optarg[strlen(optarg) - 1] == '%') {
340 				user_corrupt_pct = strtod(optarg, NULL);
341 				if (user_corrupt_pct > 100 ||
342 				    user_corrupt_pct < 0) {
343 					fprintf(stderr, "%s: Invalid percentage.\n",
344 						optarg);
345 					return 1;
346 				}
347 			} else
348 				user_corrupt_bytes = strtoull(optarg, NULL, 0);
349 			if (errno) {
350 				perror(optarg);
351 				return 1;
352 			}
353 			break;
354 		case 'd':
355 			metadata_only = 0;
356 			break;
357 		case 'n':
358 			dryrun = 1;
359 			break;
360 		case 'v':
361 			verbose = 1;
362 			break;
363 		default:
364 			print_help(argv[0]);
365 		}
366 	}
367 
368 	for (c = optind; c < argc; c++)
369 		if (process_fs(argv[c]))
370 			return 1;
371 	return 0;
372 }
373