[PATCH 3/4] Introduce syscall fault injection feature

Nahim El Atmani nahim+dev at naam.me
Wed Jul 27 18:41:16 UTC 2016


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') {
+		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





More information about the Strace-devel mailing list