• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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,off_t offset)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 
getseed(void)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 
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)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 
find_metadata_blocks(ext2_filsys fs,ext2fs_block_bitmap bmap,off_t * corrupt_bytes)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 
rand_num(uint64_t min,uint64_t max)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 
process_fs(const char * fsname)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 
print_help(const char * progname)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 
main(int argc,char * argv[])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