[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