Implement injection of syscalls with no side effects as an alternative to -1

* defs.h (INJECT_F_SYSCALL, INJECT_ACTION_FLAGS, TCB_TAMPERED_NO_FAIL,
syscall_tampered_nofail): New macros.
(inject_data): Add scno field.
* filter_qualify.c (struct inject_personality_data): New type.
(parse_inject_token): Add "pdata" argument, parse "syscall=" option.
(parse_inject_expression): Add "pdata" argument, forward it to
parse_inject_token.
(qualify_inject_common) <pdata>: New variable array, pass it to
parse_inject_expression, copy it into inject_vec.
* syscall.c (tamper_with_syscall_entering): Inject the specified syscall
if INJECT_F_SYSCALL is set.
(tamper_with_syscall_exiting): Update the check for a failed syscall
injection.
(get_syscall_result): Update get_error invocation.
* strace.1.in: Document new syscall injection expression.
* NEWS: Mention it.

Co-Authored-by: Dmitry V. Levin <ldv@altlinux.org>
Closes: https://github.com/strace/strace/issues/26
This commit is contained in:
Eugene Syromyatnikov 2018-02-01 13:51:17 +01:00 committed by Dmitry V. Levin
parent 7e0c7e0e88
commit 883617d8b7
5 changed files with 81 additions and 15 deletions

2
NEWS
View File

@ -19,6 +19,8 @@ Noteworthy changes in release ?.?? (????-??-??)
using --enable-stacktrace option.
* Added -X option for configuring xlat output formatting (addresses
Debian bug #692915).
* Implemented injection of syscalls with no side effects as an alternative
to injection of an invalid syscall (-e inject=SET:syscall= expression).
* Improved support for reproducible builds (addresses Debian bug #896016).
* Implemented decoding of BPF_PROG_QUERY and BPF_RAW_TRACEPOINT_OPEN bpf
syscall commands.

15
defs.h
View File

@ -182,12 +182,22 @@ typedef struct ioctlent {
#define INJECT_F_RETVAL 0x04
#define INJECT_F_DELAY_ENTER 0x08
#define INJECT_F_DELAY_EXIT 0x10
#define INJECT_F_SYSCALL 0x20
#define INJECT_ACTION_FLAGS \
(INJECT_F_SIGNAL \
|INJECT_F_ERROR \
|INJECT_F_RETVAL \
|INJECT_F_DELAY_ENTER \
|INJECT_F_DELAY_EXIT \
)
struct inject_data {
uint8_t flags; /* 5 of 8 flags are used so far */
uint8_t flags; /* 6 of 8 flags are used so far */
uint8_t signo; /* NSIG <= 128 */
uint16_t rval_idx; /* index in retval_vec */
uint16_t delay_idx; /* index in delay_data_vec */
uint16_t scno; /* syscall to be injected instead of -1 */
};
struct inject_opts {
@ -261,6 +271,8 @@ struct tcb {
#define TCB_INJECT_DELAY_EXIT 0x800 /* Current syscall needs to be delayed
on exit */
#define TCB_DELAYED 0x1000 /* Current syscall has been delayed */
#define TCB_TAMPERED_NO_FAIL 0x2000 /* We tamper tcb with syscall
that should not fail. */
/* qualifier flags */
#define QUAL_TRACE 0x001 /* this system call should be traced */
@ -285,6 +297,7 @@ struct tcb {
#define recovering(tcp) ((tcp)->flags & TCB_RECOVERING)
#define inject_delay_exit(tcp) ((tcp)->flags & TCB_INJECT_DELAY_EXIT)
#define syscall_delayed(tcp) ((tcp)->flags & TCB_DELAYED)
#define syscall_tampered_nofail(tcp) ((tcp)->flags & TCB_TAMPERED_NO_FAIL)
#include "xlat.h"

View File

@ -43,6 +43,11 @@ static struct number_set *raw_set;
static struct number_set *trace_set;
static struct number_set *verbose_set;
/* Only syscall numbers are personality-specific so far. */
struct inject_personality_data {
uint16_t scno;
};
static int
sigstr_to_uint(const char *s)
{
@ -102,6 +107,7 @@ parse_delay_token(const char *input, struct inject_opts *fopts, bool isenter)
static bool
parse_inject_token(const char *const token, struct inject_opts *const fopts,
struct inject_personality_data *const pdata,
const bool fault_tokens_only)
{
const char *val;
@ -137,6 +143,27 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts,
/* F == F+0 */
fopts->step = 0;
}
} else if ((val = STR_STRIP_PREFIX(token, "syscall=")) != token) {
if (fopts->data.flags & INJECT_F_SYSCALL)
return false;
for (unsigned int p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
kernel_long_t scno = scno_by_name(val, p, 0);
if (scno < 0)
return false;
/*
* We want to inject only pure system calls with no side
* effects.
*/
if (!(sysent_vec[p][scno].sys_flags & TRACE_PURE))
return false;
pdata[p].scno = scno;
}
fopts->data.flags |= INJECT_F_SYSCALL;
} else if ((val = STR_STRIP_PREFIX(token, "error=")) != token) {
if (fopts->data.flags & (INJECT_F_ERROR | INJECT_F_RETVAL))
return false;
@ -223,6 +250,7 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts,
static const char *
parse_inject_expression(char *const str,
struct inject_opts *const fopts,
struct inject_personality_data *const pdata,
const bool fault_tokens_only)
{
if (str[0] == '\0' || str[0] == ':')
@ -233,7 +261,7 @@ parse_inject_expression(char *const str,
char *token;
while ((token = strtok_r(NULL, ":", &saveptr))) {
if (!parse_inject_token(token, fopts, fault_tokens_only))
if (!parse_inject_token(token, fopts, pdata, fault_tokens_only))
return NULL;
}
@ -308,9 +336,10 @@ qualify_inject_common(const char *const str,
.delay_idx = -1
}
};
struct inject_personality_data pdata[SUPPORTED_PERSONALITIES] = { { 0 } };
char *copy = xstrdup(str);
const char *name =
parse_inject_expression(copy, &opts, fault_tokens_only);
parse_inject_expression(copy, &opts, pdata, fault_tokens_only);
if (!name)
error_msg_and_die("invalid %s '%s'", description, str);
@ -321,7 +350,7 @@ qualify_inject_common(const char *const str,
free(copy);
/* If neither of retval, error, signal or delay is specified, then ... */
if (!opts.data.flags) {
if (!(opts.data.flags & INJECT_ACTION_FLAGS)) {
if (fault_tokens_only) {
/* in fault= syntax the default error code is ENOSYS. */
opts.data.rval_idx = retval_new(ENOSYS);
@ -353,6 +382,10 @@ qualify_inject_common(const char *const str,
if (is_number_in_set_array(i, tmp_set, p)) {
add_number_to_set_array(i, inject_set, p);
inject_vec[p][i] = opts;
/* Copy per-personality data. */
inject_vec[p][i].data.scno =
pdata[p].scno;
}
}
}

View File

@ -626,7 +626,7 @@ 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\ inject\fR=\,\fIset\/\fR[:\fBerror\fR=\,\fIerrno\/\fR|:\fBretval\fR=\,\fIvalue\/\fR][:\fBsignal\fR=\,\fIsig\/\fR][:\fBdelay_enter\fR=\,\fIusecs\/\fR][:\fBdelay_exit\fR=\,\fIusecs\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
\fB\-e\ inject\fR=\,\fIset\/\fR[:\fBerror\fR=\,\fIerrno\/\fR|:\fBretval\fR=\,\fIvalue\/\fR][:\fBsignal\fR=\,\fIsig\/\fR][:\fBsyscall\fR=\fIsyscall\fR][:\fBdelay_enter\fR=\,\fIusecs\/\fR][:\fBdelay_exit\fR=\,\fIusecs\/\fR][:\fBwhen\fR=\,\fIexpr\/\fR]
Perform syscall tampering for the specified set of syscalls.
At least one of
@ -644,7 +644,8 @@ 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,
the syscall number is replaced by -1 which corresponds to an invalid syscall
(unless a syscall is specified with :\fBsyscall=\fR option),
and the error code is specified using a symbolic
.I errno
value like
@ -685,6 +686,12 @@ 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.
if :\fBsyscall\fR=\fIsyscall\fR option is specified, the corresponding syscall
with no side effects is injected instead of -1.
Currently, only "pure" (see
.BR "-e trace" = "%pure"
description) syscalls can be specified there.
Unless a :\fBwhen\fR=\,\fIexpr\fR subexpression is specified,
an injection is being made into every invocation of each syscall from the
.IR set .

View File

@ -507,9 +507,18 @@ tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
if (!recovering(tcp)) {
if (opts->data.flags & INJECT_F_SIGNAL)
*signo = opts->data.signo;
if (opts->data.flags & (INJECT_F_ERROR | INJECT_F_RETVAL) &&
!arch_set_scno(tcp, -1))
tcp->flags |= TCB_TAMPERED;
if (opts->data.flags & (INJECT_F_ERROR | INJECT_F_RETVAL)) {
kernel_long_t scno =
(opts->data.flags & INJECT_F_SYSCALL)
? (kernel_long_t) shuffle_scno(opts->data.scno)
: -1;
if (!arch_set_scno(tcp, scno)) {
tcp->flags |= TCB_TAMPERED;
if (scno != -1)
tcp->flags |= TCB_TAMPERED_NO_FAIL;
}
}
if (opts->data.flags & INJECT_F_DELAY_ENTER)
delay_tcb(tcp, opts->data.delay_idx, true);
if (opts->data.flags & INJECT_F_DELAY_EXIT)
@ -532,10 +541,11 @@ tamper_with_syscall_exiting(struct tcb *tcp)
if (!syscall_tampered(tcp))
return 0;
if (!syserror(tcp)) {
error_msg("Failed to tamper with process %d: got no error "
"(return value %#" PRI_klx ")",
tcp->pid, tcp->u_rval);
if (!syserror(tcp) ^ !!syscall_tampered_nofail(tcp)) {
error_msg("Failed to tamper with process %d: unexpectedly got"
" %serror (return value %#" PRI_klx ", error %lu)",
tcp->pid, syscall_tampered_nofail(tcp) ? "" : "no ",
tcp->u_rval, tcp->u_error);
return 1;
}
@ -1237,8 +1247,9 @@ get_syscall_result(struct tcb *tcp)
return -1;
tcp->u_error = 0;
get_error(tcp,
!(tcp->s_ent->sys_flags & SYSCALL_NEVER_FAILS)
|| syscall_tampered(tcp));
(!(tcp->s_ent->sys_flags & SYSCALL_NEVER_FAILS)
|| syscall_tampered(tcp))
&& !syscall_tampered_nofail(tcp));
return 1;
}