[RFC] Add basic syscalls fault injection

Nahim El Atmani nahim+dev at naam.me
Sat May 14 21:32:49 UTC 2016


From: Nahim El Atmani <naam at lse.epita.fr>

This patch adds a basic support for the faults injection in strace.
As a reminder fault injection is something that could be really useful in many
ways for developpers and testers. It could be used in test suites context to
assert that an application behave correctly on errors. By errors we don't
necessarely mean that something went wrong, in fact an asynchroneous
application may want to have some tests for the code path when getting an
EAGAIN value on a read for example. Normally the tester have to setup a complex
environment to create artificial errors or simply assert that the code works
the way it should. strace would definetly get rid of that easily. On another
hand if we considere the high proportion of bugs found by fuzzers nowadays it's
pretty clear that tampering with the underneath of applications, that is with
syscalls, looks promising.
For that I reused the option parser and the custom parsing function to add a
new -e keyword, `failwith' or in the shorter version `fw'. The API follows the
following form: strace­-e failwith=<syscall>:<occurrence>:<error>,...
Since the syscall is directly passed so qual_syscall() everything you used
before is still available (syscall number, name, classes...). The occurrence is
actually an integer but the room is made to support a percentage and and
introduce non determinism if needed. Likewise the actual parser support error
as an integer and further work is necessary to support the string
representation. Here is a usage example with a dummy C program that print a
message, try to kill himself and print a flag and a debug message:

strace -e kill -e failwith=kill:1:22 ./dummy
Can you get the flag before I kill myself?
kill(22978, SIGKILL)                    = -1 EINVAL (Invalid argument)
CTF{Faults_Injection_Can_Save_lives}

Syscall returned: -1 Invalid argument(22)
+++ exited with 0 +++

This currently only work in x86_64, further work is necessary to get it done on
others.


Signed-off-by: Nahim El Atmani <nahim+dev at naam.me>
---
 defs.h    |   8 +++++
 syscall.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 115 insertions(+), 2 deletions(-)

diff --git a/defs.h b/defs.h
index a3b1dda..92aad87 100644
--- a/defs.h
+++ b/defs.h
@@ -288,6 +288,13 @@ typedef struct ioctlent {
 	unsigned int code;
 } struct_ioctlent;
 
+typedef struct fail {
+	int err;
+	int cnt;
+	int occ;
+	int fail;
+} struct_fail;
+
 /* Trace Control Block */
 struct tcb {
 	int flags;		/* See below for TCB_ values */
@@ -355,6 +362,7 @@ 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 */
+#define QUAL_FAIL	0x080	/* this system call fail on purpose */
 typedef uint8_t qualbits_t;
 #define UNDEFINED_SCNO	0x100	/* Used only in tcp->qual_flg */
 
diff --git a/syscall.c b/syscall.c
index d71ead3..a3b22ac 100644
--- a/syscall.c
+++ b/syscall.c
@@ -266,6 +266,8 @@ enum {
 	MIN_QUALS = MAX_NSYSCALLS > 255 ? MAX_NSYSCALLS : 255
 };
 
+struct_fail fails_vec[MAX_NSYSCALLS];
+
 #if SUPPORTED_PERSONALITIES > 1
 unsigned current_personality;
 
@@ -359,7 +361,7 @@ update_personality(struct tcb *tcp, unsigned int personality)
 }
 #endif
 
-static int qual_syscall(), qual_signal(), qual_desc();
+static int qual_syscall(), qual_signal(), qual_desc(), qual_fail();
 
 static const struct qual_options {
 	unsigned int bitflag;
@@ -384,6 +386,8 @@ static const struct qual_options {
 	{ QUAL_WRITE,	"write",	qual_desc,	"descriptor"	},
 	{ QUAL_WRITE,	"writes",	qual_desc,	"descriptor"	},
 	{ QUAL_WRITE,	"w",		qual_desc,	"descriptor"	},
+	{ QUAL_FAIL,	"failwith",	qual_fail,	"system call"	},
+	{ QUAL_FAIL,	"fw",		qual_fail,	"system call"	},
 	{ 0,		NULL,		NULL,		NULL		},
 };
 
@@ -448,6 +452,64 @@ qual_syscall(const char *s, const unsigned int bitflag, const int not)
 }
 
 static int
+qual_fail(const char *s, const unsigned int bitflag, const int not)
+{
+	char *ms, *ss, *end;
+	char *saveptr;
+	int occ, err;
+	unsigned int i;
+
+	ms = ss = xstrdup(s);
+	ss = strtok_r(ss, ":", &saveptr);
+	if (!ss)
+		goto bad_format;
+
+	for (i = 0; i <  nsyscall_vec[current_personality]; ++i)
+		if (sysent_vec[current_personality][i].sys_name
+		    && !strcmp(ss, sysent_vec[current_personality][i].sys_name))
+			break;
+
+	if (i == nsyscall_vec[current_personality])
+		goto bad_format;
+	qual_syscall(ss, bitflag, not);
+
+	ss = strtok_r(NULL, ":", &saveptr);
+	if (!ss)
+		goto bad_format;
+
+	occ = strtol(ss, &end, 10);
+	if (end == ss || (*end != '\0' && *end != '%') || errno == ERANGE)
+		goto bad_format;
+	fails_vec[i].occ = occ;
+	if (*end == '%')
+		fails_vec[i].cnt = -1;
+
+	ss = strtok_r(NULL, ":", &saveptr);
+	if (!ss)
+		goto bad_format;
+
+	if (*ss >= '0' && *ss <= '9') {
+		err = strtol(ss, &end, 10);
+		if (end == ss || *end != '\0' || errno == ERANGE)
+			goto bad_format;
+	}
+	else {
+		/* TODO: support string form. */
+	}
+	fails_vec[i].err = err;
+	fails_vec[i].fail = 0;
+
+	free(ms);
+
+	return 0;
+
+bad_format:
+	free(ms);
+
+	return -1;
+}
+
+static int
 qual_signal(const char *s, const unsigned int bitflag, const int not)
 {
 	unsigned int i;
@@ -789,6 +851,36 @@ static void get_error(struct tcb *, const bool);
 static int getregs_old(pid_t);
 #endif
 
+static long
+fail_syscall_enter(struct tcb *tcp)
+{
+	if (fails_vec[tcp->scno].cnt != -1) {
+		if (fails_vec[tcp->scno].cnt++ % fails_vec[tcp->scno].occ)
+			return 0;
+	}
+	else { /* TODO: Support the non deterministic way */
+		return 0;
+	}
+
+	fails_vec[tcp->scno].fail = 1;
+	return ptrace(PTRACE_POKEUSER, tcp->pid,
+		      offsetof(struct user, regs.orig_rax),
+		      (unsigned long)-1);
+}
+
+static inline long
+fail_syscall_exit(struct tcb *tcp)
+{
+	if (!fails_vec[tcp->scno].fail)
+		return 0;
+
+	fails_vec[tcp->scno].fail = 0;
+	tcp->u_error = fails_vec[tcp->scno].err;
+	return ptrace(PTRACE_POKEUSER, tcp->pid,
+		      offsetof(struct user, regs.rax),
+		      (unsigned long)-fails_vec[tcp->scno].err);
+}
+
 static int
 trace_syscall_entering(struct tcb *tcp)
 {
@@ -842,6 +934,8 @@ trace_syscall_entering(struct tcb *tcp)
 # endif
 	}
 #endif
+	if (tcp->qual_flg & QUAL_FAIL)
+		fail_syscall_enter(tcp);
 
 	if (!(tcp->qual_flg & QUAL_TRACE)
 	 || (tracing_paths && !pathtrace_match(tcp))
@@ -908,6 +1002,9 @@ trace_syscall_exiting(struct tcb *tcp)
 	update_personality(tcp, tcp->currpers);
 #endif
 	res = (get_regs_error ? -1 : get_syscall_result(tcp));
+
+	if (tcp->qual_flg & QUAL_FAIL && fails_vec[tcp->scno].fail)
+		fail_syscall_exit(tcp);
 	if (filtered(tcp) || hide_log_until_execve)
 		goto ret;
 
@@ -973,7 +1070,15 @@ trace_syscall_exiting(struct tcb *tcp)
 	tprints(") ");
 	tabto();
 	u_error = tcp->u_error;
-	if (tcp->qual_flg & QUAL_RAW) {
+	if (tcp->qual_flg & QUAL_FAIL && fails_vec[tcp->scno].fail) {
+		if ((unsigned int)u_error < nerrnos && errnoent[u_error])
+			tprintf("= -1 %s ", errnoent[tcp->u_error]);
+		else
+			tprintf("= -1 %ld ", u_error);
+		tprints("(DISCARDED)");
+		fails_vec[tcp->scno].fail = 0;
+	}
+	else if (tcp->qual_flg & QUAL_RAW) {
 		if (u_error)
 			tprintf("= -1 (errno %ld)", u_error);
 		else
-- 
2.8.2





More information about the Strace-devel mailing list