Implement -e inject= option

Introduce -e inject= as a general injection option,
limit -e fault= option to syscall fault injection.

Change default return code of syscall fault injection to ENOSYS.

* qualify.c (parse_inject_token): Add fault_tokens_only argument,
do not accept retval= and signal= tokens when fault_tokens_only
is set to true.
(parse_inject_expression): Add fault_tokens_only argument,
forward it to parse_inject_token.
(qualify_inject_common): New function.
(qualify_fault): Use it.
(qualify_inject): New function.
(qual_options): New entry.
* strace.1: Describe -e inject= option.
* NEWS: Mention -e inject= option.
* tests/qual_fault-syntax.test: Test that -e fault= option does not
support retval=, signal=, and multiple error= tokens.
* tests/qual_fault.c (DEFAULT_ERRNO): Set to ENOSYS unconditionally.
* tests/qual_inject-retval.test: Replace -e fault= option
with -e inject= option.
* tests/qual_inject-syntax.test: New test.
* tests/Makefile.am (MISC_TESTS): Add it.
This commit is contained in:
Дмитрий Левин 2017-02-08 09:28:38 +00:00
parent d1dfcc533e
commit be73ca4bae
8 changed files with 207 additions and 52 deletions

3
NEWS
View File

@ -2,7 +2,8 @@ Noteworthy changes in release ?.?? (????-??-??)
===============================================
* Improvements
* Extended fault injection syntax with signal= and retval= options.
* Implemented syscall return value injection (-e inject=SET:retval= option).
* Implemented signal injection (-e inject=SET:signal= option).
* Updated lists of ioctl commands from Linux 4.10.
Noteworthy changes in release 4.15 (2016-12-14)

View File

@ -373,7 +373,8 @@ find_errno_by_name(const char *name)
}
static bool
parse_inject_token(const char *const token, struct inject_opts *const fopts)
parse_inject_token(const char *const token, struct inject_opts *const fopts,
const bool fault_tokens_only)
{
const char *val;
int intval;
@ -417,14 +418,14 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts)
if (intval < 1)
return false;
fopts->rval = -intval;
} else if ((val = strip_prefix("retval=", token))) {
} else if (!fault_tokens_only && (val = strip_prefix("retval=", token))) {
if (fopts->rval != INJECT_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))) {
} else if (!fault_tokens_only && (val = strip_prefix("signal=", token))) {
intval = sigstr_to_uint(val);
if (intval < 1 || intval > NSIG_BYTES * 8)
return false;
@ -438,7 +439,8 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts)
static char *
parse_inject_expression(const char *const s, char **buf,
struct inject_opts *const fopts)
struct inject_opts *const fopts,
const bool fault_tokens_only)
{
char *saveptr = NULL;
char *name = NULL;
@ -449,7 +451,7 @@ parse_inject_expression(const char *const s, char **buf,
token = strtok_r(NULL, ":", &saveptr)) {
if (!name)
name = token;
else if (!parse_inject_token(token, fopts))
else if (!parse_inject_token(token, fopts, fault_tokens_only))
goto parse_error;
}
@ -504,7 +506,9 @@ qualify_raw(const char *const str)
}
static void
qualify_fault(const char *const str)
qualify_inject_common(const char *const str,
const bool fault_tokens_only,
const char *const description)
{
struct inject_opts opts = {
.first = 1,
@ -513,22 +517,28 @@ qualify_fault(const char *const str)
.signo = 0
};
char *buf = NULL;
char *name = parse_inject_expression(str, &buf, &opts);
char *name = parse_inject_expression(str, &buf, &opts, fault_tokens_only);
if (!name) {
error_msg_and_die("invalid %s '%s'", "fault argument", str);
error_msg_and_die("invalid %s '%s'", description, str);
}
/*
* If signal is specified but neither retval nor error are specified,
* disable syscall fault injection.
*/
if (opts.signo && opts.rval == INJECT_OPTS_RVAL_DEFAULT) {
opts.rval = INJECT_OPTS_RVAL_DISABLE;
if (opts.rval == INJECT_OPTS_RVAL_DEFAULT) {
/* If neither retval nor error is specified, then ... */
if (opts.signo) {
/* disable syscall fault injection if signal is specified. */
opts.rval = INJECT_OPTS_RVAL_DISABLE;
} else if (fault_tokens_only) {
/* default error code for fault= syntax is ENOSYS */
opts.rval = -ENOSYS;
} else {
/* an error has to be specified in inject= syntax. */
error_msg_and_die("invalid %s '%s'", description, str);
}
}
struct number_set tmp_set[SUPPORTED_PERSONALITIES];
memset(tmp_set, 0, sizeof(tmp_set));
qualify_syscall_tokens(name, tmp_set, "fault argument");
qualify_syscall_tokens(name, tmp_set, description);
free(buf);
@ -559,6 +569,18 @@ qualify_fault(const char *const str)
}
}
static void
qualify_fault(const char *const str)
{
qualify_inject_common(str, true, "fault argument");
}
static void
qualify_inject(const char *const str)
{
qualify_inject_common(str, false, "inject argument");
}
static const struct qual_options {
const char *name;
void (*qualify)(const char *);
@ -581,6 +603,7 @@ static const struct qual_options {
{ "writes", qualify_write },
{ "w", qualify_write },
{ "fault", qualify_fault },
{ "inject", qualify_inject },
};
void

View File

@ -343,8 +343,9 @@ is one of
.BR signal ,
.BR read ,
.BR write ,
.BR fault ,
or
.B fault
.B inject
and
.I value
is a qualifier-dependent symbol or number. The default
@ -469,31 +470,33 @@ 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|:\fBretval\fR=\,\fIvalue\/\fR][:\fBsignal\fR=\,\fIsig\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
\fB\-e\ inject\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
value like
.B ENOSYS
or a numeric value within 1..4095 range, this error code overrides
the default error code returned by the kernel, which is traditionally
.B ENOSYS
for invalid syscall numbers on most architectures.
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.
At least one of
.BR error ,
.BR retval ,
or
.B signal
options has to be specified.
.B error
and
.B retval
are mutually exclusive.
If :\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,
and the error code is specified using a symbolic
.I errno
value like
.B ENOSYS
or a numeric value within 1..4095 range.
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.
If :\fBsignal\fR=\,\fIsig\/\fR option is specified with either a symbolic value
like
.B SIGSEGV
@ -525,7 +528,7 @@ The format of the subexpression is one of the following:
.RS 4
For every syscall from the
.IR set ,
perform a syscall fault injection for the syscall invocation number
perform an injection for the syscall invocation number
.I first
only.
.RE
@ -534,7 +537,7 @@ only.
.RS 4
For every syscall from the
.IR set ,
perform syscall fault injections for the syscall invocation number
perform injections for the syscall invocation number
.I first
and all subsequent invocations.
.RE
@ -543,7 +546,7 @@ and all subsequent invocations.
.RS 4
For every syscall from the
.IR set ,
perform syscall fault injections for syscall invocations number
perform injections for syscall invocations number
.IR first ,
.IR first + step ,
.IR first + step + step ,
@ -554,7 +557,7 @@ and so on.
For example, to fail each third and subsequent chdir syscalls with
.BR ENOENT ,
use
\fB\-e\ fault\fR=\,\fIchdir\/\fR:\fBerror\fR=\,\fIENOENT\/\fR:\fBwhen\fR=\,\fI3\/\fB+\fR.
\fB\-e\ inject\fR=\,\fIchdir\/\fR:\fBerror\fR=\,\fIENOENT\/\fR:\fBwhen\fR=\,\fI3\/\fB+\fR.
The valid range for numbers
.I first
@ -562,21 +565,32 @@ and
.I step
is 1..65535.
A fault expression can contain only one
An injection expression can contain only one
.BR error =
or
.BR retval =
specification.
If a fault expression contains multiple
If an injection expression contains multiple
.BR when =
specifications, the last one takes precedence.
Accounting of syscalls that are subject to fault injection
Accounting of syscalls that are subject to injection
is done per syscall and per tracee.
Specification of syscall fault injection can be combined
Specification of syscall injection can be combined
with other syscall filtering options, for example,
\fB\-P \fI/dev/urandom \fB\-e fault\fR=\,\fIall\/\fR:\fBerror\fR=\,\fIENOENT\fR.
\fB\-P \fI/dev/urandom \fB\-e inject\fR=\,\fIfile\/\fR:\fBerror\fR=\,\fIENOENT\fR.
.TP
\fB\-e\ fault\fR=\,\fIset\/\fR[:\fBerror\fR=\,\fIerrno\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
Perform syscall fault injection for the specified set of syscalls.
This is equivalent to more generic
\fB\-e\ inject\fR= expression with default value of
.I errno
option set to
.IR ENOSYS .
.TP
.BI "\-P " path
Trace only system calls accessing

View File

@ -846,6 +846,7 @@ MISC_TESTS = \
qual_fault-syntax.test \
qual_fault.test \
qual_inject-retval.test \
qual_inject-syntax.test \
qual_signal.test \
qual_syscall.test \
redirect-fds.test \

View File

@ -2,7 +2,7 @@
#
# Check -e fault= syntax.
#
# Copyright (c) 2016 Dmitry V. Levin <ldv@altlinux.org>
# Copyright (c) 2016=2017 Dmitry V. Levin <ldv@altlinux.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -98,6 +98,9 @@ for arg in '' , ,, ,,, : :: ::: \! \!, \!: \
\!chdir,nonsense \
1,nonsense \
\!1,nonsense \
chdir:retval=0 \
chdir:signal=1 \
chdir:error=1:error=2 \
; do
$STRACE -e fault="$arg" true 2> "$LOG" &&
fail_with "$arg"

View File

@ -41,12 +41,7 @@
static const int expfd = 4;
static const int gotfd = 5;
#ifndef __s390__
# define DEFAULT_ERRNO ENOSYS
#else /* __s390__ */
/* Invalid syscall number causes EPERM instead of traditional ENOSYS. */
# define DEFAULT_ERRNO EPERM
#endif
#define DEFAULT_ERRNO ENOSYS
static const char *errstr;
static int is_raw, err, first, step, iter, try;

View File

@ -11,7 +11,7 @@ check_injection()
syscall=chdir
rval="$1"; shift
run_strace -a12 -e$syscall -efault="$syscall:retval=$rval" "$@" \
run_strace -a12 -e$syscall -einject="$syscall:retval=$rval" "$@" \
./qual_inject-retval "$rval" > "$EXP"
match_diff "$LOG" "$EXP"
rm -f "$EXP"

118
tests/qual_inject-syntax.test Executable file
View File

@ -0,0 +1,118 @@
#!/bin/sh
#
# Check -e inject= syntax.
#
# Copyright (c) 2016-2017 Dmitry V. Levin <ldv@altlinux.org>
# 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.
. "${srcdir=.}/init.sh"
#
# F
# F+
# F+S
fail_with()
{
dump_log_and_fail_with \
"strace -e inject=$* failed to handle an argument error properly"
}
for arg in '' , ,, ,,, : :: ::: \! \!, \!: \
invalid_syscall_name \
invalid_syscall_name:when=3 \
-1 \!-1 \
-1:when=4 \
-2 \
-2:when=5 \
32767 \!32767 \
32767:when=6 \
42 \
chdir \
chdir:42 \!chdir:42 \
chdir:42:when=7 \
chdir:invalid \
chdir:invalid:when=8 \
chdir:error= \
chdir:error=:when=10 \
chdir:error=invalid_error_name \
chdir:error=invalid_error_name:when=11 \
chdir:error=-1 \
chdir:error=-1:when=12 \
chdir:error=-2 \
chdir:error=-2:when=13 \
chdir:error=3+ \
chdir:error=3+:when=14 \
chdir:error=4096 \
chdir:error=4096:when=15 \
chdir:when=7:error=invalid_error_name \
chdir:when= \
chdir:when=:error=19 \
chdir:when=0 \
chdir:when=0:error=20 \
chdir:when=-1 \
chdir:when=-1:error=21 \
chdir:when=-2+ \
chdir:when=-2+:error=22 \
chdir:when=-3+0 \
chdir:when=-3+0:error=23 \
chdir:when=4- \
chdir:when=4-:error=24 \
chdir:when=5+- \
chdir:when=5+-:error=25 \
chdir:when=6++ \
chdir:when=6++:error=26 \
chdir:when=7+0 \
chdir:when=7+0:error=27 \
chdir:when=8+-1 \
chdir:when=8+-1:error=28 \
chdir:when=9+1+ \
chdir:when=9+1+:error=29 \
chdir:when=65536 \
chdir:when=65536:error=30 \
chdir:when=1+65536 \
chdir:when=1+65536:error=31 \
file,nonsense \
\!desc,nonsense \
chdir,nonsense \
\!chdir,nonsense \
1,nonsense \
\!1,nonsense \
chdir:retval=-1 \
chdir:signal=0 \
chdir:signal=129 \
chdir:retval=0:retval=1 \
chdir:error=1:error=2 \
chdir:retval=0:error=1 \
chdir:error=1:retval=0 \
chdir:retval=0:signal=1:error=1 \
; do
$STRACE -e inject="$arg" true 2> "$LOG" &&
fail_with "$arg"
LC_ALL=C grep -F 'invalid inject argument' < "$LOG" > /dev/null ||
fail_with "$arg"
done
exit 0