You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
6.4 KiB
146 lines
6.4 KiB
Some examples for inject
|
|
|
|
inject guarantees the appropriate erroneous return of the specified injection
|
|
mode (kmalloc,bio,etc) given a call chain and an optional set of predicates. You
|
|
can also optionally print out the generated BPF program for
|
|
modification/debugging purposes.
|
|
|
|
As a simple example, let's say you wanted to fail all mounts. As of 4.17 we can
|
|
fail syscalls directly, so let's do that:
|
|
|
|
# ./inject.py kmalloc -v 'SyS_mount()'
|
|
|
|
The first argument indicates the mode (or what to fail). Appropriate headers are
|
|
specified, if necessary. The verbosity flag prints the generated program. Note
|
|
that some syscalls will be available as 'SyS_xyz' and some will be available as
|
|
'sys_xyz'. This is largely dependent on the number of arguments each syscall
|
|
takes.
|
|
|
|
Trying to mount various filesystems will fail and report an inability to
|
|
allocate memory, as expected.
|
|
|
|
Whenever a predicate is missing, an implicit "(true)" is inserted. The example
|
|
above can be explicitly written as:
|
|
|
|
# ./inject.py kmalloc -v '(true) => SyS_mount()(true)'
|
|
|
|
The "(true)" without an associated function is a predicate for the error
|
|
injection mechanism of the current mode. In the case of kmalloc, the predicate
|
|
would have access to the arguments of:
|
|
|
|
int should_failslab(struct kmem_cache *s, gfp_t gfpflags);
|
|
|
|
The bio mode works similarly, with access to the arguments of:
|
|
|
|
static noinline int should_fail_bio(struct bio *bio)
|
|
|
|
We also note that it's unnecessary to state the arguments of the function if you
|
|
have no intention to reference them in the associated predicate.
|
|
|
|
Now let's say we want to be a bit more specific; suppose you want to fail
|
|
kmalloc() from mount_subtree() when called from btrfs_mount(). This will fail
|
|
only btrfs mounts:
|
|
|
|
# ./inject.py kmalloc -v 'mount_subtree() => btrfs_mount()'
|
|
|
|
Attempting to mount btrfs filesystem during the execution of this command will
|
|
yield an error, but other filesystems will be fine.
|
|
|
|
Next, lets say we want to hit one of the BUG_ONs in fs/btrfs. As of 4.16-rc3,
|
|
there is a BUG_ON in btrfs_prepare_close_one_device() at fs/btrfs/volumes.c:1002
|
|
|
|
To hit this, we can use the following:
|
|
|
|
# ./inject.py kmalloc -v 'btrfs_alloc_device() => btrfs_close_devices()'
|
|
|
|
While the script was executing, I mounted and unmounted btrfs, causing a
|
|
segfault on umount(since that satisfied the call path indicated). A look at
|
|
dmesg will confirm that the erroneous return value injected by the script
|
|
tripped the BUG_ON, causing a segfault down the line.
|
|
|
|
In general, it's worth noting that the required specificity of the call chain is
|
|
dependent on how much granularity you need. The example above might have
|
|
performed as expected without the intermediate btrfs_alloc_device, but might
|
|
have also done something unexpected(an earlier kmalloc could have failed before
|
|
the one we were targetting).
|
|
|
|
For hot paths, the approach outlined above isn't enough. If a path is traversed
|
|
very often, we can distinguish distinct calls with function arguments. Let's say
|
|
we want to fail the dentry allocation of a file creatively named 'bananas'. We
|
|
can do the following:
|
|
|
|
# ./inject.py kmalloc -v 'd_alloc_parallel(struct dentry *parent, const struct
|
|
qstr *name)(STRCMP(name->name, 'bananas'))'
|
|
|
|
While this script is executing, any operation that would cause a dentry
|
|
allocation where the name is 'bananas' fails, as expected.
|
|
|
|
Here, since we're referencing a function argument in our predicate, we need to
|
|
provide the function signature up to the argument we're using.
|
|
|
|
To note, STRCMP is a workaround for some rewriter issues. It will take input of
|
|
the form (x->...->z, 'literal'), and generate some equivalent code that the
|
|
verifier is more friendly about. It's not horribly robust, but works for the
|
|
purposes of making string comparisons a bit easier.
|
|
|
|
Finally, we briefly demonstrate how to inject bio failures. The mechanism is
|
|
identical, so any information from above will apply.
|
|
|
|
Let's say we want to fail bio requests when the request is to some specific
|
|
sector. An example use case would be to fail superblock writes in btrfs. For
|
|
btrfs, we know that there must be a superblock at 65536 bytes, or sector 128.
|
|
This allows us to run the following:
|
|
|
|
# ./inject.py bio -v -I 'linux/blkdev.h' '(({struct gendisk *d = bio->bi_disk;
|
|
struct disk_part_tbl *tbl = d->part_tbl; struct hd_struct **parts = (void *)tbl +
|
|
sizeof(struct disk_part_tbl); struct hd_struct **partp = parts + bio->bi_partno;
|
|
struct hd_struct *p = *partp; dev_t disk = p->__dev.devt; disk ==
|
|
MKDEV(254,16);}) && bio->bi_iter.bi_sector == 128)'
|
|
|
|
The predicate in the command above has two parts. The first is a compound
|
|
statement which shortens to "only if the system is btrfs", but is long due
|
|
to rewriter/verifier shenanigans. The major/minor information can be found
|
|
however; I used Python. The second part simply checks the starting
|
|
address of bi_iter. While executing, this script effectively fails superblock
|
|
writes to the superblock at sector 128 without affecting other filesystems.
|
|
|
|
As an extension to the above, one could easily fail all btrfs superblock writes
|
|
(we only fail the primary) by calculating the sector number of the mirrors and
|
|
amending the predicate accordingly.
|
|
|
|
Inject also provides a probability option; this allows you to fail the
|
|
path+predicates some percentage of the time. For example, let's say we want to
|
|
fail our mounts half the time:
|
|
|
|
# ./inject.py kmalloc -v -P 0.01 'SyS_mount()'
|
|
|
|
USAGE message:
|
|
usage: inject.py [-h] [-I header] [-P probability] [-v] {kmalloc,bio} spec
|
|
|
|
Fail specified kernel functionality when call chain and predicates are met
|
|
|
|
positional arguments:
|
|
{kmalloc,bio} indicate which base kernel function to fail
|
|
spec specify call chain
|
|
|
|
optional arguments:
|
|
-h, --help show this help message and exit
|
|
-I header, --include header
|
|
additional header files to include in the BPF program
|
|
-P probability, --probability probability
|
|
probability that this call chain will fail
|
|
-v, --verbose print BPF program
|
|
|
|
EXAMPLES:
|
|
# ./inject.py kmalloc -v 'SyS_mount()'
|
|
Fails all calls to syscall mount
|
|
# ./inject.py kmalloc -v '(true) => SyS_mount()(true)'
|
|
Explicit rewriting of above
|
|
# ./inject.py kmalloc -v 'mount_subtree() => btrfs_mount()'
|
|
Fails btrfs mounts only
|
|
# ./inject.py kmalloc -v 'd_alloc_parallel(struct dentry *parent, const struct \
|
|
qstr *name)(STRCMP(name->name, 'bananas'))'
|
|
Fails dentry allocations of files named 'bananas'
|
|
# ./inject.py kmalloc -v -P 0.01 'SyS_mount()'
|
|
Fails calls to syscall mount with 1% probability
|