Re: [PATCH v2] fault-inject: support systematic fault injection

From: Akinobu Mita
Date: Fri Apr 07 2017 - 12:47:31 EST


2017-04-07 3:33 GMT+09:00 Michal Hocko <mhocko@xxxxxxxxxx>:
> [Let's add linux-api - please always cc this list when adding/modifying
> user visible interfaces]
>
> On Tue 28-03-17 15:01:28, Dmitry Vyukov wrote:
>> Add /proc/self/task/<current-tid>/fail-nth file that allows failing
>> 0-th, 1-st, 2-nd and so on calls systematically.
>> Excerpt from the added documentation:
>
> I didn't really get to read through details here but it just feels wrong
> to add this debugging only feature into proc. It also smells like one
> off thing as well.

We have 'sched' (CONFIG_SCHED_DEBUG), 'latency' (CONFIG_LATENCYTOP)
and 'make-it-fail' as debugging per-process proc files. So it doesn't
look very wrong to me. But I would like to avoid per-process proc
directory becoming messy. Do you think introducing /proc/<pid>/debug/
directory for debugging stuff makes sense?

Side note: 'fail-nth' was originally a single debugfs file
/sys/kernel/debug/fail_once. But it actually read/write current task's
fail_nth field, so I suggested changing per process procfs file.i
This change enables to inject N-th fail to kernel threads, too.

>> ===
>> Write to this file of integer N makes N-th call in the current task fail
>> (N is 0-based). Read from this file returns a single char 'Y' or 'N'
>> that says if the fault setup with a previous write to this file was
>> injected or not, and disables the fault if it wasn't yet injected.
>> Note that this file enables all types of faults (slab, futex, etc).
>> This setting takes precedence over all other generic settings like
>> probability, interval, times, etc. But per-capability settings
>> (e.g. fail_futex/ignore-private) take precedence over it.
>> This feature is intended for systematic testing of faults in a single
>> system call. See an example below.
>> ===
>>
>> Why adding new setting:
>> 1. Existing settings are global rather than per-task.
>> So parallel testing is not possible.
>> 2. attr->interval is close but it depends on attr->count
>> which is non reset to 0, so interval does not work as expected.
>> 3. Trying to model this with existing settings requires manipulations
>> of all of probability, interval, times, space, task-filter and
>> unexposed count and per-task make-it-fail files.
>> 4. Existing settings are per-failure-type, and the set of failure
>> types is potentially expanding.
>> 5. make-it-fail can't be changed by unprivileged user and aggressive
>> stress testing better be done from an unprivileged user.
>> Similarly, this would require opening the debugfs files to the
>> unprivileged user, as he would need to reopen at least times file
>> (not possible to pre-open before dropping privs).
>>
>> The proposed interface solves all of the above (see the example).
>>
>> Signed-off-by: Dmitry Vyukov <dvyukov@xxxxxxxxxx>
>> Cc: Akinobu Mita <akinobu.mita@xxxxxxxxx>
>> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
>> Cc: linux-kernel@xxxxxxxxxxxxxxx
>> Cc: linux-mm@xxxxxxxxx
>>
>> ---
>> We want to integrate this into syzkaller fuzzer.
>> A prototype has found 10 bugs in kernel in first day of usage:
>> https://groups.google.com/forum/#!searchin/syzkaller/%22FAULT_INJECTION%22%7Csort:relevance
>>
>> Changes since v1:
>> - change file name from /sys/kernel/debug/fail_once
>> to /proc/self/task/<current-tid>/fail-nth as per
>> Akinobu suggestion
>>
>> ---
>> Documentation/fault-injection/fault-injection.txt | 78 +++++++++++++++++++++++
>> fs/proc/base.c | 52 +++++++++++++++
>> include/linux/sched.h | 1 +
>> kernel/fork.c | 4 ++
>> lib/fault-inject.c | 7 ++
>> 5 files changed, 142 insertions(+)
>>
>> diff --git a/Documentation/fault-injection/fault-injection.txt b/Documentation/fault-injection/fault-injection.txt
>> index 415484f3d59a..192d8cbcc5f9 100644
>> --- a/Documentation/fault-injection/fault-injection.txt
>> +++ b/Documentation/fault-injection/fault-injection.txt
>> @@ -134,6 +134,22 @@ use the boot option:
>> fail_futex=
>> mmc_core.fail_request=<interval>,<probability>,<space>,<times>
>>
>> +o proc entries
>> +
>> +- /proc/self/task/<current-tid>/fail-nth:
>> +
>> + Write to this file of integer N makes N-th call in the current task fail
>> + (N is 0-based). Read from this file returns a single char 'Y' or 'N'
>> + that says if the fault setup with a previous write to this file was
>> + injected or not, and disables the fault if it wasn't yet injected.
>> + Note that this file enables all types of faults (slab, futex, etc).
>> + This setting takes precedence over all other generic debugfs settings
>> + like probability, interval, times, etc. But per-capability settings
>> + (e.g. fail_futex/ignore-private) take precedence over it.
>> +
>> + This feature is intended for systematic testing of faults in a single
>> + system call. See an example below.
>> +
>> How to add new fault injection capability
>> -----------------------------------------
>>
>> @@ -278,3 +294,65 @@ allocation failure.
>> # env FAILCMD_TYPE=fail_page_alloc \
>> ./tools/testing/fault-injection/failcmd.sh --times=100 \
>> -- make -C tools/testing/selftests/ run_tests
>> +
>> +Systematic faults using fail-nth
>> +---------------------------------
>> +
>> +The following code systematically faults 0-th, 1-st, 2-nd and so on
>> +capabilities in the socketpair() system call.
>> +
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include <sys/socket.h>
>> +#include <sys/syscall.h>
>> +#include <fcntl.h>
>> +#include <unistd.h>
>> +#include <string.h>
>> +#include <stdlib.h>
>> +#include <stdio.h>
>> +#include <errno.h>
>> +
>> +int main()
>> +{
>> + int i, err, res, fail_nth, fds[2];
>> + char buf[128];
>> +
>> + system("echo N > /sys/kernel/debug/failslab/ignore-gfp-wait");
>> + sprintf(buf, "/proc/self/task/%ld/fail-nth", syscall(SYS_gettid));
>> + fail_nth = open(buf, O_RDWR);
>> + for (i = 0;; i++) {
>> + sprintf(buf, "%d", i);
>> + write(fail_nth, buf, strlen(buf));
>> + res = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
>> + err = errno;
>> + read(fail_nth, buf, 1);
>> + if (res == 0) {
>> + close(fds[0]);
>> + close(fds[1]);
>> + }
>> + printf("%d-th fault %c: res=%d/%d\n", i, buf[0], res, err);
>> + if (buf[0] != 'Y')
>> + break;
>> + }
>> + return 0;
>> +}
>> +
>> +An example output:
>> +
>> +0-th fault Y: res=-1/23
>> +1-th fault Y: res=-1/23
>> +2-th fault Y: res=-1/23
>> +3-th fault Y: res=-1/12
>> +4-th fault Y: res=-1/12
>> +5-th fault Y: res=-1/23
>> +6-th fault Y: res=-1/23
>> +7-th fault Y: res=-1/23
>> +8-th fault Y: res=-1/12
>> +9-th fault Y: res=-1/12
>> +10-th fault Y: res=-1/12
>> +11-th fault Y: res=-1/12
>> +12-th fault Y: res=-1/12
>> +13-th fault Y: res=-1/12
>> +14-th fault Y: res=-1/12
>> +15-th fault Y: res=-1/12
>> +16-th fault N: res=0/12
>> diff --git a/fs/proc/base.c b/fs/proc/base.c
>> index 6e8655845830..66001172249b 100644
>> --- a/fs/proc/base.c
>> +++ b/fs/proc/base.c
>> @@ -1353,6 +1353,53 @@ static const struct file_operations proc_fault_inject_operations = {
>> .write = proc_fault_inject_write,
>> .llseek = generic_file_llseek,
>> };
>> +
>> +static ssize_t proc_fail_nth_write(struct file *file, const char __user *buf,
>> + size_t count, loff_t *ppos)
>> +{
>> + struct task_struct *task;
>> + int err, n;
>> +
>> + task = get_proc_task(file_inode(file));
>> + if (!task)
>> + return -ESRCH;
>> + put_task_struct(task);
>> + if (task != current)
>> + return -EPERM;
>> + err = kstrtoint_from_user(buf, count, 10, &n);
>> + if (err)
>> + return err;
>> + if (n < 0 || n == INT_MAX)
>> + return -EINVAL;
>> + current->fail_nth = n + 1;
>> + return len;
>> +}
>> +
>> +static ssize_t proc_fail_nth_read(struct file *file, char __user *buf,
>> + size_t count, loff_t *ppos)
>> +{
>> + struct task_struct *task;
>> + int err;
>> +
>> + task = get_proc_task(file_inode(file));
>> + if (!task)
>> + return -ESRCH;
>> + put_task_struct(task);
>> + if (task != current)
>> + return -EPERM;
>> + if (count < 1)
>> + return -EINVAL;
>> + err = put_user((char)(current->fail_nth ? 'N' : 'Y'), buf);
>> + if (err)
>> + return err;
>> + current->fail_nth = 0;
>> + return 1;
>> +}
>> +
>> +static const struct file_operations proc_fail_nth_operations = {
>> + .read = proc_fail_nth_read,
>> + .write = proc_fail_nth_write,
>> +};
>> #endif
>>
>>
>> @@ -3296,6 +3343,11 @@ static const struct pid_entry tid_base_stuff[] = {
>> #endif
>> #ifdef CONFIG_FAULT_INJECTION
>> REG("make-it-fail", S_IRUGO|S_IWUSR, proc_fault_inject_operations),
>> + /*
>> + * Operations on the file check that the task is current,
>> + * so we create it with 0666 to support testing under unprivileged user.
>> + */
>> + REG("fail-nth", 0666, proc_fail_nth_operations),
>> #endif
>> #ifdef CONFIG_TASK_IO_ACCOUNTING
>> ONE("io", S_IRUSR, proc_tid_io_accounting),
>> diff --git a/include/linux/sched.h b/include/linux/sched.h
>> index 543e0ea82684..7b50221fea51 100644
>> --- a/include/linux/sched.h
>> +++ b/include/linux/sched.h
>> @@ -1897,6 +1897,7 @@ struct task_struct {
>> #endif
>> #ifdef CONFIG_FAULT_INJECTION
>> int make_it_fail;
>> + int fail_nth;
>> #endif
>> /*
>> * when (nr_dirtied >= nr_dirtied_pause), it's time to call
>> diff --git a/kernel/fork.c b/kernel/fork.c
>> index 61284d8122fa..869c97a0a930 100644
>> --- a/kernel/fork.c
>> +++ b/kernel/fork.c
>> @@ -545,6 +545,10 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
>>
>> kcov_task_init(tsk);
>>
>> +#ifdef CONFIG_FAULT_INJECTION
>> + tsk->fail_nth = 0;
>> +#endif
>> +
>> return tsk;
>>
>> free_stack:
>> diff --git a/lib/fault-inject.c b/lib/fault-inject.c
>> index 6a823a53e357..d6516ba64d33 100644
>> --- a/lib/fault-inject.c
>> +++ b/lib/fault-inject.c
>> @@ -107,6 +107,12 @@ static inline bool fail_stacktrace(struct fault_attr *attr)
>>
>> bool should_fail(struct fault_attr *attr, ssize_t size)
>> {
>> + if (in_task() && current->fail_nth) {
>> + if (--current->fail_nth == 0)
>> + goto fail;
>> + return false;
>> + }
>> +
>> /* No need to check any other properties if the probability is 0 */
>> if (attr->probability == 0)
>> return false;
>> @@ -134,6 +140,7 @@ bool should_fail(struct fault_attr *attr, ssize_t size)
>> if (!fail_stacktrace(attr))
>> return false;
>>
>> +fail:
>> fail_dump(attr);
>>
>> if (atomic_read(&attr->times) != -1)
>> --
>> 2.12.2.564.g063fe858b8-goog
>>
>> --
>> To unsubscribe, send a message with 'unsubscribe linux-mm' in
>> the body to majordomo@xxxxxxxxxx For more info on Linux MM,
>> see: http://www.linux-mm.org/ .
>> Don't email: <a href=mailto:"dont@xxxxxxxxx";> email@xxxxxxxxx </a>
>
> --
> Michal Hocko
> SUSE Labs