Implement success injection

This extends fault injection syntax with :retval= option.
When :retval=VALUE is specified, the syscall number is replaced by -1
and a bogus success VALUE is returned to the callee.

* defs.h (fault_opts): Remove err field, add rval field.
(MAX_ERRNO_VALUE, FAULT_OPTS_RVAL_DEFAULT, FAULT_OPTS_RVAL_DISABLE):
New macros.
* qualify.c (parse_fault_token): Handle retval= token.
(qualify_fault): Update fault_opts initialization after the move
from struct fault_opts.err to struct fault_opts.rval.
* syscall.c (arch_set_success): New prototype.
(inject_syscall_fault_entering): Check opts->rval instead of opts->err.
(update_syscall_fault_exiting): Implement retval injection.
* strace.1: Update the section on fault injection.
* NEWS: Mention retval= option.
* linux/aarch64/set_error.c (arch_set_success): New function.
* linux/alpha/set_error.c: Likewise.
* linux/arc/set_error.c: Likewise.
* linux/arm/set_error.c: Likewise.
* linux/avr32/set_error.c: Likewise.
* linux/bfin/set_error.c: Likewise.
* linux/crisv10/set_error.c: Likewise.
* linux/hppa/set_error.c: Likewise.
* linux/i386/set_error.c: Likewise.
* linux/ia64/set_error.c: Likewise.
* linux/m68k/set_error.c: Likewise.
* linux/metag/set_error.c: Likewise.
* linux/microblaze/set_error.c: Likewise.
* linux/mips/set_error.c: Likewise.
* linux/nios2/set_error.c: Likewise.
* linux/or1k/set_error.c: Likewise.
* linux/powerpc/set_error.c: Likewise.
* linux/riscv/set_error.c: Likewise.
* linux/s390/set_error.c: Likewise.
* linux/sh/set_error.c: Likewise.
* linux/sh64/set_error.c: Likewise.
* linux/sparc/set_error.c: Likewise.
* linux/sparc64/set_error.c: Likewise.
* linux/tile/set_error.c: Likewise.
* linux/x86_64/set_error.c: Likewise.
* linux/xtensa/set_error.c: Likewise.

Closes: https://github.com/strace/strace/issues/3
This commit is contained in:
Elvira Khabirova 2017-01-25 02:59:16 +03:00 committed by Dmitry V. Levin
parent a0795c594a
commit 41d647ce67
31 changed files with 295 additions and 34 deletions

2
NEWS
View File

@ -2,7 +2,7 @@ Noteworthy changes in release ?.?? (????-??-??)
===============================================
* Improvements
* Implemented signal injection as an extension to syscall fault injection.
* Extended fault injection syntax with signal= and retval= options.
Noteworthy changes in release 4.15 (2016-12-14)
===============================================

6
defs.h
View File

@ -200,10 +200,14 @@ typedef struct ioctlent {
struct fault_opts {
uint16_t first;
uint16_t step;
int16_t err;
uint16_t signo;
int rval;
};
#define MAX_ERRNO_VALUE 4095
#define FAULT_OPTS_RVAL_DEFAULT (-(MAX_ERRNO_VALUE + 1))
#define FAULT_OPTS_RVAL_DISABLE (FAULT_OPTS_RVAL_DEFAULT - 1)
/* Trace Control Block */
struct tcb {
int flags; /* See below for TCB_ values */

View File

@ -1,5 +1,7 @@
#define arch_set_error arm_set_error
#define arch_set_success arm_set_success
#include "arm/set_error.c"
#undef arch_set_success
#undef arch_set_error
static int
@ -11,3 +13,13 @@ arch_set_error(struct tcb *tcp)
aarch64_regs.regs[0] = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
if (aarch64_io.iov_len == sizeof(arm_regs))
return arm_set_success(tcp);
aarch64_regs.regs[0] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
alpha_r0 = tcp->u_error;
return upoke(tcp->pid, REG_R0, alpha_r0);
}
static int
arch_set_success(struct tcb *tcp)
{
return upoke(tcp->pid, REG_A3, (alpha_a3 = 0))
|| upoke(tcp->pid, REG_R0, (alpha_r0 = tcp->u_rval));
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
arc_regs.scratch.r0 = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
arc_regs.scratch.r0 = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
arm_regs.ARM_r0 = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
arm_regs.ARM_r0 = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
avr32_regs.r12 = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
avr32_regs.r12 = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
bfin_r0 = -tcp->u_error;
return upoke(tcp->pid, PT_R0, bfin_r0);
}
static int
arch_set_success(struct tcb *tcp)
{
bfin_r0 = tcp->u_rval;
return upoke(tcp->pid, PT_R0, bfin_r0);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
cris_r10 = -tcp->u_error;
return upoke(tcp->pid, 4 * PT_R10, cris_r10);
}
static int
arch_set_success(struct tcb *tcp)
{
cris_r10 = tcp->u_rval;
return upoke(tcp->pid, 4 * PT_R10, cris_r10);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
hppa_r28 = -tcp->u_error;
return upoke(tcp->pid, PT_GR28, hppa_r28);
}
static int
arch_set_success(struct tcb *tcp)
{
hppa_r28 = tcp->u_rval;
return upoke(tcp->pid, PT_GR28, hppa_r28);
}

View File

@ -8,3 +8,14 @@ arch_set_error(struct tcb *tcp)
return set_regs(tcp->pid);
#endif
}
static int
arch_set_success(struct tcb *tcp)
{
i386_regs.eax = tcp->u_rval;
#ifdef HAVE_GETREGS_OLD
return upoke(tcp->pid, 4 * EAX, i386_regs.eax);
#else
return set_regs(tcp->pid);
#endif
}

View File

@ -9,3 +9,13 @@ arch_set_error(struct tcb *tcp)
}
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
ia64_regs.gr[8] = tcp->u_rval;
if (!ia64_ia32mode) {
ia64_regs.gr[10] = 0;
}
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
m68k_regs.d0 = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
m68k_regs.d0 = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
metag_regs.dx[0][0] = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
metag_regs.dx[0][0] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
microblaze_r3 = -tcp->u_error;
return upoke(tcp->pid, 3 * 4, microblaze_r3);
}
static int
arch_set_success(struct tcb *tcp)
{
microblaze_r3 = tcp->u_rval;
return upoke(tcp->pid, 3 * 4, microblaze_r3);
}

View File

@ -5,3 +5,11 @@ arch_set_error(struct tcb *tcp)
mips_REG_A3 = -1;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
mips_REG_V0 = tcp->u_rval;
mips_REG_A3 = 0;
return set_regs(tcp->pid);
}

View File

@ -5,3 +5,11 @@ arch_set_error(struct tcb *tcp)
nios2_regs.regs[2] = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
nios2_regs.regs[7] = 0;
nios2_regs.regs[2] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
or1k_regs.gpr[11] = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_setsuccess(struct tcb *tcp)
{
or1k_regs.gpr[11] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -8,3 +8,16 @@ arch_set_error(struct tcb *tcp)
return set_regs(tcp->pid);
#endif
}
static int
arch_set_success(struct tcb *tcp)
{
ppc_regs.gpr[3] = tcp->u_rval;
ppc_regs.ccr &= ~0x10000000;
#ifdef HAVE_GETREGS_OLD
return upoke(tcp->pid, sizeof(long) * PT_CCR, ppc_regs.ccr) ||
upoke(tcp->pid, sizeof(long) * (PT_R0 + 3), ppc_regs.gpr[3]);
#else
return set_regs(tcp->pid);
#endif
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
riscv_regs.a0 = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
riscv_regs.a0 = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
s390_regset.gprs[2] = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
s390_regset.gprs[2] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
sh_r0 = -tcp->u_error;
return upoke(tcp->pid, 4 * REG_REG0, sh_r0);
}
static int
arch_set_success(struct tcb *tcp)
{
sh_r0 = tcp->u_rval;
return upoke(tcp->pid, 4 * REG_REG0, sh_r0);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
sh64_r9 = -tcp->u_error;
return upoke(tcp->pid, REG_GENERAL(9), sh64_r9);
}
static int
arch_set_success(struct tcb *tcp)
{
sh64_r9 = tcp->u_rval;
return upoke(tcp->pid, REG_GENERAL(9), sh64_r9);
}

View File

@ -5,3 +5,11 @@ arch_set_error(struct tcb *tcp)
sparc_regs.u_regs[U_REG_O0] = tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
sparc_regs.psr &= ~PSR_C;
sparc_regs.u_regs[U_REG_O0] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -5,3 +5,11 @@ arch_set_error(struct tcb *tcp)
sparc_regs.u_regs[U_REG_O0] = tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
sparc_regs.tstate &= ~0x1100000000UL;
sparc_regs.u_regs[U_REG_O0] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
tile_regs.regs[0] = -tcp->u_error;
return set_regs(tcp->pid);
}
static int
arch_set_success(struct tcb *tcp)
{
tile_regs.regs[0] = tcp->u_rval;
return set_regs(tcp->pid);
}

View File

@ -1,6 +1,8 @@
#ifndef HAVE_GETREGS_OLD
# define arch_set_error i386_set_error
# define arch_set_success i386_set_success
# include "i386/set_error.c"
# undef arch_set_success
# undef arch_set_error
#endif /* !HAVE_GETREGS_OLD */
@ -18,3 +20,18 @@ arch_set_error(struct tcb *tcp)
return set_regs(tcp->pid);
#endif
}
static int
arch_set_success(struct tcb *tcp)
{
#ifdef HAVE_GETREGS_OLD
x86_64_regs.rax = (long long) tcp->u_rval;
return upoke(tcp->pid, 8 * RAX, x86_64_regs.rax);
#else
if (x86_io.iov_len == sizeof(i386_regs))
return i386_set_success(tcp);
x86_64_regs.rax = (long long) tcp->u_rval;
return set_regs(tcp->pid);
#endif
}

View File

@ -4,3 +4,10 @@ arch_set_error(struct tcb *tcp)
xtensa_a2 = -tcp->u_error;
return upoke(tcp->pid, REG_A_BASE + 2, xtensa_a2);
}
static int
arch_set_success(struct tcb *tcp)
{
xtensa_a2 = tcp->u_rval;
return upoke(tcp->pid, REG_A_BASE + 2, xtensa_a2);
}

View File

@ -409,12 +409,21 @@ parse_fault_token(const char *const token, struct fault_opts *const fopts)
fopts->step = 0;
}
} else if ((val = strip_prefix("error=", token))) {
intval = string_to_uint_upto(val, 4095);
if (fopts->rval != FAULT_OPTS_RVAL_DEFAULT)
return false;
intval = string_to_uint_upto(val, MAX_ERRNO_VALUE);
if (intval < 0)
intval = find_errno_by_name(val);
if (intval < 1)
return false;
fopts->err = intval;
fopts->rval = -intval;
} else if ((val = strip_prefix("retval=", token))) {
if (fopts->rval != FAULT_OPTS_RVAL_DEFAULT)
return false;
intval = string_to_uint(val);
if (intval < 0)
return false;
fopts->rval = intval;
} else if ((val = strip_prefix("signal=", token))) {
intval = sigstr_to_uint(val);
if (intval < 1 || intval > NSIG_BYTES * 8)
@ -500,7 +509,7 @@ qualify_fault(const char *const str)
struct fault_opts opts = {
.first = 1,
.step = 1,
.err = -1,
.rval = FAULT_OPTS_RVAL_DEFAULT,
.signo = 0
};
char *buf = NULL;
@ -510,11 +519,12 @@ qualify_fault(const char *const str)
}
/*
* If neither error nor signal is specified,
* fallback to the default platform error code.
* If signal is specified but neither retval nor error are specified,
* disable syscall fault injection.
*/
if (opts.signo == 0 && opts.err == -1)
opts.err = 0;
if (opts.signo && opts.rval == FAULT_OPTS_RVAL_DEFAULT) {
opts.rval = FAULT_OPTS_RVAL_DISABLE;
}
struct number_set tmp_set[SUPPORTED_PERSONALITIES];
memset(tmp_set, 0, sizeof(tmp_set));

View File

@ -469,10 +469,12 @@ Note that this is independent from the normal tracing of the
system call which is controlled by the option
.BR -e "\ " trace = write .
.TP
\fB\-e\ fault\fR=\,\fIset\/\fR[:\fBerror\fR=\,\fIerrno\/\fR][:\fBsignal\fR=\,\fIsig\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
Perform a syscall fault injection for the specified set of syscalls.
When a fault is injected into a syscall invocation, the syscall number
is replaced by -1 which corresponds to an invalid syscall.
\fB\-e\ fault\fR=\,\fIset\/\fR[:\fBerror\fR=\,\fIerrno\/\fR|:\fBretval\fR=\,\fIvalue\/\fR][:\fBsignal\fR=\,\fIsig\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
Perform syscall tampering for the specified set of syscalls.
When no options are specified or :\fBerror\fR=\,\fIerrno\/\fR
option is specified, a fault is injected into a syscall invocation:
the syscall number is replaced by -1 which corresponds to an invalid syscall.
If an error code is specified using a symbolic
.I errno
@ -483,26 +485,34 @@ the default error code returned by the kernel, which is traditionally
.B ENOSYS
for invalid syscall numbers on most architectures.
If a signal is specified using either a symbolic value like
If :\fBretval\fR=\,\fIvalue\/\fR option is specified,
success injection is performed: the syscall number is replaced by -1,
but a bogus success value is returned to the callee.
.B error
and
.B retval
are mutually exclusive.
If :\fBsignal\fR=\,\fIsig\/\fR option is specified with either a symbolic value
like
.B SIGSEGV
or a numeric value within 1..\fBSIGRTMAX\fR range,
that signal is delivered on entering every syscall specified by the
.IR set .
If :\fBsignal\fR=\,\fIsig\/\fR option is specified without
:\fBerror\fR=\,\fIerrno\/\fR option, then only a signal
:\fBerror\fR=\,\fIerrno\/\fR or :\fBretval\fR=\,\fIvalue\/\fR options,
then only a signal
.I sig
is delivered without a syscall fault injection.
Conversely, :\fBerror\fR=\,\fIerrno\/\fR option without
Conversely, :\fBerror\fR=\,\fIerrno\/\fR or
:\fBretval\fR=\,\fIvalue\/\fR option without
:\fBsignal\fR=\,\fIsig\/\fR option injects a fault without delivering a signal.
If both :\fBerror\fR=\,\fIerrno\/\fR and :\fBsignal\fR=\,\fIsig\/\fR
options are specified, then both a fault is injected with the specified
error code
.I errno
and a signal
.I sig
is delivered.
If both :\fBerror\fR=\,\fIerrno\/\fR or :\fBretval\fR=\,\fIvalue\/\fR
and :\fBsignal\fR=\,\fIsig\/\fR options are specified, then both
a fault or success is injected and a signal is delivered.
Unless a :\fBwhen\fR=\,\fIexpr\fR subexpression is specified,
an injection is being made into every invocation of each syscall from the
@ -552,10 +562,12 @@ and
.I step
is 1..65535.
If a fault expression contains multiple
A fault expression can contain only one
.BR error =
specifications, the last one takes precedence.
Likewise, if a fault expression contains multiple
or
.BR retval =
specification.
If a fault expression contains multiple
.BR when =
specifications, the last one takes precedence.

View File

@ -559,6 +559,7 @@ static int arch_get_scno(struct tcb *tcp);
static int arch_set_scno(struct tcb *, kernel_ulong_t);
static void get_error(struct tcb *, const bool);
static int arch_set_error(struct tcb *);
static int arch_set_success(struct tcb *);
struct fault_opts *fault_vec[SUPPORTED_PERSONALITIES];
@ -595,7 +596,7 @@ inject_syscall_fault_entering(struct tcb *tcp, unsigned int *signo)
if (opts->signo > 0)
*signo = opts->signo;
if (opts->err != -1 && !arch_set_scno(tcp, -1))
if (opts->rval != FAULT_OPTS_RVAL_DISABLE && !arch_set_scno(tcp, -1))
tcp->flags |= TCB_FAULT_INJ;
return 0;
@ -606,11 +607,29 @@ update_syscall_fault_exiting(struct tcb *tcp)
{
struct fault_opts *opts = tcb_fault_opts(tcp);
if (opts && opts->err > 0 && tcp->u_error != (uint16_t) opts->err) {
unsigned long u_error = tcp->u_error;
tcp->u_error = opts->err;
if (arch_set_error(tcp))
tcp->u_error = u_error;
if (!opts)
return 0;
if (opts->rval >= 0) {
kernel_long_t u_rval = tcp->u_rval;
tcp->u_rval = opts->rval;
if (arch_set_success(tcp)) {
tcp->u_rval = u_rval;
} else {
tcp->u_error = 0;
}
} else {
unsigned long new_error = -opts->rval;
if (new_error != tcp->u_error && new_error <= MAX_ERRNO_VALUE) {
unsigned long u_error = tcp->u_error;
tcp->u_error = new_error;
if (arch_set_error(tcp)) {
tcp->u_error = u_error;
}
}
}
return 0;
@ -819,11 +838,11 @@ trace_syscall_exiting(struct tcb *tcp)
if (tcp->qual_flg & QUAL_RAW) {
if (u_error) {
tprintf("= -1 (errno %lu)", u_error);
if (syscall_fault_injected(tcp))
tprints(" (INJECTED)");
} else {
tprintf("= %#" PRI_klx, tcp->u_rval);
}
if (syscall_fault_injected(tcp))
tprints(" (INJECTED)");
}
else if (!(sys_res & RVAL_NONE) && u_error) {
switch (u_error) {
@ -944,6 +963,8 @@ trace_syscall_exiting(struct tcb *tcp)
}
if ((sys_res & RVAL_STR) && tcp->auxstr)
tprintf(" (%s)", tcp->auxstr);
if (syscall_fault_injected(tcp))
tprints(" (INJECTED)");
}
if (Tflag) {
tv_sub(&tv, &tv, &tcp->etime);