]>
Commit | Line | Data |
---|---|---|
c4d5b81e DW |
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> | |
0165cfad | 21 | #include <unistd.h> |
c4d5b81e DW |
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 | ||
7248e265 DW |
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) | |
0165cfad DW |
37 | { |
38 | if (lseek(fd, offset, SEEK_SET) < 0) | |
39 | return 0; | |
40 | ||
41 | return write(fd, buf, count); | |
42 | } | |
7248e265 | 43 | #endif /* !defined HAVE_PWRITE64 && !defined HAVE_PWRITE */ |
0165cfad | 44 | |
4e222d9b | 45 | static int getseed(void) |
c4d5b81e DW |
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 | } | |
c4c9bc59 DW |
55 | if (read(fd, &r, sizeof(r)) != sizeof(r)) |
56 | printf("Unable to read random seed!\n"); | |
c4d5b81e DW |
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 | ||
4e222d9b TT |
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) | |
c4d5b81e DW |
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 | ||
4e222d9b TT |
84 | static errcode_t find_metadata_blocks(ext2_filsys fs, ext2fs_block_bitmap bmap, |
85 | off_t *corrupt_bytes) | |
c4d5b81e DW |
86 | { |
87 | dgrp_t i; | |
0165cfad | 88 | blk64_t b, c; |
c4d5b81e DW |
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; | |
c4d5b81e DW |
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 | ||
4e222d9b | 166 | static uint64_t rand_num(uint64_t min, uint64_t max) |
c4d5b81e DW |
167 | { |
168 | uint64_t x; | |
4e222d9b | 169 | unsigned int i; |
c4d5b81e DW |
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 | ||
4e222d9b | 178 | static int process_fs(const char *fsname) |
c4d5b81e DW |
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; | |
4e222d9b | 186 | off_t i; |
c4d5b81e DW |
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) | |
377e3a96 TT |
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); | |
c4d5b81e DW |
282 | if (dryrun) |
283 | continue; | |
7248e265 DW |
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 | |
0165cfad | 290 | if (pwrite(fd, &c, sizeof(c), off) != sizeof(c)) { |
c4d5b81e DW |
291 | perror(fsname); |
292 | goto fail3; | |
293 | } | |
7248e265 DW |
294 | #else |
295 | if (my_pwrite(fd, &c, sizeof(c), off) != sizeof(c)) { | |
296 | perror(fsname); | |
297 | goto fail3; | |
298 | } | |
299 | #endif | |
c4d5b81e DW |
300 | } |
301 | close(fd); | |
302 | ||
303 | /* Clean up */ | |
8375a881 | 304 | ret = ext2fs_close_free(&fs); |
c4d5b81e | 305 | if (ret) { |
c4d5b81e DW |
306 | fprintf(stderr, "%s: error while closing filesystem\n", |
307 | fsname); | |
8375a881 | 308 | return 1; |
c4d5b81e DW |
309 | } |
310 | ||
311 | return 0; | |
312 | fail3: | |
313 | close(fd); | |
c4d5b81e DW |
314 | if (corrupt_map != fs->block_map) |
315 | ext2fs_free_block_bitmap(corrupt_map); | |
316 | fail: | |
8375a881 | 317 | ext2fs_close_free(&fs); |
c4d5b81e DW |
318 | return 1; |
319 | } | |
320 | ||
4e222d9b | 321 | static void print_help(const char *progname) |
c4d5b81e DW |
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 | } |