btrfs-progs: corrupt-block: corrupt generic item data

btrfs-corrupt-block already has a mix of generic and specific corruption
options, but currently lacks the capacity for totally arbitrary
corruption in item data.

There is already a flag for corruption size (bytes/-b), so add a flag
for an offset and a value to memset the item with. Exercise the new
flags with a new variant for -I (item) corruption. Look up the item as
before, but instead of corrupting a field in the item struct, corrupt an
offset/size in the item data.

The motivating example for this is that in testing fsverity with btrfs,
we need to corrupt the generated Merkle tree--metadata item data which
is an opaque blob to btrfs.

Reviewed-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Boris Burkov 2022-07-26 13:43:23 -07:00 committed by David Sterba
parent cef73a5e98
commit b2ada05941

View File

@ -98,12 +98,14 @@ static void print_usage(int ret)
printf("\t-m The metadata block to corrupt (must also specify -f for the field to corrupt)\n");
printf("\t-K <u64,u8,u64> Corrupt the given key (must also specify -f for the field and optionally -r for the root)\n");
printf("\t-f The field in the item to corrupt\n");
printf("\t-I <u64,u8,u64> Corrupt an item corresponding to the passed key triplet (must also specify the field to corrupt and root for the item)\n");
printf("\t-I <u64,u8,u64> Corrupt an item corresponding to the passed key triplet (must also specify the field, or a (bytes, offset, value) tuple to corrupt and root for the item)\n");
printf("\t-D <u64,u8,u64> Corrupt a dir item corresponding to the passed key triplet, must also specify a field\n");
printf("\t-d <u64,u8,u64> Delete item corresponding to passed key triplet\n");
printf("\t-r Operate on this root\n");
printf("\t-C Delete a csum for the specified bytenr. When used with -b it'll delete that many bytes, otherwise it's just sectorsize\n");
printf("\t--block-group OFFSET corrupt the given block group\n");
printf("\t-v Value to use for corrupting item data\n");
printf("\t-o Offset to use for corrupting item data\n");
exit(ret);
}
@ -974,6 +976,56 @@ out:
return ret;
}
static int corrupt_btrfs_item_data(struct btrfs_root *root,
struct btrfs_key *key,
u64 bogus_offset, u64 bogus_size,
char bogus_value)
{
struct btrfs_trans_handle *trans;
struct btrfs_path *path;
int ret;
void *data;
struct extent_buffer *leaf;
int slot;
u32 item_size;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
fprintf(stderr, "Couldn't start transaction %ld\n",
PTR_ERR(trans));
ret = PTR_ERR(trans);
goto free_path;
}
ret = btrfs_search_slot(trans, root, key, path, 0, 1);
if (ret != 0) {
fprintf(stderr, "Error searching to node %d\n", ret);
goto commit_txn;
}
leaf = path->nodes[0];
slot = path->slots[0];
data = btrfs_item_ptr(leaf, slot, void);
item_size = btrfs_item_size(leaf, slot);
if (bogus_offset + bogus_size > item_size) {
fprintf(stderr, "Item corruption past end of item: %llu > %u\n", bogus_offset + bogus_size, item_size);
ret = -EINVAL;
goto commit_txn;
}
data += bogus_offset;
memset_extent_buffer(leaf, bogus_value, (unsigned long)data, bogus_size);
btrfs_mark_buffer_dirty(leaf);
commit_txn:
btrfs_commit_transaction(trans, root);
free_path:
btrfs_free_path(path);
return ret;
}
static int delete_item(struct btrfs_root *root, struct btrfs_key *key)
{
struct btrfs_trans_handle *trans;
@ -1230,6 +1282,8 @@ int main(int argc, char **argv)
u64 csum_bytenr = 0;
u64 block_group = 0;
char field[FIELD_BUF_LEN];
u64 bogus_value = (u64)-1;
u64 bogus_offset = (u64)-1;
field[0] = '\0';
memset(&key, 0, sizeof(key));
@ -1258,11 +1312,13 @@ int main(int argc, char **argv)
{ "root", no_argument, NULL, 'r'},
{ "csum", required_argument, NULL, 'C'},
{ "block-group", required_argument, NULL, GETOPT_VAL_BLOCK_GROUP},
{ "value", required_argument, NULL, 'v'},
{ "offset", required_argument, NULL, 'o'},
{ "help", no_argument, NULL, GETOPT_VAL_HELP},
{ NULL, 0, NULL, 0 }
};
c = getopt_long(argc, argv, "l:c:b:eEkuUi:f:x:m:K:I:D:d:r:C:",
c = getopt_long(argc, argv, "l:c:b:eEkuUi:f:x:m:K:I:D:d:r:C:v:o:",
long_options, NULL);
if (c < 0)
break;
@ -1328,6 +1384,12 @@ int main(int argc, char **argv)
case GETOPT_VAL_BLOCK_GROUP:
block_group = arg_strtou64(optarg);
break;
case 'v':
bogus_value = arg_strtou64(optarg);
break;
case 'o':
bogus_offset = arg_strtou64(optarg);
break;
case GETOPT_VAL_HELP:
default:
print_usage(c != GETOPT_VAL_HELP);
@ -1454,7 +1516,16 @@ int main(int argc, char **argv)
if (!root_objectid)
print_usage(1);
ret = corrupt_btrfs_item(target_root, &key, field);
if (*field != 0)
ret = corrupt_btrfs_item(target_root, &key, field);
else if (bogus_offset != (u64)-1 &&
bytes != (u64)-1 &&
bogus_value != (u64)-1)
ret = corrupt_btrfs_item_data(target_root, &key,
bogus_offset, bytes,
bogus_value);
else
print_usage(1);
goto out_close;
}
if (delete) {