@@ -12,7 +12,8 @@ HFILES = addr.h agf.h agfl.h agi.h attr.h attrshort.h bit.h block.h bmap.h \
dir2.h dir2sf.h dquot.h echo.h faddr.h field.h \
flist.h fprint.h frag.h freesp.h hash.h help.h init.h inode.h input.h \
io.h logformat.h malloc.h metadump.h output.h print.h quit.h sb.h \
- sig.h strvec.h text.h type.h write.h attrset.h symlink.h fsmap.h
+ sig.h strvec.h text.h type.h write.h attrset.h symlink.h fsmap.h \
+ fuzz.h
CFILES = $(HFILES:.h=.c)
LSRCFILES = xfs_admin.sh xfs_ncheck.sh xfs_metadump.sh
@@ -19,13 +19,8 @@
#include "libxfs.h"
#include "bit.h"
-#undef setbit /* defined in param.h on Linux */
-
-static int getbit(char *ptr, int bit);
-static void setbit(char *ptr, int bit, int val);
-
-static int
-getbit(
+int
+getbit_l(
char *ptr,
int bit)
{
@@ -39,8 +34,8 @@ getbit(
return (*ptr & mask) >> shift;
}
-static void
-setbit(
+void
+setbit_l(
char *ptr,
int bit,
int val)
@@ -106,7 +101,7 @@ getbitval(
for (i = 0, rval = 0LL; i < nbits; i++) {
- if (getbit(p, bit + i)) {
+ if (getbit_l(p, bit + i)) {
/* If the last bit is on and we care about sign
* bits and we don't have a full 64 bit
* container, turn all bits on between the
@@ -162,7 +157,7 @@ setbitval(
if (bitoff % NBBY || nbits % NBBY) {
for (bit = 0; bit < nbits; bit++)
- setbit(out, bit + bitoff, getbit(in, bit));
+ setbit_l(out, bit + bitoff, getbit_l(in, bit));
} else
memcpy(out + byteize(bitoff), in, byteize(nbits));
}
@@ -21,9 +21,12 @@
#define bitszof(x,y) bitize(szof(x,y))
#define byteize(s) ((s) / NBBY)
#define bitoffs(s) ((s) % NBBY)
+#define byteize_up(s) (((s) + NBBY - 1) / NBBY)
#define BVUNSIGNED 0
#define BVSIGNED 1
extern __int64_t getbitval(void *obj, int bitoff, int nbits, int flags);
-extern void setbitval(void *obuf, int bitoff, int nbits, void *ibuf);
+extern void setbitval(void *obuf, int bitoff, int nbits, void *ibuf);
+extern int getbit_l(char *ptr, int bit);
+extern void setbit_l(char *ptr, int bit, int val);
@@ -51,6 +51,7 @@
#include "dquot.h"
#include "fsmap.h"
#include "crc.h"
+#include "fuzz.h"
cmdinfo_t *cmdtab;
int ncmds;
@@ -146,4 +147,5 @@ init_commands(void)
type_init();
write_init();
dquot_init();
+ fuzz_init();
}
new file mode 100644
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2000-2002,2005 Silicon Graphics, Inc.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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"
+
+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"
+" In data mode 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 &&
+ !iocur_top->ino_buf) {
+ 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 (corrupt) {
+ local_ops.verify_write = xfs_dummy_verify;
+ dbprintf(_("Allowing fuzz of corrupted data and bad CRC\n"));
+ } else if (iocur_top->ino_buf) {
+ local_ops.verify_write = xfs_verify_recalc_inode_crc;
+ dbprintf(_("Allowing fuzz of corrupted inode 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));
+ 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));
+ 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 verb\n"));
+ dbprintf("Verbs: %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]);
+ return;
+ }
+
+ /* 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)) {
+ flist_free(fl);
+ dbprintf(_("parsing error\n"));
+ return;
+ }
+
+ 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]);
+ flist_free(fl);
+ return;
+ }
+
+ /* Write the fuzzed value back */
+ write_cur();
+
+ flist_print(fl);
+ print_flist(fl);
+ flist_free(fl);
+}
new file mode 100644
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 Oracle. All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+extern void fuzz_init(void);
+extern void fuzz_struct(const field_t *fields, int argc, char **argv);
@@ -465,6 +465,15 @@ xfs_dummy_verify(
}
void
+xfs_verify_recalc_inode_crc(
+ struct xfs_buf *bp)
+{
+ ASSERT(iocur_top->ino_buf);
+ libxfs_dinode_calc_crc(mp, iocur_top->data);
+ iocur_top->ino_crc_ok = 1;
+}
+
+void
xfs_verify_recalc_crc(
struct xfs_buf *bp)
{
@@ -64,6 +64,7 @@ extern void set_cur(const struct typ *t, __int64_t d, int c, int ring_add,
extern void ring_add(void);
extern void set_iocur_type(const struct typ *t);
extern void xfs_dummy_verify(struct xfs_buf *bp);
+extern void xfs_verify_recalc_inode_crc(struct xfs_buf *bp);
extern void xfs_verify_recalc_crc(struct xfs_buf *bp);
/*
@@ -39,6 +39,7 @@
#include "dir2.h"
#include "text.h"
#include "symlink.h"
+#include "fuzz.h"
static const typ_t *findtyp(char *name);
static int type_f(int argc, char **argv);
@@ -254,10 +255,17 @@ handle_struct(
int argc,
char **argv)
{
- if (action == DB_WRITE)
+ switch (action) {
+ case DB_FUZZ:
+ fuzz_struct(fields, argc, argv);
+ break;
+ case DB_WRITE:
write_struct(fields, argc, argv);
- else
+ break;
+ case DB_READ:
print_struct(fields, argc, argv);
+ break;
+ }
}
void
@@ -267,10 +275,17 @@ handle_string(
int argc,
char **argv)
{
- if (action == DB_WRITE)
+ switch (action) {
+ case DB_WRITE:
write_string(fields, argc, argv);
- else
+ break;
+ case DB_READ:
print_string(fields, argc, argv);
+ break;
+ case DB_FUZZ:
+ dbprintf(_("string fuzzing not supported.\n"));
+ break;
+ }
}
void
@@ -280,10 +295,17 @@ handle_block(
int argc,
char **argv)
{
- if (action == DB_WRITE)
+ switch (action) {
+ case DB_WRITE:
write_block(fields, argc, argv);
- else
+ break;
+ case DB_READ:
print_block(fields, argc, argv);
+ break;
+ case DB_FUZZ:
+ dbprintf(_("use 'blocktrash' or 'write' to fuzz a block.\n"));
+ break;
+ }
}
void
@@ -293,6 +315,14 @@ handle_text(
int argc,
char **argv)
{
- if (action != DB_WRITE)
+ switch (action) {
+ case DB_FUZZ:
+ /* fall through */
+ case DB_WRITE:
+ dbprintf(_("text writing/fuzzing not supported.\n"));
+ break;
+ case DB_READ:
print_text(fields, argc, argv);
+ break;
+ }
}
@@ -30,6 +30,7 @@ typedef enum typnm
TYP_TEXT, TYP_FINOBT, TYP_NONE
} typnm_t;
+#define DB_FUZZ 2
#define DB_WRITE 1
#define DB_READ 0
@@ -594,6 +594,55 @@ in units of 512-byte blocks, no matter what the filesystem's block size is.
.BI "The optional " start " and " end " arguments can be used to constrain
the output to a particular range of disk blocks.
.TP
+.BI "fuzz [\-c] [\-d] " "field action"
+Write garbage into a specific structure field on disk.
+Expert mode must be enabled to use this command.
+The operation happens immediately; there is no buffering.
+.IP
+The fuzz command can take the following
+.IR action "s"
+against a field:
+.RS 1.0i
+.TP 0.4i
+.B zeroes
+Clears all bits in the field.
+.TP 0.4i
+.B ones
+Sets all bits in the field.
+.TP 0.4i
+.B firstbit
+Flips the first bit in the field.
+For a scalar value, this is the highest bit.
+.TP 0.4i
+.B middlebit
+Flips the middle bit in the field.
+.TP 0.4i
+.B lastbit
+Flips the last bit in the field.
+For a scalar value, this is the lowest bit.
+.TP 0.4i
+.B add
+Adds a small value to a scalar field.
+.TP 0.4i
+.B sub
+Subtracts a small value from a scalar field.
+.TP 0.4i
+.B random
+Randomizes the contents of the field.
+.RE
+.IP
+The following switches affect the write behavior:
+.RS 1.0i
+.TP 0.4i
+.B \-c
+Skip write verifiers and CRC recalculation; allows invalid data to be written
+to disk.
+.TP 0.4i
+.B \-d
+Skip write verifiers but perform CRC recalculation; allows invalid data to be
+written to disk to test detection of invalid data.
+.RE
+.TP
.BI hash " string
Prints the hash value of
.I string
@@ -755,7 +804,7 @@ and
bits respectively, and their string equivalent reported
(but no modifications are made).
.TP
-.BI "write [\-c] [" "field value" "] ..."
+.BI "write [\-c] [\-d] [" "field value" "] ..."
Write a value to disk.
Specific fields can be set in structures (struct mode),
or a block can be set to data values (data mode),
@@ -778,6 +827,10 @@ with no arguments gives more information on the allowed commands.
.B \-c
Skip write verifiers and CRC recalculation; allows invalid data to be written
to disk.
+.TP 0.4i
+.B \-d
+Skip write verifiers but perform CRC recalculation; allows invalid data to be
+written to disk to test detection of invalid data.
.RE
.SH TYPES
This section gives the fields in each structure type and their meanings.
Introduce a new 'fuzz' command to write creative values into disk structure fields. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com> --- db/Makefile | 3 db/bit.c | 17 +- db/bit.h | 5 - db/command.c | 2 db/fuzz.c | 461 +++++++++++++++++++++++++++++++++++++++++++++++++++++ db/fuzz.h | 21 ++ db/io.c | 9 + db/io.h | 1 db/type.c | 44 ++++- db/type.h | 1 man/man8/xfs_db.8 | 55 ++++++ 11 files changed, 598 insertions(+), 21 deletions(-) create mode 100644 db/fuzz.c create mode 100644 db/fuzz.h -- To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html