[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