[PATCH 3/4] Introduce syscall fault injection feature
Gabriel Laskar
gabriel at lse.epita.fr
Fri Jul 29 13:02:57 UTC 2016
On Wed, Jul 27, 2016 at 08:41:16PM +0200, Nahim El Atmani wrote:
> From: Nahim El Atmani <naam at lse.epita.fr>
>
> From: Nahim El Atmani <nahim+dev at naam.me>
>
> * defs.h: Add new qualifier and struct fault_opts
> * linux/x86_64/fault.h: New file.
> (fault_set_sc_err): New function.
> (fault_discard_sc): Likewise.
> * Makefile.am: Add it.
> * syscall.c (reallocate_fault): New function.
> (qualify_one): Also extend the size of faults_vec.
> (qual_fault): New function.
> (qual_signal): Also reallocate faults_vec.
> (fault_syscall_enter): New function.
> (fault_syscall_exit): Likewise.
> (trace_syscall_entering): Discard syscall if needed.
> (trace_syscall_exiting): Set syscall's return error value, print when syscall
> has been discarded.
>
> Signed-off-by: Nahim El Atmani <nahim+dev at naam.me>
> Reviewed-By: Gabriel Laskar <gabriel at lse.epita.fr>
> ---
> * The fault injection is currently only available for i386 & x86_64, hence a
> new configure flag have been created (--enable-fault-injection) to keep the
> build clean on other architectures.
> * Concerning the struct qual_options, the name 'fault' and short name 'f' are
> not fixed, feel free to comment if something better comes to your mind.
> * Regarding the error number checks in qual_fault(), they're light on purpose
> to allow one to return any kind of return value.
> * There is still one TODO left which is supporting the fuzzy approach using
> percentage as an occurrence. It should comes soon since all the option
> parsing is done. On that note fuzzing means we want to be able to reproduce,
> so we have to keep the seed somewhere and take it as an input. I my opinion
> using the environment for this kind of things is better than adding a new
> option, what do you think?
>
> Makefile.am | 5 ++
> configure.ac | 22 ++++++-
> defs.h | 25 ++++++++
> linux/i386/fault.h | 42 +++++++++++++
> linux/x86_64/fault.h | 42 +++++++++++++
> syscall.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++-
> 6 files changed, 307 insertions(+), 3 deletions(-)
> create mode 100644 linux/i386/fault.h
> create mode 100644 linux/x86_64/fault.h
>
> diff --git a/Makefile.am b/Makefile.am
> index 1e7554e..5272708 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -659,6 +659,11 @@ EXTRA_DIST = \
> xlat/gen.sh \
> xlate.el
>
> +if ENABLE_FAULT_INJECTION
> +EXTRA_DIST += linux/x86_64/fault.h \
> + linux/i386/fault.h
> +endif
> +
> .PHONY: srpm
> srpm: dist-xz
> rpmbuild --define '%_srcrpmdir .' -ts $(distdir).tar.xz
> diff --git a/configure.ac b/configure.ac
> index 4af1649..d7302a6 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -680,6 +680,27 @@ AC_SUBST(dl_LIBS)
>
> AC_PATH_PROG([PERL], [perl])
>
> +AC_ARG_ENABLE([fault-injection],
> + [AS_HELP_STRING([--enable-fault-injection],
> + [enable fault injection support (x86_64 only)])],
> + [], [enable_fault_injection=no])
> +case "$enable_fault_injection" in
> + yes) enable_fault_injection=1 ;;
> + no) enable_fault_injection=0 ;;
> + *) AC_MSG_ERROR([bad value $enable_fault_injection for fault-injection option]) ;;
> +esac
> +AC_DEFINE_UNQUOTED([ENABLE_FAULT_INJECTION], [$enable_fault_injection],
> + [Define to 1 if you want fault injection support.])
> +
> +AM_CONDITIONAL([ENABLE_FAULT_INJECTION], [test "x$enable_fault_injection" = x1])
> +AC_MSG_CHECKING([for fault injection support])
> +if [test $enable_fault_injection]
> +then
> + AC_MSG_RESULT([yes])
> +else
> + AC_MSG_RESULT([no])
> +fi
> +
> dnl stack trace with libunwind
> libunwind_CPPFLAGS=
> libunwind_LDFLAGS=
> @@ -767,7 +788,6 @@ if test "x$use_libunwind" = xyes; then
> AC_SUBST(libunwind_CPPFLAGS)
> fi
> AM_CONDITIONAL([USE_LIBUNWIND], [test "x$use_libunwind" = xyes])
> -AC_MSG_RESULT([$use_libunwind])
>
> if test "$arch" = mips && test "$no_create" != yes; then
> mkdir -p linux/mips
> diff --git a/defs.h b/defs.h
> index 2edf943..d4b0dde 100644
> --- a/defs.h
> +++ b/defs.h
> @@ -363,6 +363,9 @@ struct tcb {
> #define QUAL_SIGNAL 0x010 /* report events with this signal */
> #define QUAL_READ 0x020 /* dump data read on this file descriptor */
> #define QUAL_WRITE 0x040 /* dump data written to this file descriptor */
> +#if ENABLE_FAULT_INJECTION
> +#define QUAL_FAULT 0x080 /* this system call fail on purpose */
> +#endif
> typedef uint8_t qualbits_t;
>
> #define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
> @@ -458,6 +461,28 @@ enum iov_decode {
> IOV_DECODE_NETLINK
> };
>
> +#if ENABLE_FAULT_INJECTION
> +/* Fault injection qualifiers concerning syscalls:
> + * FAULT_ENTER: already discarded, but error is not yet propagated
> + * FAULT_DONE: already discarded and we were using FAULT_AT (prevent overflow)
> + * FAULT_AT: discard syscall at the nth time
> + * FAULT_EVERY: discard syscall every nth time
> + * FAULT_FUZZY: discard syscall on a random basis every nth percent of the time
> + */
> +#define FAULT_ENTER 1
> +#define FAULT_EVERY (1 << 2)
> +#define FAULT_FUZZY (1 << 3)
> +#define FAULT_AT (1 << 4)
> +#define FAULT_DONE (1 << 5)
> +
> +struct fault_opts {
> + int err;
> + int cnt;
> + int occ;
> + qualbits_t flag;
> +};
> +#endif
> +
> typedef enum {
> CFLAG_NONE = 0,
> CFLAG_ONLY_STATS,
> diff --git a/linux/i386/fault.h b/linux/i386/fault.h
> new file mode 100644
> index 0000000..7306a7f
> --- /dev/null
> +++ b/linux/i386/fault.h
> @@ -0,0 +1,42 @@
> +/*
> + * Copyright (c) 2016 Nahim El Atmani <nahim+dev at naam.me>
> + * All rights reserved.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + * 3. The name of the author may not be used to endorse or promote products
> + * derived from this software without specific prior written permission.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
> + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
> + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
> + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +static inline long
> +fault_set_sc_err(struct tcb *tcp, int error)
> +{
> + return ptrace(PTRACE_POKEUSER, tcp->pid,
> + offsetof(struct user, regs.eax),
> + (unsigned long long int)error);
> +}
> +
> +static inline long
> +fault_discard_sc(struct tcb *tcp)
> +{
> + return ptrace(PTRACE_POKEUSER, tcp->pid,
> + offsetof(struct user, regs.orig_eax),
> + (unsigned long long int)-1);
> +}
> diff --git a/linux/x86_64/fault.h b/linux/x86_64/fault.h
> new file mode 100644
> index 0000000..17dcbee
> --- /dev/null
> +++ b/linux/x86_64/fault.h
> @@ -0,0 +1,42 @@
> +/*
> + * Copyright (c) 2016 Nahim El Atmani <nahim+dev at naam.me>
> + * All rights reserved.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + * 3. The name of the author may not be used to endorse or promote products
> + * derived from this software without specific prior written permission.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
> + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
> + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
> + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +static inline long
> +fault_set_sc_err(struct tcb *tcp, int error)
> +{
> + return ptrace(PTRACE_POKEUSER, tcp->pid,
> + offsetof(struct user, regs.rax),
> + (unsigned long long int)error);
> +}
> +
> +static inline long
> +fault_discard_sc(struct tcb *tcp)
> +{
> + return ptrace(PTRACE_POKEUSER, tcp->pid,
> + offsetof(struct user, regs.orig_rax),
> + (unsigned long long int)-1);
> +}
> diff --git a/syscall.c b/syscall.c
> index cf087d6..b886094 100644
> --- a/syscall.c
> +++ b/syscall.c
> @@ -266,6 +266,14 @@ enum {
> MIN_QUALS = MAX_NSYSCALLS > 255 ? MAX_NSYSCALLS : 255
> };
>
> +#if ENABLE_FAULT_INJECTION
> +#include "fault.h"
> +struct fault_opts *faults_vec[SUPPORTED_PERSONALITIES];
> +#define syscall_failed(tcp) \
> + (((tcp)->qual_flg & QUAL_FAULT) && \
> + (faults_vec[current_personality][(tcp)->scno].flag & FAULT_ENTER))
> +#endif
> +
> #if SUPPORTED_PERSONALITIES > 1
> unsigned current_personality;
>
> @@ -359,6 +367,9 @@ update_personality(struct tcb *tcp, unsigned int personality)
> }
> #endif
>
> +#if ENABLE_FAULT_INJECTION
> +static int qual_fault();
> +#endif
> static int qual_syscall(), qual_signal(), qual_desc();
>
> static const struct qual_options {
> @@ -384,6 +395,10 @@ static const struct qual_options {
> { QUAL_WRITE, "write", qual_desc, "descriptor" },
> { QUAL_WRITE, "writes", qual_desc, "descriptor" },
> { QUAL_WRITE, "w", qual_desc, "descriptor" },
> +#if ENABLE_FAULT_INJECTION
> + { QUAL_FAULT, "fault", qual_fault, "fault argument"},
> + { QUAL_FAULT, "f", qual_fault, "fault argument"},
> +#endif
> { 0, NULL, NULL, NULL },
> };
>
> @@ -406,6 +421,15 @@ reallocate_qual(const unsigned int n)
> num_quals = n;
> }
>
> +#if ENABLE_FAULT_INJECTION
> +static inline void
> +reallocate_fault(const unsigned int n)
> +{
> + reallocate_vec((void **)faults_vec, num_quals,
> + sizeof(struct fault_opts), n);
> +}
> +#endif
> +
> static int
> find_errno_by_name(const char *name)
> {
> @@ -439,8 +463,12 @@ qualify_one(const unsigned int n, unsigned int bitflag, const int not, const int
> {
> int p;
>
> - if (num_quals <= n)
> + if (num_quals <= n) {
> reallocate_qual(n + 1);
> +#if ENABLE_FAULT_INJECTION
> + reallocate_fault(n + 1);
> +#endif
> + }
>
> for (p = 0; p < SUPPORTED_PERSONALITIES; p++) {
> if (pers == p || pers < 0) {
> @@ -481,6 +509,78 @@ qual_syscall(const char *s, const unsigned int bitflag, const int not)
> return rc;
> }
>
> +#if ENABLE_FAULT_INJECTION
> +static int
> +qual_fault(const char *s, const unsigned int bitflag, const int not)
> +{
> + int i, p, negative;
> + struct fault_opts opts;
> + char *ms, *ss, *end, *saveptr;
> +
> + ms = ss = xstrdup(s);
> + ss = strtok_r(ss, ":", &saveptr);
> + if (!ss)
> + goto bad_format;
> +
> + ss = strtok_r(NULL, ":", &saveptr);
> + if (!ss)
> + goto bad_format;
> +
> + opts.occ = strtol(ss, &end, 10);
> + if (end == ss || (*end != '\0' && *end != '%' && *end != '.')
> + || errno == ERANGE || errno == EINVAL || opts.occ < 1
> + || (*end == '%' && opts.occ > 100))
> + goto bad_format;
> + switch (*end) {
> + case '%': opts.flag |= FAULT_FUZZY; break;
> + case '.': opts.flag |= FAULT_EVERY; break;
> + default: opts.flag |= FAULT_AT;
> + }
> +
> + ss = strtok_r(NULL, ":", &saveptr);
> + if (!ss)
> + goto bad_format;
> +
> + negative = 0;
> + if (*ss == '-') {
> + negative = 1;
> + ++ss;
> + }
> +
> + if (*ss >= '0' && *ss <= '9') {
We should be able to pass a negative number as an error value, no?
This also justify the usage of string_to_int instead of string_to_uint.
> + if (-1 == (opts.err = string_to_int(ss)))
> + goto bad_format;
> + }
> + else {
> + opts.err = find_errno_by_name(ss);
> + if (opts.err < 0)
> + goto bad_format;
> + }
> + if (negative)
> + opts.err *= -1;
> +
> + for (p = 0; p < SUPPORTED_PERSONALITIES; p++) {
> + if (*ms >= '0' && *ms <= '9')
> + i = string_to_uint(ms);
> + else
> + i = find_scno_by_name(ms, p);
> +
> + if (i < 0)
> + goto bad_format;
> +
> + qualify_one(i, bitflag, not, -1);
> + memcpy(&faults_vec[p][i], &opts, sizeof(struct fault_opts));
> + }
> +
> + free(ms);
> + return 0;
> +
> +bad_format:
> + free(ms);
> + return -1;
> +}
> +#endif
> +
> static int
> qual_signal(const char *s, const unsigned int bitflag, const int not)
> {
> @@ -546,8 +646,12 @@ qualify(const char *s)
> int not;
> unsigned int i;
>
> - if (num_quals == 0)
> + if (num_quals == 0) {
> reallocate_qual(MIN_QUALS);
> +#if ENABLE_FAULT_INJECTION
> + reallocate_fault(MIN_QUALS);
> +#endif
> + }
>
> opt = &qual_options[0];
> for (i = 0; (p = qual_options[i].option_name); i++) {
> @@ -808,6 +912,41 @@ static void get_error(struct tcb *, const bool);
> static int getregs_old(pid_t);
> #endif
>
> +#if ENABLE_FAULT_INJECTION
> +static long
> +fault_syscall_enter(struct tcb *tcp)
> +{
> + struct fault_opts *opts = &faults_vec[current_personality][tcp->scno];
> +
> + if (opts->flag & FAULT_DONE)
> + return 0;
> + opts->cnt++;
> + if ((opts->flag & FAULT_EVERY) && (opts->cnt % opts->occ))
> + return 0;
> + if ((opts->flag & FAULT_AT) && (opts->cnt != opts->occ))
> + return 0;
> + if (opts->flag & FAULT_FUZZY) /* TODO: Support the fuzzy way */
> + return 0;
> + opts->flag |= FAULT_ENTER;
> +
> + return fault_discard_sc(tcp);
> +}
> +
> +static inline long
> +fault_syscall_exit(struct tcb *tcp)
> +{
> + struct fault_opts *opts = &faults_vec[current_personality][tcp->scno];
> +
> + if (!(opts->flag & FAULT_ENTER))
> + return 0;
> + else if (opts->flag & FAULT_AT)
> + opts->flag |= FAULT_DONE;
> +
> + tcp->u_error = opts->err;
> + return fault_set_sc_err(tcp, opts->err);
> +}
> +#endif
> +
> static int
> trace_syscall_entering(struct tcb *tcp)
> {
> @@ -868,6 +1007,12 @@ trace_syscall_entering(struct tcb *tcp)
> return 0;
> }
>
> +#if ENABLE_FAULT_INJECTION
> + if (tcp->qual_flg & QUAL_FAULT)
> + if (fault_syscall_enter(tcp))
> + res = -1;
> +#endif
> +
> tcp->flags &= ~TCB_FILTERED;
>
> if (cflag == CFLAG_ONLY_STATS || hide_log_until_execve) {
> @@ -922,9 +1067,17 @@ trace_syscall_exiting(struct tcb *tcp)
> update_personality(tcp, tcp->currpers);
> #endif
> res = (get_regs_error ? -1 : get_syscall_result(tcp));
> +
> if (filtered(tcp) || hide_log_until_execve)
> goto ret;
>
> +#if ENABLE_FAULT_INJECTION
> + if (tcp->qual_flg & QUAL_FAULT
> + && (faults_vec[current_personality][tcp->scno].flag & FAULT_ENTER))
> + if (fault_syscall_exit(tcp))
> + res = -1;
> +#endif
> +
> if (cflag) {
> count_syscall(tcp, &tv);
> if (cflag == CFLAG_ONLY_STATS) {
> @@ -985,13 +1138,24 @@ trace_syscall_exiting(struct tcb *tcp)
> tprints(") ");
> tabto();
> u_error = tcp->u_error;
> +
> if (tcp->qual_flg & QUAL_RAW) {
> if (u_error)
> tprintf("= -1 (errno %ld)", u_error);
> else
> tprintf("= %#lx", tcp->u_rval);
> +#if ENABLE_FAULT_INJECTION
> + if (syscall_failed(tcp)) {
> + tprints("(DISCARDED)");
> + faults_vec[current_personality][tcp->scno].flag &= ~FAULT_ENTER;
> + }
> +#endif
> }
> +#if ENABLE_FAULT_INJECTION
> + else if ((!(sys_res & RVAL_NONE) && u_error) || syscall_failed(tcp)) {
> +#else
> else if (!(sys_res & RVAL_NONE) && u_error) {
> +#endif
> switch (u_error) {
> /* Blocked signals do not interrupt any syscalls.
> * In this case syscalls don't return ERESTARTfoo codes.
> @@ -1054,6 +1218,12 @@ trace_syscall_exiting(struct tcb *tcp)
> else
> tprintf("= -1 ERRNO_%lu (%s)", u_error,
> strerror(u_error));
> +#if ENABLE_FAULT_INJECTION
> + if (syscall_failed(tcp)) {
> + faults_vec[current_personality][tcp->scno].flag &= ~FAULT_ENTER;
> + tprintf("(DISCARDED)");
> + }
> +#endif
> break;
> }
> if ((sys_res & RVAL_STR) && tcp->auxstr)
> --
> Nahim El Atmani
>
>
> ------------------------------------------------------------------------------
> _______________________________________________
> Strace-devel mailing list
> Strace-devel at lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/strace-devel
--
Gabriel Laskar
More information about the Strace-devel
mailing list