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