| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2002,2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "libxfs.h" |
| #include <ctype.h> |
| #include <time.h> |
| #include "bit.h" |
| #include "block.h" |
| #include "command.h" |
| #include "type.h" |
| #include "faddr.h" |
| #include "fprint.h" |
| #include "field.h" |
| #include "flist.h" |
| #include "io.h" |
| #include "init.h" |
| #include "output.h" |
| #include "print.h" |
| #include "write.h" |
| #include "malloc.h" |
| #include "fuzz.h" |
| |
| static int fuzz_f(int argc, char **argv); |
| static void fuzz_help(void); |
| |
| static const cmdinfo_t fuzz_cmd = |
| { "fuzz", NULL, fuzz_f, 0, -1, 0, N_("[-c] [-d] field fuzzcmd..."), |
| N_("fuzz values on disk"), fuzz_help }; |
| |
| void |
| fuzz_init(void) |
| { |
| if (!expert_mode) |
| return; |
| |
| add_command(&fuzz_cmd); |
| srand48(clock()); |
| } |
| |
| static void |
| fuzz_help(void) |
| { |
| dbprintf(_( |
| "\n" |
| " The 'fuzz' command fuzzes fields in any on-disk data structure. For\n" |
| " block fuzzing, see the 'blocktrash' or 'write' commands." |
| "\n" |
| " Examples:\n" |
| " Struct mode: 'fuzz core.uid zeroes' - set an inode uid field to 0.\n" |
| " 'fuzz crc ones' - set a crc filed to all ones.\n" |
| " 'fuzz bno[11] firstbit' - set the high bit of a block array.\n" |
| " 'fuzz keys[5].startblock add' - increase a btree key value.\n" |
| " 'fuzz uuid random' - randomize the superblock uuid.\n" |
| "\n" |
| " Type 'fuzz' by itself for a list of specific commands.\n\n" |
| " Specifying the -c option will allow writes of invalid (corrupt) data with\n" |
| " an invalid CRC. Specifying the -d option will allow writes of invalid data,\n" |
| " but still recalculate the CRC so we are forced to check and detect the\n" |
| " invalid data appropriately.\n\n" |
| )); |
| |
| } |
| |
| static int |
| fuzz_f( |
| int argc, |
| char **argv) |
| { |
| pfunc_t pf; |
| extern char *progname; |
| int c; |
| bool corrupt = false; /* Allow write of bad data w/ invalid CRC */ |
| bool invalid_data = false; /* Allow write of bad data w/ valid CRC */ |
| struct xfs_buf_ops local_ops; |
| const struct xfs_buf_ops *stashed_ops = NULL; |
| |
| if (x.isreadonly & LIBXFS_ISREADONLY) { |
| dbprintf(_("%s started in read only mode, fuzzing disabled\n"), |
| progname); |
| return 0; |
| } |
| |
| if (cur_typ == NULL) { |
| dbprintf(_("no current type\n")); |
| return 0; |
| } |
| |
| pf = cur_typ->pfunc; |
| if (pf == NULL) { |
| dbprintf(_("no handler function for type %s, fuzz unsupported.\n"), |
| cur_typ->name); |
| return 0; |
| } |
| |
| while ((c = getopt(argc, argv, "cd")) != EOF) { |
| switch (c) { |
| case 'c': |
| corrupt = true; |
| break; |
| case 'd': |
| invalid_data = true; |
| break; |
| default: |
| dbprintf(_("bad option for fuzz command\n")); |
| return 0; |
| } |
| } |
| |
| if (corrupt && invalid_data) { |
| dbprintf(_("Cannot specify both -c and -d options\n")); |
| return 0; |
| } |
| |
| if (invalid_data && |
| iocur_top->typ->crc_off == TYP_F_NO_CRC_OFF && |
| xfs_sb_version_hascrc(&mp->m_sb)) { |
| dbprintf(_("Cannot recalculate CRCs on this type of object\n")); |
| return 0; |
| } |
| |
| argc -= optind; |
| argv += optind; |
| |
| /* |
| * If the buffer has no verifier or we are using standard verifier |
| * paths, then just fuzz it and return |
| */ |
| if (!iocur_top->bp->b_ops || |
| !(corrupt || invalid_data)) { |
| (*pf)(DB_FUZZ, cur_typ->fields, argc, argv); |
| return 0; |
| } |
| |
| |
| /* Temporarily remove write verifier to write bad data */ |
| stashed_ops = iocur_top->bp->b_ops; |
| local_ops.verify_read = stashed_ops->verify_read; |
| iocur_top->bp->b_ops = &local_ops; |
| |
| if (!xfs_sb_version_hascrc(&mp->m_sb)) { |
| local_ops.verify_write = xfs_dummy_verify; |
| } else if (corrupt) { |
| local_ops.verify_write = xfs_dummy_verify; |
| dbprintf(_("Allowing fuzz of corrupted data and bad CRC\n")); |
| } else if (iocur_top->typ->crc_off == TYP_F_CRC_FUNC) { |
| local_ops.verify_write = iocur_top->typ->set_crc; |
| dbprintf(_("Allowing fuzz of corrupted data with good CRC\n")); |
| } else { /* invalid data */ |
| local_ops.verify_write = xfs_verify_recalc_crc; |
| dbprintf(_("Allowing fuzz of corrupted data with good CRC\n")); |
| } |
| |
| (*pf)(DB_FUZZ, cur_typ->fields, argc, argv); |
| |
| iocur_top->bp->b_ops = stashed_ops; |
| |
| return 0; |
| } |
| |
| /* Write zeroes to the field */ |
| static bool |
| fuzz_zeroes( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| char *out = buf; |
| int bit; |
| |
| if (bitoff % NBBY || nbits % NBBY) { |
| for (bit = 0; bit < nbits; bit++) |
| setbit_l(out, bit + bitoff, 0); |
| } else |
| memset(out + byteize(bitoff), 0, byteize(nbits)); |
| return true; |
| } |
| |
| /* Write ones to the field */ |
| static bool |
| fuzz_ones( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| char *out = buf; |
| int bit; |
| |
| if (bitoff % NBBY || nbits % NBBY) { |
| for (bit = 0; bit < nbits; bit++) |
| setbit_l(out, bit + bitoff, 1); |
| } else |
| memset(out + byteize(bitoff), 0xFF, byteize(nbits)); |
| return true; |
| } |
| |
| /* Flip the high bit in the (presumably big-endian) field */ |
| static bool |
| fuzz_firstbit( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| setbit_l((char *)buf, bitoff, !getbit_l((char *)buf, bitoff)); |
| return true; |
| } |
| |
| /* Flip the low bit in the (presumably big-endian) field */ |
| static bool |
| fuzz_lastbit( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| setbit_l((char *)buf, bitoff + nbits - 1, |
| !getbit_l((char *)buf, bitoff + nbits - 1)); |
| return true; |
| } |
| |
| /* Flip the middle bit in the (presumably big-endian) field */ |
| static bool |
| fuzz_middlebit( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| setbit_l((char *)buf, bitoff + nbits / 2, |
| !getbit_l((char *)buf, bitoff + nbits / 2)); |
| return true; |
| } |
| |
| /* Format and shift a number into a buffer for setbitval. */ |
| static char * |
| format_number( |
| uint64_t val, |
| __be64 *out, |
| int bit_length) |
| { |
| int offset; |
| char *rbuf = (char *)out; |
| |
| /* |
| * If the length of the field is not a multiple of a byte, push |
| * the bits up in the field, so the most signicant field bit is |
| * the most significant bit in the byte: |
| * |
| * before: |
| * val |----|----|----|----|----|--MM|mmmm|llll| |
| * after |
| * val |----|----|----|----|----|MMmm|mmll|ll00| |
| */ |
| offset = bit_length % NBBY; |
| if (offset) |
| val <<= (NBBY - offset); |
| |
| /* |
| * convert to big endian and copy into the array |
| * rbuf |----|----|----|----|----|MMmm|mmll|ll00| |
| */ |
| *out = cpu_to_be64(val); |
| |
| /* |
| * Align the array to point to the field in the array. |
| * rbuf = |MMmm|mmll|ll00| |
| */ |
| offset = sizeof(__be64) - 1 - ((bit_length - 1) / sizeof(__be64)); |
| return rbuf + offset; |
| } |
| |
| /* Increase the value by some small prime number. */ |
| static bool |
| fuzz_add( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| uint64_t val; |
| __be64 out; |
| char *b; |
| |
| if (nbits > 64) |
| return false; |
| |
| val = getbitval(buf, bitoff, nbits, BVUNSIGNED); |
| val += (nbits > 8 ? 2017 : 137); |
| b = format_number(val, &out, nbits); |
| setbitval(buf, bitoff, nbits, b); |
| |
| return true; |
| } |
| |
| /* Decrease the value by some small prime number. */ |
| static bool |
| fuzz_sub( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| uint64_t val; |
| __be64 out; |
| char *b; |
| |
| if (nbits > 64) |
| return false; |
| |
| val = getbitval(buf, bitoff, nbits, BVUNSIGNED); |
| val -= (nbits > 8 ? 2017 : 137); |
| b = format_number(val, &out, nbits); |
| setbitval(buf, bitoff, nbits, b); |
| |
| return true; |
| } |
| |
| /* Randomize the field. */ |
| static bool |
| fuzz_random( |
| void *buf, |
| int bitoff, |
| int nbits) |
| { |
| int i, bytes; |
| char *b, *rbuf; |
| |
| bytes = byteize_up(nbits); |
| rbuf = b = malloc(bytes); |
| if (!b) { |
| perror("fuzz_random"); |
| return false; |
| } |
| |
| for (i = 0; i < bytes; i++) |
| *b++ = (char)lrand48(); |
| |
| setbitval(buf, bitoff, nbits, rbuf); |
| free(rbuf); |
| |
| return true; |
| } |
| |
| struct fuzzcmd { |
| const char *verb; |
| bool (*fn)(void *buf, int bitoff, int nbits); |
| }; |
| |
| /* Keep these verbs in sync with enum fuzzcmds. */ |
| static struct fuzzcmd fuzzverbs[] = { |
| {"zeroes", fuzz_zeroes}, |
| {"ones", fuzz_ones}, |
| {"firstbit", fuzz_firstbit}, |
| {"middlebit", fuzz_middlebit}, |
| {"lastbit", fuzz_lastbit}, |
| {"add", fuzz_add}, |
| {"sub", fuzz_sub}, |
| {"random", fuzz_random}, |
| {NULL, NULL}, |
| }; |
| |
| /* ARGSUSED */ |
| void |
| fuzz_struct( |
| const field_t *fields, |
| int argc, |
| char **argv) |
| { |
| const ftattr_t *fa; |
| flist_t *fl; |
| flist_t *sfl; |
| int bit_length; |
| struct fuzzcmd *fc; |
| bool success; |
| int parentoffset; |
| |
| if (argc != 2) { |
| dbprintf(_("Usage: fuzz fieldname fuzzcmd\n")); |
| dbprintf("Fuzz commands: %s", fuzzverbs->verb); |
| for (fc = fuzzverbs + 1; fc->verb != NULL; fc++) |
| dbprintf(", %s", fc->verb); |
| dbprintf(".\n"); |
| return; |
| } |
| |
| fl = flist_scan(argv[0]); |
| if (!fl) { |
| dbprintf(_("unable to parse '%s'.\n"), argv[0]); |
| return; |
| } |
| |
| /* Find our fuzz verb */ |
| for (fc = fuzzverbs; fc->verb != NULL; fc++) |
| if (!strcmp(fc->verb, argv[1])) |
| break; |
| if (fc->fn == NULL) { |
| dbprintf(_("Unknown fuzz command '%s'.\n"), argv[1]); |
| goto out_free; |
| } |
| |
| /* if we're a root field type, go down 1 layer to get field list */ |
| if (fields->name[0] == '\0') { |
| fa = &ftattrtab[fields->ftyp]; |
| ASSERT(fa->ftyp == fields->ftyp); |
| fields = fa->subfld; |
| } |
| |
| /* run down the field list and set offsets into the data */ |
| if (!flist_parse(fields, fl, iocur_top->data, 0)) { |
| dbprintf(_("parsing error\n")); |
| goto out_free; |
| } |
| |
| sfl = fl; |
| parentoffset = 0; |
| while (sfl->child) { |
| parentoffset = sfl->offset; |
| sfl = sfl->child; |
| } |
| |
| /* |
| * For structures, fsize * fcount tells us the size of the region we are |
| * modifying, which is usually a single structure member and is pointed |
| * to by the last child in the list. |
| * |
| * However, if the base structure is an array and we have a direct index |
| * into the array (e.g. write bno[5]) then we are returned a single |
| * flist object with the offset pointing directly at the location we |
| * need to modify. The length of the object we are modifying is then |
| * determined by the size of the individual array entry (fsize) and the |
| * indexes defined in the object, not the overall size of the array |
| * (which is what fcount returns). |
| */ |
| bit_length = fsize(sfl->fld, iocur_top->data, parentoffset, 0); |
| if (sfl->fld->flags & FLD_ARRAY) |
| bit_length *= sfl->high - sfl->low + 1; |
| else |
| bit_length *= fcount(sfl->fld, iocur_top->data, parentoffset); |
| |
| /* Fuzz the value */ |
| success = fc->fn(iocur_top->data, sfl->offset, bit_length); |
| if (!success) { |
| dbprintf(_("unable to fuzz field '%s'\n"), argv[0]); |
| goto out_free; |
| } |
| |
| /* Write the fuzzed value back */ |
| write_cur(); |
| |
| flist_print(fl); |
| print_flist(fl); |
| out_free: |
| flist_free(fl); |
| } |