Compare commits

...

15 Commits

Author SHA1 Message Date
Nikolay Marchuk
c2f6e94c5d Update NEWS 2018-06-13 15:05:09 +00:00
Nikolay Marchuk
fd1273c7da Add description of new filtering syntax
* strace.1.in (.SS Filtering): Add description of new filtering syntax.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
5329c0c0d7 tests: check path filter
* tests/filtering_path.c: New file.
* tests/filtering_path.test: Likewise.
* tests/.gitignore: Add filtering_path.
* tests/Makefile.am (check_PROGRAMS): Add filtering_path.
(MISC_TESTS): Add filtering_path.test.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
0fdb1f18e9 tests: check fd filter
* tests/filtering_fd-syntax.test: New file.
* tests/filtering_fd.c: Likewise.
* tests/filtering_fd.test: Likewise.
* tests/mmap_name.sh: Likewise.
* tests/mmap.test: Move mmap name testing to mmap_name.sh.
* tests/options-syntax.test: Remove fd filtering checks.
* tests/.gitignore: Add filtering_fd.
* tests/Makefile.am (check_PROGRAMS): Add filtering_fd.
(MISC_TESTS): Add filering_fd-syntax.test, filtering_fd.test.
(EXTRA_DIST): Add mmap_name.sh.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
98e1b86c8c tests: check new syntax of inject/fault actions
* tests/qual_fault-exit_group.test: Add checks with new syntax.
* tests/qual_fault.test: Likewise.
* tests/qual_inject-error-signal.test: Likewise.
* tests/qual_inject-retval.test: Likewise.
* tests/qual_inject-signal.test: Likewise.
* tests/tampering-syntax.sh: New file.
* tests/qual_fault-syntax.test: Move common syntax checks to tampering-syntax.sh.
Add checks with new syntax.
* tests/qual_inject-syntax.test: Likewise.
* tests/Makefile.am (EXTRA_DIST): Add tampering-syntax.sh.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
fe9e13c311 tests: check expression parsing
* tests/filtering_action-syntax.test: New file.
* tests/filtering_expression-empty.test: Likewise.
* tests/filtering_expression-syntax.test: Likewise.
* tests/Makefile.am (MISC_TESTS): Add new files.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
39ece647b0 tests: check error handling of syscall filter
* tests/filtering_syscall-syntax.test: Add checks of syscall filter.
* tests/qual_fault-syntax.test: Move checks with ':' to filtering_syscall-syntax.test
* tests/qual_inject-syntax.test: Likewise.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
60f0cd1d92 Add stacktrace filter action
* defs.h (QUAL_STACKTRACE): Add new qual flag.
(stacktrace): Add macro for checking QUAL_STACKTRACE.
(stack_trace_enabled): Change description.
* filter.h (DECL_FILTER_ACTION): Declare stacktrace filter action type.
* filter_action.c (action_types): Add stacktrace filter action type.
(add_action): Update stack_trace_enabled.
* strace.c (stack_trace_enabled): Change description.
(init): Use filtering_parse for -k option.
* syscall.c (syscall_entering_trace, syscall_exiting_decode,
syscall_exiting_trace): Use stacktrace macro instead of
stack_trace_enabled.
* unwind.c (apply_stacktrace): Add filter action function.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
9aeb73fb6b Implement new filtering language parsing
* basic_filters.c (lookup_class, qualify_syscall_class, qualify_syscall,
qualify_syscall_tokens, parse_syscall_filter, qualify_tokens,
parse_fd_filter, parse_path_filter): Add qualify_mode argument.
(qualify_tokens, qualify_syscall_tokens): Use set inversion only in
qualify mode.
(lookup_class): Use deprecated class names only in qualify mode.
* defs.h (qualify): Remove declaration.
(filtering_parse): Add new declaration.
* filter.c (struct filter_type, parse_filter):
Add bool argument to parse_*_filter declarations.
* filter.h (parse_filter_action, parse_qualify_action,
parse_filter_expression): Add new declarations.
(parse_filter, qualify_tokens, qualify_syscall_tokens):
Add qualify_mode argument.
(DECL_FILTER): Add bool argument to parse_*_filter declarations.
* filter_action.c (parse_filter_action): Add new parsing function.
(inject_path_tracing): Use filtering_parse instead of qualify.
* filter_expression.c (parse_filter_expression): Implement parsing of filter
expression.
(parse_operator, push_operator, is_higher_priority): Add helper functions.
(is_space_ascii, is_allowed_in_name): Add new declarations.
* filter_parse.c: New file.
* filter_qualify.c (qualify_read, qualify_write, qualify_signals,
qualify_trace, qualify_abbrev, qualify_verbose, qualify_raw,
qualify_inject_common, qualify_fault, qualify_inject): Use main_part and args
arguments.
* strace.c (init): Use filtering_parse instead of qualify.
* Makefile.am (strace_SOURCES): Add filter_parse.c.

[ldv: simplify *_qualify_mode]
[ldv: eliminate parse_null]
[ldv: optimize lookup_class]
[ldv: use loop initial declarations]
[ldv: ATTRIBUTE_FALLTHROUGH]
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
747169ad6d Optimize default filtering
* filter_action.c (default_flags): Add default flags variable.
(add_action): Update default flags.
(filter_syscall): Add default_flags to qual_flg.
(filtering_parse_finish): Init trace action for pathtracing.
* strace.c (init): Remove default filters.
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
a732d730b7 Improve fd filtering
* pathtrace.c (fdmatch_fd_func): New function.
(match_fd_common): Likewise.
(pathtrace_match_set): Move fd matching to match_fd_common.
* filter.h (match_fd_common): Add new declaration.
* basic_filters.c (is_fd_in_set): New function.
(run_fd_filter): Use match_fd_common for fd filter.

[ldv: cleanup run_fd_filter]
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
c411e208bd Implement path filter
* basic_filters.c (parse_path_filter, run_path_filter, free_path_filter):
New functions.
* filter.c (filter_types): Add path filter type.
(set_filter_priv_data): New function.
* filter.h (set_filter_priv_data, expression_add_filter_and):
Add declarations.
(DECL_FILTER): Add path filter declaration.
* filter_action.c (inject_path_tracing): New function.
(filtering_parse_finish): Use it.
* filter_expression.c (add_variable_token, add_operator_token,
expression_add_filter_and): New functions.
* pathtrace.c (storepath): Duplicate path.

[ldv: use loop initial declarations]
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
c2a9854870 Implement fd filter and read/write actions
* basic_actions.c (apply_read, apply_write): New functions.
* basic_filters.c (parse_fd_filter, run_fd_filter, free_fd_filter): Likewise.
* defs.h (QUAL_READ, QUAL_WRITE): Add new flags.
(dump_read, dump_write): Add macros for these flags.
* filter.c (filter_types): Add fd filter type.
* filter.h (DECL_FILTER): Add fd filter declaration.
(DECL_FILTER_ACTION): Add read and write filter action declarations.
* filter_action.c (action_types): Add read and write filter action types.
* filter_qualify.c (read_set, write_set): Remove set variables.
(qualify_read, qualify_write): Use new filtering API.
* number_set.h (read_set, write_set): Remove set variable declarations.
* syscall.c (dumpio): Check dump_read, dump_write macros
instead of global sets.

[ldv: simplify *_qualify_mode]
[ldv: cleanup run_fd_filter]
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
f1bba432e1 Implement inject and fault actions
* basic_actions.c (not_injected, apply_inject, parse_inject_common,
parse_inject, apply_fault, parse_fault): New functions.
* defs.h (struct inject_opts): Add init flag.
(qual_flags): Remove declaration.
* filter_action.c (action_types): Add inject and fault action types.
(set_filter_action_priv_data): New function.
* filter_qualify.c (inject_set): Remove variable.
(parse_inject_expression): Remove function.
(parse_inject_common_args): Add function for inject/fault arguments parsing.
(qualify_inject_common): Use parse_inject_common_args instead of
parse_inject_expression, use new filtering API.
(qualify_fault, qualify_inject): Remove "argument" from description
argument of qualify_inject_common.
(qual_flags): Remove function.
* filter.h (parse_inject_common_args, not_injected,
set_filter_action_priv_data): New declarations.
(DECL_FILTER_ACTION): Declare inject and fault actions.
(DECL_FILTER_ACTION_PARSER): Declare inject and fault action parsers.
* strace.c (trace_syscall): Call filter_syscall only when tcp->qual_flg
is empty.
* syscall.c (decode_socket_subcall): Remove qual_flags from decoder.
(decode_ipc_subcall): Likewise.
(decode_mips_subcall): Likewise.
(get_scno): Likewise.
(inject_vec, tamper_with_syscall_entering): Remove inject_vec support code.

[ldv: fix segfault in parse_inject_common_args]
[ldv: simplify *_qualify_mode]
2018-06-13 15:05:09 +00:00
Nikolay Marchuk
f939d6d2b1 Introduce new filtering architecture
This change introduces new filtering architecture primitives: filter,
filter_action and bool_expression. Filtering is now done after decoding of
syscall and tcp->qual_flg stores filtering results.

* basic_actions.c: New file.
* filter_action.c: Likewise.
* filter_expression.c: Likewise.
* filter.c: Likewise.
* basic_filters.c (parse_syscall_filter, run_syscall_filter,
free_syscall_filter): New functions.
* defs.h (filter_syscall, filtering_parsing_finish): Add new declarations.
* filter.h: Add new declarations.
* filter_qualify.c (abbrev_set, raw_set, trace_set, verbose_set):
Remove set variables.
(qualify_trace, qualify_abbrev, qualify_verbose, qualify_raw):
Use new filtering API.
(qual_flags): Remove QUAL_* flags for trace, abbrev, verbose, raw.
* strace.c (init): Call filtering_parse_finish after command line parsing.
(trace_syscall): Add filtering after syscall decoding.
* Makefile.am (strace_SOURCES): Add new files.

[ldv: simplify *_qualify_mode]
[ldv: eliminate parse_null]
[ldv: introduce qualify_filter]
[ldv: use loop initial declarations]
2018-06-13 15:05:09 +00:00
38 changed files with 3007 additions and 456 deletions

View File

@ -93,6 +93,7 @@ strace_SOURCES = \
aio.c \
alpha.c \
arch_defs.h \
basic_actions.c \
basic_filters.c \
bind.c \
bjm.c \
@ -146,7 +147,11 @@ strace_SOURCES = \
fetch_struct_statfs.c \
file_handle.c \
file_ioctl.c \
filter_action.c \
filter_expression.c \
filter_parse.c \
filter_qualify.c \
filter.c \
filter.h \
flock.c \
flock.h \

4
NEWS
View File

@ -1,6 +1,10 @@
Noteworthy changes in release ?.?? (????-??-??)
===============================================
* Changes in command line syntax
* New syntax for -e option for filter-based expressions.
Traditional syntax is also supported.
* Changes in behaviour
* On x32 personality, 64-bit syscalls (such as readv) are now shown with "#64"
suffix instead of "64:" prefix. Unlike the old names, these new names

124
basic_actions.c Normal file
View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "defs.h"
#include "filter.h"
bool
is_traced(struct tcb *tcp)
{
return traced(tcp);
}
bool
not_injected(struct tcb *tcp)
{
return !inject(tcp);
}
void
apply_trace(struct tcb *tcp, void *priv_data)
{
tcp->qual_flg |= QUAL_TRACE;
}
void
apply_raw(struct tcb *tcp, void *priv_data)
{
tcp->qual_flg |= QUAL_RAW;
}
void
apply_abbrev(struct tcb *tcp, void *priv_data)
{
tcp->qual_flg |= QUAL_ABBREV;
}
void
apply_verbose(struct tcb *tcp, void *priv_data)
{
tcp->qual_flg |= QUAL_VERBOSE;
}
void
apply_inject(struct tcb *tcp, void *priv_data)
{
struct inject_opts *opts = priv_data;
tcp->qual_flg |= QUAL_INJECT;
if (!tcp->inject_vec[current_personality])
tcp->inject_vec[current_personality] =
xcalloc(nsyscalls, sizeof(struct inject_opts));
if (scno_in_range(tcp->scno)
&& !tcp->inject_vec[current_personality][tcp->scno].data.flags)
tcp->inject_vec[current_personality][tcp->scno] = *opts;
}
static void *
parse_inject_common(const char *str, bool fault_tokens_only,
const char *description)
{
struct inject_opts *opts = xmalloc(sizeof(struct inject_opts));
char *copy = xstrdup(str);
parse_inject_common_args(copy, opts, fault_tokens_only, false);
if (!opts->data.flags)
error_msg_and_die("invalid %s argument '%s'",
description, str ? str : "");
free(copy);
return opts;
}
void *
parse_inject(const char *str)
{
return parse_inject_common(str, false, "inject");
}
void
apply_fault(struct tcb *tcp, void *priv_data)
{
apply_inject(tcp, priv_data);
}
void *
parse_fault(const char *str)
{
return parse_inject_common(str, true, "fault");
}
void
apply_read(struct tcb *tcp, void *_priv_data)
{
tcp->qual_flg |= QUAL_READ;
}
void
apply_write(struct tcb *tcp, void *_priv_data)
{
tcp->qual_flg |= QUAL_WRITE;
}

View File

@ -91,7 +91,7 @@ qualify_syscall_regex(const char *s, struct number_set *set)
}
static unsigned int
lookup_class(const char *s)
lookup_class(const char *s, bool qualify_mode)
{
static const struct {
const char *name;
@ -123,6 +123,8 @@ lookup_class(const char *s)
};
for (unsigned int i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
if (!qualify_mode && *s != '%')
break;
if (strcmp(s, syscall_class[i].name) == 0)
return syscall_class[i].value;
}
@ -131,9 +133,9 @@ lookup_class(const char *s)
}
static bool
qualify_syscall_class(const char *s, struct number_set *set)
qualify_syscall_class(const char *s, struct number_set *set, bool qualify_mode)
{
const unsigned int n = lookup_class(s);
const unsigned int n = lookup_class(s, qualify_mode);
if (!n)
return false;
@ -181,7 +183,7 @@ qualify_syscall_name(const char *s, struct number_set *set)
}
static bool
qualify_syscall(const char *token, struct number_set *set)
qualify_syscall(const char *token, struct number_set *set, bool qualify_mode)
{
bool ignore_fail = false;
@ -193,7 +195,7 @@ qualify_syscall(const char *token, struct number_set *set)
return qualify_syscall_number(token, set) || ignore_fail;
if (*token == '/')
return qualify_syscall_regex(token + 1, set) || ignore_fail;
return qualify_syscall_class(token, set)
return qualify_syscall_class(token, set, qualify_mode)
|| qualify_syscall_name(token, set)
|| ignore_fail;
}
@ -203,7 +205,8 @@ qualify_syscall(const char *token, struct number_set *set)
* according to STR specification.
*/
void
qualify_syscall_tokens(const char *const str, struct number_set *const set)
qualify_syscall_tokens(const char *const str, struct number_set *const set,
bool qualify_mode)
{
/* Clear all sets. */
clear_number_set_array(set, SUPPORTED_PERSONALITIES);
@ -213,9 +216,15 @@ qualify_syscall_tokens(const char *const str, struct number_set *const set)
* of the remaining specification.
*/
const char *s = str;
while (*s == '!') {
invert_number_set_array(set, SUPPORTED_PERSONALITIES);
++s;
if (qualify_mode) {
/*
* Each leading ! character means inversion
* of the remaining specification.
*/
while (*s == '!') {
invert_number_set_array(set, SUPPORTED_PERSONALITIES);
++s;
}
}
if (strcmp(s, "none") == 0) {
@ -244,7 +253,7 @@ qualify_syscall_tokens(const char *const str, struct number_set *const set)
for (const char *token = strtok_r(copy, ",", &saveptr);
token; token = strtok_r(NULL, ",", &saveptr)) {
done = qualify_syscall(token, set);
done = qualify_syscall(token, set, qualify_mode);
if (!done)
error_msg_and_die("invalid system call '%s'", token);
}
@ -255,12 +264,33 @@ qualify_syscall_tokens(const char *const str, struct number_set *const set)
error_msg_and_die("invalid system call '%s'", str);
}
void *
parse_syscall_filter(const char *str, bool qualify_mode)
{
struct number_set *set = alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(str, set, qualify_mode);
return set;
}
bool
run_syscall_filter(struct tcb *tcp, void *priv_data)
{
return is_number_in_set_array(tcp->scno, priv_data, current_personality);
}
void
free_syscall_filter(void *priv_data)
{
free_number_set_array(priv_data, SUPPORTED_PERSONALITIES);
}
/*
* Add numbers to SET according to STR specification.
*/
void
qualify_tokens(const char *const str, struct number_set *const set,
string_to_uint_func func, const char *const name)
string_to_uint_func func, const char *const name,
bool qualify_mode)
{
/* Clear the set. */
clear_number_set_array(set, 1);
@ -270,9 +300,15 @@ qualify_tokens(const char *const str, struct number_set *const set,
* of the remaining specification.
*/
const char *s = str;
while (*s == '!') {
invert_number_set_array(set, 1);
++s;
if (qualify_mode) {
/*
* Each leading ! character means inversion
* of the remaining specification.
*/
while (*s == '!') {
invert_number_set_array(set, 1);
++s;
}
}
if (strcmp(s, "none") == 0) {
@ -313,3 +349,55 @@ qualify_tokens(const char *const str, struct number_set *const set,
if (number < 0)
error_msg_and_die("invalid %s '%s'", name, str);
}
void *
parse_fd_filter(const char *str, bool qualify_mode)
{
struct number_set *set = alloc_number_set_array(1);
qualify_tokens(str, set, string_to_uint, "descriptor", qualify_mode);
return set;
}
static bool
is_fd_in_set(struct tcb *tcp, int fd, void *data) {
return fd < 0 ? false : is_number_in_set(fd, data);
}
bool
run_fd_filter(struct tcb *tcp, void *priv_data)
{
return match_fd_common(tcp, &is_fd_in_set, priv_data);
}
void
free_fd_filter(void *priv_data)
{
free_number_set_array(priv_data, 1);
}
void *
parse_path_filter(const char *path, bool qualify_mode)
{
struct path_set *set = xcalloc(1, sizeof(struct path_set));
pathtrace_select_set(path, set);
return set;
}
bool
run_path_filter(struct tcb *tcp, void *priv_data)
{
return pathtrace_match_set(tcp, priv_data);
}
void
free_path_filter(void *priv_data)
{
struct path_set *set = priv_data;
for (unsigned int i = 0; i < set->num_selected; ++i)
free((char *) set->paths_selected[i]);
free(set->paths_selected);
free(set);
return;
}

17
defs.h
View File

@ -268,6 +268,11 @@ struct tcb {
#define QUAL_VERBOSE 0x004 /* decode the structures of this syscall */
#define QUAL_RAW 0x008 /* print all args in hex for this syscall */
#define QUAL_INJECT 0x010 /* tamper with this system call on purpose */
#define QUAL_READ 0x020 /* dump data read in this syscall */
#define QUAL_WRITE 0x040 /* dump data written in this syscall */
#ifdef ENABLE_STACKTRACE
# define QUAL_STACKTRACE 0x080 /* do the stack trace */
#endif
#define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
@ -277,8 +282,13 @@ struct tcb {
#define traced(tcp) ((tcp)->qual_flg & QUAL_TRACE)
#define verbose(tcp) ((tcp)->qual_flg & QUAL_VERBOSE)
#define abbrev(tcp) ((tcp)->qual_flg & QUAL_ABBREV)
#define dump_read(tcp) ((tcp)->qual_flg & QUAL_READ)
#define dump_write(tcp) ((tcp)->qual_flg & QUAL_WRITE)
#define raw(tcp) ((tcp)->qual_flg & QUAL_RAW)
#define inject(tcp) ((tcp)->qual_flg & QUAL_INJECT)
#ifdef ENABLE_STACKTRACE
# define stacktrace(tcp) ((tcp)->qual_flg & QUAL_STACKTRACE)
#endif
#define filtered(tcp) ((tcp)->flags & TCB_FILTERED)
#define hide_log(tcp) ((tcp)->flags & TCB_HIDE_LOG)
#define syscall_tampered(tcp) ((tcp)->flags & TCB_TAMPERED)
@ -395,7 +405,7 @@ extern struct path_set {
extern unsigned xflag;
extern unsigned followfork;
#ifdef ENABLE_STACKTRACE
/* if this is true do the stack trace for every system call */
/* if this is true do the initialization of stack tracing mechanism */
extern bool stack_trace_enabled;
#endif
extern unsigned ptrace_setoptions;
@ -913,8 +923,9 @@ extern void print_ifindex(unsigned int);
extern void print_bpf_filter_code(const uint16_t code, bool extended);
extern void qualify(const char *);
extern unsigned int qual_flags(const unsigned int);
extern void filtering_parse(const char *);
extern void filtering_parsing_finish(void);
extern void filter_syscall(struct tcb *);
#define DECL_IOCTL(name) \
extern int \

122
filter.c Normal file
View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "defs.h"
#include "filter.h"
#define FILTER_TYPE(name) \
{#name, parse_ ## name ## _filter, run_ ## name ## _filter, \
free_ ## name ## _filter}
/* End of FILTER_TYPE definition. */
static const struct filter_type {
const char *name;
void *(*parse_filter)(const char *, bool);
bool (*run_filter)(struct tcb *, void *);
void (*free_priv_data)(void *);
} filter_types[] = {
FILTER_TYPE(syscall),
FILTER_TYPE(fd),
FILTER_TYPE(path),
};
#undef FILTER_TYPE
struct filter {
const struct filter_type *type;
void *priv_data;
};
static const struct filter_type *
lookup_filter_type(const char *str)
{
for (unsigned int i = 0; i < ARRAY_SIZE(filter_types); i++) {
if (!strcmp(filter_types[i].name, str))
return &filter_types[i];
}
return NULL;
}
struct filter *
add_filter_to_array(struct filter **filters, unsigned int *nfilters,
const char *name)
{
const struct filter_type *type = lookup_filter_type(name);
struct filter *filter;
if (!type)
error_msg_and_die("invalid filter '%s'", name);
*filters = xreallocarray(*filters, ++(*nfilters),
sizeof(struct filter));
filter = &((*filters)[*nfilters - 1]);
filter->type = type;
return filter;
}
void
parse_filter(struct filter *filter, const char *str, bool qualify_mode)
{
filter->priv_data = filter->type->parse_filter(str, qualify_mode);
}
static bool
run_filter(struct tcb *tcp, struct filter *filter)
{
return filter->type->run_filter(tcp, filter->priv_data);
}
void
run_filters(struct tcb *tcp, struct filter *filters, unsigned int nfilters,
bool *variables_buf)
{
for (unsigned int i = 0; i < nfilters; ++i)
variables_buf[i] = run_filter(tcp, &filters[i]);
}
void
free_filter(struct filter *filter)
{
if (!filter)
return;
filter->type->free_priv_data(filter->priv_data);
}
void
set_filters_qualify_mode(struct filter **filters, unsigned int *nfilters)
{
for (unsigned int i = 0; i < *nfilters - 1; ++i)
free_filter(*filters + i);
(*filters)[0] = (*filters)[*nfilters - 1];
*filters = xreallocarray(*filters, 1, sizeof(struct filter));
*nfilters = 1;
}
void
set_filter_priv_data(struct filter *filter, void *priv_data)
{
if (filter)
filter->priv_data = priv_data;
}

View File

@ -30,10 +30,92 @@
#define STRACE_FILTER_H
struct number_set;
struct filter;
struct filter_action;
struct bool_expression;
typedef int (*string_to_uint_func)(const char *);
void qualify_tokens(const char *str, struct number_set *set,
string_to_uint_func func, const char *name);
void qualify_syscall_tokens(const char *str, struct number_set *set);
string_to_uint_func func, const char *name,
bool qualify_mode);
void qualify_syscall_tokens(const char *str, struct number_set *set,
bool qualify_mode);
void parse_inject_common_args(char *, struct inject_opts *,
const bool fault_tokens_only, bool qualify_mode);
bool is_traced(struct tcb *);
bool not_injected(struct tcb *);
typedef bool (*match_fd_func)(struct tcb *, int, void *);
int match_fd_common(struct tcb *, match_fd_func, void *);
/* filter api */
struct filter* add_filter_to_array(struct filter **, unsigned int *nfilters,
const char *name);
void parse_filter(struct filter *, const char *str, bool qualify_mode);
void run_filters(struct tcb *, struct filter *, unsigned int, bool *);
void free_filter(struct filter *);
void set_filters_qualify_mode(struct filter **, unsigned int *nfilters);
void set_filter_priv_data(struct filter *, void *);
/* filter action api */
struct filter *create_filter(struct filter_action *, const char *name);
struct filter_action *find_or_add_action(const char *);
void set_qualify_mode(struct filter_action *);
void parse_filter_action(const char *, const char *, const char *);
void set_filter_action_priv_data(struct filter_action *, void *);
/* filter expression api */
struct bool_expression *create_expression();
bool run_expression(struct bool_expression *, bool *, unsigned int);
void set_expression_qualify_mode(struct bool_expression *);
void expression_add_filter_and(struct bool_expression *, unsigned int);
void parse_filter_expression(struct bool_expression *, const char *,
struct filter_action *, unsigned int);
void parse_qualify_action(const char *, const char *, const char *);
#define DECL_FILTER(name) \
extern void * \
parse_ ## name ## _filter(const char *, bool); \
extern bool \
run_ ## name ## _filter(struct tcb *, void *); \
extern void \
free_ ## name ## _filter(void *) \
/* End of DECL_FILTER definition. */
DECL_FILTER(syscall);
DECL_FILTER(fd);
DECL_FILTER(path);
#undef DECL_FILTER
#define DECL_FILTER_ACTION(name) \
extern void \
apply_ ## name(struct tcb *, void *) \
/* End of DECL_FILTER_ACTION definition. */
DECL_FILTER_ACTION(trace);
DECL_FILTER_ACTION(raw);
DECL_FILTER_ACTION(abbrev);
DECL_FILTER_ACTION(verbose);
DECL_FILTER_ACTION(inject);
DECL_FILTER_ACTION(fault);
DECL_FILTER_ACTION(read);
DECL_FILTER_ACTION(write);
DECL_FILTER_ACTION(stacktrace);
#undef DECL_FILTER_ACTION
#define DECL_FILTER_ACTION_PARSER(name) \
extern void * \
parse_ ## name(const char *); \
/* End of DECL_FILTER_ACTION_PARSER definition. */
#define parse_NULL NULL
DECL_FILTER_ACTION_PARSER(inject);
DECL_FILTER_ACTION_PARSER(fault);
#undef DECL_FILTER_ACTION_PARSER
#endif /* !STRACE_FILTER_H */

234
filter_action.c Normal file
View File

@ -0,0 +1,234 @@
/*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "defs.h"
#include "filter.h"
#define FILTER_ACTION_TYPE(NAME, PRIORITY, FLAG, PARSER, PREFILTER) \
{#NAME, PRIORITY, FLAG, parse_ ## PARSER, PREFILTER, apply_ ## NAME}
/* End of FILTER_ACTION_TYPE definition. */
static const struct filter_action_type {
const char *name;
/* The highest priority is 0. */
unsigned int priority;
unsigned int qual_flg;
void * (*parse_args)(const char *);
bool (*prefilter)(struct tcb *);
void (*apply)(struct tcb *, void *);
} action_types[] = {
FILTER_ACTION_TYPE(trace, 0, QUAL_TRACE, NULL, NULL),
FILTER_ACTION_TYPE(inject, 1, QUAL_INJECT, inject, not_injected),
FILTER_ACTION_TYPE(fault, 1, QUAL_INJECT, fault, not_injected),
FILTER_ACTION_TYPE(raw, 2, QUAL_RAW, NULL, is_traced),
FILTER_ACTION_TYPE(abbrev, 2, QUAL_ABBREV, NULL, is_traced),
FILTER_ACTION_TYPE(verbose, 2, QUAL_VERBOSE, NULL, is_traced),
FILTER_ACTION_TYPE(read, 2, QUAL_READ, NULL, is_traced),
FILTER_ACTION_TYPE(write, 2, QUAL_WRITE, NULL, is_traced),
# ifdef ENABLE_STACKTRACE
FILTER_ACTION_TYPE(stacktrace, 2, QUAL_STACKTRACE, NULL, is_traced),
# endif
};
#undef FILTER_ACTION_TYPE
struct filter_action {
/* Used to correct order of actions with the same priority. */
unsigned int id;
const struct filter_action_type *type;
struct bool_expression *expr;
unsigned int nfilters;
struct filter *filters;
void *priv_data;
};
static unsigned int default_flags = DEFAULT_QUAL_FLAGS;
static struct filter_action *filter_actions;
static unsigned int nfilter_actions;
static bool *variables_buf;
/*
* Compares action priorities. If actions have the same priority,
* uses LIFO order.
*/
static int
compare_action_priority(const void *a, const void *b)
{
const struct filter_action *action_a = a;
const struct filter_action *action_b = b;
unsigned int priority_a = action_a->type->priority;
unsigned int priority_b = action_b->type->priority;
if (priority_a != priority_b) {
return (priority_a < priority_b) ? -1 : 1;
} else {
return (action_a->id > action_b->id) ? -1 : 1;
}
}
static void
inject_path_tracing(void)
{
struct filter_action *action = find_or_add_action("trace");
struct filter *path_filter;
if (!action->nfilters)
filtering_parse("trace=all");
path_filter = add_filter_to_array(&action->filters, &action->nfilters,
"path");
set_filter_priv_data(path_filter, &global_path_set);
expression_add_filter_and(action->expr, action->nfilters - 1);
}
void
filtering_parsing_finish(void)
{
unsigned int maxfilters = 0;
/* Inject path filter into trace action. */
if (tracing_paths)
inject_path_tracing();
/* Sort actions by priority */
if (nfilter_actions == 0)
return;
qsort(filter_actions, nfilter_actions, sizeof(struct filter_action),
&compare_action_priority);
/* Allocate variables_buf sufficient for any action */
for (unsigned int i = 0; i < nfilter_actions; ++i) {
if (filter_actions[i].nfilters > maxfilters)
maxfilters = filter_actions[i].nfilters;
}
variables_buf = xcalloc(maxfilters, sizeof(bool));
}
static const struct filter_action_type *
lookup_filter_action_type(const char *str)
{
for (unsigned int i = 0; i < ARRAY_SIZE(action_types); ++i) {
if (!strcmp(action_types[i].name, str))
return &action_types[i];
}
return NULL;
}
static struct filter_action *
add_action(const struct filter_action_type *type)
{
struct filter_action *action;
/* Update default_flags */
if (default_flags & type->qual_flg)
default_flags &= ~type->qual_flg;
/* Enable stack tracing. */
#ifdef ENABLE_STACKTRACE
if (type->qual_flg & QUAL_STACKTRACE)
stack_trace_enabled = true;
#endif
filter_actions = xreallocarray(filter_actions, ++nfilter_actions,
sizeof(struct filter_action));
action = &filter_actions[nfilter_actions - 1];
memset(action, 0, sizeof(*action));
action->id = nfilter_actions - 1;
action->type = type;
action->expr = create_expression();
return action;
}
struct filter_action *
find_or_add_action(const char *name)
{
const struct filter_action_type *type = lookup_filter_action_type(name);
if (!type)
error_msg_and_die("invalid filter action '%s'", name);
/* If action takes arguments, add new action */
if (type->parse_args)
return add_action(type);
for (unsigned int i = 0; i < nfilter_actions; ++i) {
if (filter_actions[i].type == type)
return &filter_actions[i];
}
return add_action(type);
}
void
parse_filter_action(const char *action_name, const char *expr, const char *args)
{
struct filter_action *action = find_or_add_action(action_name);
parse_filter_expression(action->expr, expr, action, action->nfilters);
if (args && !action->type->parse_args)
error_msg("%s action takes no arguments, ignored arguments "
"'%s'", action->type->name, args);
action->priv_data = action->type->parse_args
? action->type->parse_args(args) : NULL;
}
static void
run_filter_action(struct tcb *tcp, struct filter_action *action)
{
if (action->type->prefilter && !action->type->prefilter(tcp))
return;
run_filters(tcp, action->filters, action->nfilters, variables_buf);
if (run_expression(action->expr, variables_buf, action->nfilters))
action->type->apply(tcp, action->priv_data);
}
struct filter *
create_filter(struct filter_action *action, const char *name)
{
return add_filter_to_array(&action->filters, &action->nfilters, name);
}
void
set_qualify_mode(struct filter_action *action)
{
set_filters_qualify_mode(&action->filters, &action->nfilters);
set_expression_qualify_mode(action->expr);
}
void
filter_syscall(struct tcb *tcp)
{
tcp->qual_flg |= default_flags;
for (unsigned int i = 0; i < nfilter_actions; ++i)
run_filter_action(tcp, &filter_actions[i]);
}
void
set_filter_action_priv_data(struct filter_action *action, void *priv_data)
{
if (action)
action->priv_data = priv_data;
}

458
filter_expression.c Normal file
View File

@ -0,0 +1,458 @@
/*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "defs.h"
#include <stdarg.h>
#include "filter.h"
extern bool is_space_ascii(char);
extern bool is_allowed_in_name(char);
struct expression_token {
enum token_type {
TOK_VARIABLE,
TOK_OPERATOR
} type;
union token_data {
unsigned int variable_id;
enum operator_type {
OP_NOT,
OP_AND,
OP_OR
} operator_id;
} data;
};
/* Pseudo-operator used for parsing */
#define OP_PARENTHESIS 3
struct bool_expression {
unsigned int ntokens;
struct expression_token *tokens;
};
struct bool_expression *
create_expression(void)
{
return xcalloc(1, sizeof(struct bool_expression));
}
static void
reallocate_expression(struct bool_expression *const expr,
const unsigned int new_ntokens)
{
if (!expr)
error_msg_and_die("invalid expression");
expr->tokens = xreallocarray(expr->tokens, new_ntokens,
sizeof(*expr->tokens));
if (new_ntokens > expr->ntokens)
memset(expr->tokens + expr->ntokens, 0,
sizeof(*expr->tokens) * (new_ntokens - expr->ntokens));
expr->ntokens = new_ntokens;
}
static void
add_variable_token(struct bool_expression *expr, unsigned int id)
{
struct expression_token token;
token.type = TOK_VARIABLE;
token.data.variable_id = id;
reallocate_expression(expr, expr->ntokens + 1);
expr->tokens[expr->ntokens - 1] = token;
}
static void
add_operator_token(struct bool_expression *expr, int op) {
struct expression_token token;
token.type = TOK_OPERATOR;
token.data.operator_id = op;
reallocate_expression(expr, expr->ntokens + 1);
expr->tokens[expr->ntokens - 1] = token;
}
void
expression_add_filter_and(struct bool_expression *expr, unsigned int filter_id)
{
add_variable_token(expr, filter_id);
add_operator_token(expr, OP_AND);
}
void
set_expression_qualify_mode(struct bool_expression *expr)
{
if (!expr)
error_msg_and_die("invalid expression");
reallocate_expression(expr, 1);
expr->tokens[0].type = TOK_VARIABLE;
expr->tokens[0].data.variable_id = 0;
}
ATTRIBUTE_FORMAT((printf, 3, 4))
static int
printf_append(char **ptr, char *end, const char *fmt, ...)
{
int ret;
va_list args;
va_start(args, fmt);
ret = vsnprintf(*ptr, end - *ptr, fmt, args);
va_end(args);
if (ret < 0)
return ret;
*ptr += MIN(ret, end - *ptr);
return ret;
}
/* Print full diagnostics for corrupted expression */
ATTRIBUTE_NORETURN
static void
handle_corrupted_expression(struct bool_expression *expr, bool *stack,
unsigned int stack_size, unsigned int current_pos,
bool *variables, unsigned int variables_num)
{
char *buf, *pos, *end;
unsigned int buf_size;
unsigned int i;
/* Calculate buffer size. */
buf_size = sizeof("corrupted filter expression:");
buf_size += sizeof("expression (ntokens = ):")
+ 3 * sizeof(unsigned int)
+ (sizeof("op_") + 3 * sizeof(int)) * expr->ntokens;
buf_size += sizeof("variables (nvariables = ):") + 3 * sizeof(int)
+ sizeof("false") * variables_num;
buf_size += sizeof("current position: ") + 3 * sizeof(int);
buf_size += sizeof("stack (stack_size = ):") + 3 * sizeof(int)
+ sizeof("false") * stack_size;
buf = xcalloc(buf_size, 1);
pos = buf;
end = buf + buf_size;
printf_append(&pos, end, "corrupted filter expression:\n");
/* Print expression. */
printf_append(&pos, end, "expression (ntokens = %u):", expr->ntokens);
for (i = 0; i < expr->ntokens; ++i) {
switch (expr->tokens[i].type) {
case TOK_VARIABLE:
printf_append(&pos, end, " v_%u",
expr->tokens[i].data.variable_id);
break;
case TOK_OPERATOR:
switch (expr->tokens[i].data.operator_id) {
case OP_NOT:
printf_append(&pos, end, " not");
break;
case OP_AND:
printf_append(&pos, end, " and");
break;
case OP_OR:
printf_append(&pos, end, " or");
break;
default:
printf_append(&pos, end, " op_%d",
expr->tokens[i].data.operator_id);
}
break;
default:
printf_append(&pos, end, " ?_%d", expr->tokens[i].type);
}
}
printf_append(&pos, end, "\n");
/* Print variables. */
printf_append(&pos, end, "variables (nvariables = %u):", variables_num);
for (i = 0; i < variables_num; ++i)
printf_append(&pos, end, !variables[i] ? " false" : " true");
printf_append(&pos, end, "\n");
printf_append(&pos, end, "current position: %u\n", current_pos);
/* Print current stack state. */
printf_append(&pos, end, "stack (stack_size = %u):", stack_size);
for (i = 0; i < stack_size; ++i)
printf_append(&pos, end, !stack[i] ? " false" : " true");
error_msg_and_die("%s", buf);
}
#define MAX_STACK_SIZE 32
bool
run_expression(struct bool_expression *expr, bool *variables,
unsigned int variables_num)
{
bool stack[MAX_STACK_SIZE];
unsigned int stack_size = 0;
unsigned int i;
for (i = 0; i < expr->ntokens; ++i) {
struct expression_token *tok = &expr->tokens[i];
switch (tok->type) {
case TOK_VARIABLE:
if (stack_size == MAX_STACK_SIZE)
handle_corrupted_expression(expr, stack,
stack_size, i,
variables,
variables_num);
if (tok->data.variable_id >= variables_num)
handle_corrupted_expression(expr, stack,
stack_size, i,
variables,
variables_num);
stack[stack_size++] = variables[tok->data.variable_id];
break;
case TOK_OPERATOR:
switch (tok->data.operator_id) {
case OP_NOT:
if (stack_size == 0)
handle_corrupted_expression(expr, stack,
stack_size, i,
variables,
variables_num);
stack[stack_size - 1] = !stack[stack_size - 1];
break;
case OP_AND:
if (stack_size < 2)
handle_corrupted_expression(expr, stack,
stack_size, i,
variables,
variables_num);
stack[stack_size - 2] = stack[stack_size - 2]
&& stack[stack_size - 1];
--stack_size;
break;
case OP_OR:
if (stack_size < 2)
handle_corrupted_expression(expr, stack,
stack_size, i,
variables,
variables_num);
stack[stack_size - 2] = stack[stack_size - 2]
|| stack[stack_size - 1];
--stack_size;
break;
default:
handle_corrupted_expression(expr, stack,
stack_size, i,
variables,
variables_num);
}
break;
}
}
if (stack_size != 1)
handle_corrupted_expression(expr, stack, stack_size, i,
variables, variables_num);
return stack[0];
}
/*
* Parse operator and add operator length to str and pos.
* Return -1 if no operator found.
*/
static int
parse_operator(char **str, unsigned int *pos)
{
#define _OP(s, op) { s, sizeof(s) - 1, op }
struct {
const char *str;
int len;
enum operator_type op;
} ops[] = {
_OP("!", OP_NOT),
_OP("not", OP_NOT),
_OP("&&", OP_AND),
_OP("and", OP_AND),
_OP("||", OP_OR),
_OP("or", OP_OR),
};
#undef _OP
char *p = *str;
for (unsigned int i = 0; i < ARRAY_SIZE(ops); i++) {
if (!strncmp(p, ops[i].str, ops[i].len) &&
(!is_allowed_in_name(ops[i].str[0]) ||
!is_allowed_in_name(p[ops[i].len]))) {
*str += ops[i].len - 1;
*pos += ops[i].len - 1;
return ops[i].op;
}
}
return -1;
}
static char *
unescape_argument(char **str)
{
char *p;
char *p_new;
bool escaped = false;
unsigned int size = 1;
char *new_str = xcalloc(strlen(*str) + 1, 1);
for (p = *str, p_new = new_str; *p; ++p) {
if (!escaped) {
if (*p == '\\') {
escaped = true;
continue;
} else if (is_space_ascii(*p) || *p == ')' || *p == '|'
|| *p == '&') {
break;
}
}
escaped = false;
*(p_new++) = *p;
size++;
}
*str = p - 1;
return xreallocarray(new_str, size, 1);
}
static void
push_operator(int *stack, unsigned int *stack_size, int op)
{
if (*stack_size == MAX_STACK_SIZE)
error_msg_and_die("stack overflow (expression is too complex)");
stack[*stack_size] = op;
(*stack_size)++;
}
static bool
is_higher_priority(int op_a, int op_b)
{
bool op_priority[] = {
[OP_NOT] = 2,
[OP_AND] = 1,
[OP_OR] = 0,
};
return op_priority[op_a] > op_priority[op_b];
}
void
parse_filter_expression(struct bool_expression *expr, const char *str,
struct filter_action *action, unsigned int start_id)
{
enum {
WAIT_FILTER,
FILTER_NAME,
FILTER_ARG,
WAIT_OPERATOR,
} state = WAIT_FILTER;
unsigned int variable_id = start_id;
/* Current stack stack_size */
unsigned int st_size = 0;
int stack[MAX_STACK_SIZE];
char *copy = xstrdup(str);
struct filter *cur_filter = NULL;
char *filter_name = NULL;
char *filter_arg = NULL;
int op;
unsigned int pos = 0;
for (char *p = copy; *p; ++p, ++pos) {
switch (state) {
case WAIT_FILTER:
if (*p == '(') {
push_operator(stack, &st_size, OP_PARENTHESIS);
} else if ((op = parse_operator(&p, &pos)) >= 0) {
if (op == OP_NOT) {
push_operator(stack, &st_size, op);
} else {
error_msg_and_die("invalid operator "
"at '%s':%u",
str, pos);
}
} else if (!is_space_ascii(*p)) {
filter_name = p;
state = FILTER_NAME;
}
break;
case FILTER_NAME:
if (is_space_ascii(*p)) {
*p = '\0';
cur_filter = create_filter(action, filter_name);
filter_arg = NULL;
state = FILTER_ARG;
}
break;
case FILTER_ARG:
if (!filter_arg && is_space_ascii(*p))
break;
filter_arg = unescape_argument(&p);
parse_filter(cur_filter, filter_arg, false);
free(filter_arg);
add_variable_token(expr, variable_id++);
state = WAIT_OPERATOR;
break;
case WAIT_OPERATOR:
if (is_space_ascii(*p))
break;
if (*p == ')') {
while ((st_size > 0) &&
(stack[st_size - 1] != OP_PARENTHESIS)) {
op = stack[--st_size];
add_operator_token(expr, op);
}
--st_size;
break;
}
op = parse_operator(&p, &pos);
if (op < 0 || op == OP_NOT)
error_msg_and_die("invalid operator at '%s':%u",
str, pos);
/* Pop operators with higher priority. */
while ((st_size > 0) &&
(stack[st_size - 1] != OP_PARENTHESIS) &&
is_higher_priority(stack[st_size - 1], op))
add_operator_token(expr, stack[--st_size]);
push_operator(stack, &st_size, op);
state = WAIT_FILTER;
break;
}
}
free(copy);
if (state != WAIT_OPERATOR)
error_msg_and_die("unfinished filter expression '%s'", str);
while (st_size > 0)
add_operator_token(expr, stack[--st_size]);
if (start_id > 0)
add_operator_token(expr, OP_OR);
}

252
filter_parse.c Normal file
View File

@ -0,0 +1,252 @@
/*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "defs.h"
#include "filter.h"
bool
is_space_ascii(char c)
{
return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') ||
(c == '\v') || (c == '\f');
}
bool
is_allowed_in_name(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9') || (c == '_');
}
/*
* Split expression into action name, filter expression or qualify set
* and action arguments.
*/
void
filtering_parse(const char *str)
{
enum parsing_states {
F_EMPTY,
F_BEGIN,
F_QUAL_SET,
F_FILT_EXPR,
F_QUAL_ARGS,
F_FILT_ARGS,
F_END,
} state = F_EMPTY;
const char *begin = NULL;
const char *action_name = "trace";
const char *main_part = NULL;
const char *args = NULL;
bool action_specified = false;
bool escaped = false;
int parentheses_count = 0;
/* Used to store position of last terminating parenthesis. */
char *expression_end = NULL;
/* Used to provide diagnostics. */
unsigned int pos = 0;
char *copy = xstrdup(str);
for (char *p = copy; *p; ++p, ++pos) {
switch (state) {
case F_EMPTY:
switch (*p) {
/* trace(), action name omitted */
case '(':
parentheses_count++;
main_part = p;
state = F_FILT_EXPR;
break;
/* missing action name */
case '=':
*p = '\0';
error_msg_and_die("invalid filter action '%s'",
copy);
default:
if (is_space_ascii(*p)) {
break;
} else if (!strncmp(p, "not", 3) && *(p + 3) &&
(is_space_ascii(*(p + 3)) ||
*(p + 3) == '(')) {
main_part = p;
state = F_FILT_EXPR;
break;
} else {
begin = p;
state = F_BEGIN;
}
}
if (state != F_BEGIN)
break;
/* else fall through to check for qualify set */
ATTRIBUTE_FALLTHROUGH;
case F_BEGIN:
switch (*p) {
/* action(...) */
case '(':
if (*begin == '!') {
main_part = begin;
} else {
action_name = begin;
action_specified = true;
*p = '\0';
main_part = p + 1;
}
state = F_FILT_EXPR;
parentheses_count++;
break;
/* action=... */
case '=':
action_name = begin;
action_specified = true;
*p = '\0';
main_part = p + 1;
state = F_QUAL_SET;
break;
case ':':
main_part = begin;
*p = '\0';
args = p + 1;
state = F_QUAL_ARGS;
break;
case ';':
error_msg_and_die("invalid arguments position "
"'%s':%u",
str, pos);
/* qualify set without action. */
case ',':
case '?':
case '/':
case '%':
case '-':
main_part = begin;
state = F_QUAL_SET;
break;
default:
/* new expression without action. */
if (is_space_ascii(*p)) {
main_part = begin;
state = F_FILT_EXPR;
}
}
break;
case F_QUAL_SET:
if (*p == ':') {
*p = '\0';
args = p + 1;
state = F_QUAL_ARGS;
}
break;
case F_FILT_EXPR:
if (!escaped) {
switch (*p) {
case ';':
if (parentheses_count != 1 ||
!action_specified)
error_msg_and_die("invalid "
"arguments "
"position "
"'%s':%u",
str, pos);
*p = '\0';
args = p + 1;
state = F_FILT_ARGS;
break;
case '(':
parentheses_count++;
break;
case ')':
if (parentheses_count <= 0)
error_msg_and_die("unexpected "
"')' at "
"'%s':%u",
str, pos);
parentheses_count--;
expression_end = p;
if (action_specified &&
parentheses_count == 0)
state = F_END;
break;
case '\\':
escaped = true;
break;
}
} else
escaped = false;
break;
case F_QUAL_ARGS:
break;
case F_FILT_ARGS:
if (!escaped) {
switch (*p) {
case ')':
parentheses_count--;
expression_end = p;
state = F_END;
break;
case '\\':
escaped = true;
break;
}
} else
escaped = false;
break;
case F_END:
if (!is_space_ascii(*p))
error_msg_and_die("unexpected '%c' at "
"'%s':%u", *p, str, pos);
}
}
switch (state) {
case F_EMPTY:
main_part = copy;
parse_qualify_action(action_name, main_part, args);
break;
case F_BEGIN:
main_part = begin;
ATTRIBUTE_FALLTHROUGH;
case F_QUAL_SET:
case F_QUAL_ARGS:
parse_qualify_action(action_name, main_part, args);
break;
case F_FILT_EXPR:
case F_FILT_ARGS:
case F_END:
if (parentheses_count != 0)
error_msg_and_die("missing ')' in '%s'", str);
if (action_specified && expression_end)
*expression_end = '\0';
parse_filter_action(action_name, main_part, args);
break;
}
free(copy);
}

View File

@ -33,16 +33,8 @@
#include "delay.h"
#include "retval.h"
struct number_set *read_set;
struct number_set *write_set;
struct number_set *signal_set;
static struct number_set *abbrev_set;
static struct number_set *inject_set;
static struct number_set *raw_set;
static struct number_set *trace_set;
static struct number_set *verbose_set;
static int
sigstr_to_uint(const char *s)
{
@ -220,161 +212,144 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts,
return true;
}
static const char *
parse_inject_expression(char *const str,
struct inject_opts *const fopts,
const bool fault_tokens_only)
void
parse_inject_common_args(char *const str, struct inject_opts *const opts,
const bool fault_tokens_only, const bool qualify_mode)
{
if (str[0] == '\0' || str[0] == ':')
return "";
char *saveptr = NULL;
const char *name = strtok_r(str, ":", &saveptr);
const char *delim = qualify_mode ? ":" : ";";
char *token;
while ((token = strtok_r(NULL, ":", &saveptr))) {
if (!parse_inject_token(token, fopts, fault_tokens_only))
return NULL;
}
return name;
}
static void
qualify_read(const char *const str)
{
if (!read_set)
read_set = alloc_number_set_array(1);
qualify_tokens(str, read_set, string_to_uint, "descriptor");
}
static void
qualify_write(const char *const str)
{
if (!write_set)
write_set = alloc_number_set_array(1);
qualify_tokens(str, write_set, string_to_uint, "descriptor");
}
static void
qualify_signals(const char *const str)
{
if (!signal_set)
signal_set = alloc_number_set_array(1);
qualify_tokens(str, signal_set, sigstr_to_uint, "signal");
}
static void
qualify_trace(const char *const str)
{
if (!trace_set)
trace_set = alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(str, trace_set);
}
static void
qualify_abbrev(const char *const str)
{
if (!abbrev_set)
abbrev_set = alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(str, abbrev_set);
}
static void
qualify_verbose(const char *const str)
{
if (!verbose_set)
verbose_set = alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(str, verbose_set);
}
static void
qualify_raw(const char *const str)
{
if (!raw_set)
raw_set = alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(str, raw_set);
}
static void
qualify_inject_common(const char *const str,
const bool fault_tokens_only,
const char *const description)
{
struct inject_opts opts = {
*opts = (struct inject_opts) {
.first = 1,
.step = 1,
.data = {
.delay_idx = -1
}
};
char *copy = xstrdup(str);
const char *name =
parse_inject_expression(copy, &opts, fault_tokens_only);
if (!name)
error_msg_and_die("invalid %s '%s'", description, str);
struct number_set *tmp_set =
alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(name, tmp_set);
free(copy);
for (char *token = str ? strtok_r(str, delim, &saveptr) : NULL;
token; token = strtok_r(NULL, delim, &saveptr)) {
if (!parse_inject_token(token, opts, fault_tokens_only)) {
/* return an error by resetting inject flags */
opts->data.flags = 0;
return;
}
}
/* If neither of retval, error, signal or delay is specified, then ... */
if (!opts.data.flags) {
if (!opts->data.flags) {
if (fault_tokens_only) {
/* in fault= syntax the default error code is ENOSYS. */
opts.data.rval_idx = retval_new(ENOSYS);
opts.data.flags |= INJECT_F_ERROR;
opts->data.rval_idx = retval_new(ENOSYS);
opts->data.flags |= INJECT_F_ERROR;
} else {
/* in inject= syntax this is not allowed. */
error_msg_and_die("invalid %s '%s'", description, str);
return;
}
}
/*
* Initialize inject_vec according to tmp_set.
* Merge tmp_set into inject_set.
*/
for (unsigned int p = 0; p < SUPPORTED_PERSONALITIES; ++p) {
if (number_set_array_is_empty(tmp_set, p))
continue;
if (!inject_set) {
inject_set =
alloc_number_set_array(SUPPORTED_PERSONALITIES);
}
if (!inject_vec[p]) {
inject_vec[p] = xcalloc(nsyscall_vec[p],
sizeof(*inject_vec[p]));
}
for (unsigned int i = 0; i < nsyscall_vec[p]; ++i) {
if (is_number_in_set_array(i, tmp_set, p)) {
add_number_to_set_array(i, inject_set, p);
inject_vec[p][i] = opts;
}
}
}
free_number_set_array(tmp_set, SUPPORTED_PERSONALITIES);
}
static void
qualify_fault(const char *const str)
qualify_signals(const char *const main_part, const char *const args)
{
qualify_inject_common(str, true, "fault argument");
if (!signal_set)
signal_set = alloc_number_set_array(1);
qualify_tokens(main_part, signal_set, sigstr_to_uint, "signal", true);
if (args)
error_msg("signal action takes no arguments, ignored arguments "
"'%s'", args);
}
static void
qualify_inject(const char *const str)
qualify_filter(const char *const main_part, const char *const args,
const char *const action_name, const char *const filter_type)
{
qualify_inject_common(str, false, "inject argument");
struct filter_action *action = find_or_add_action(action_name);
struct filter *filter = create_filter(action, filter_type);
parse_filter(filter, main_part, true);
if (args)
error_msg("%s action takes no arguments, ignored arguments "
"'%s'", action_name, args);
set_qualify_mode(action);
}
static void
qualify_read(const char *const main_part, const char *const args)
{
qualify_filter(main_part, args, "read", "fd");
}
static void
qualify_write(const char *const main_part, const char *const args)
{
qualify_filter(main_part, args, "write", "fd");
}
static void
qualify_trace(const char *const main_part, const char *const args)
{
qualify_filter(main_part, args, "trace", "syscall");
}
static void
qualify_abbrev(const char *const main_part, const char *const args)
{
qualify_filter(main_part, args, "abbrev", "syscall");
}
static void
qualify_verbose(const char *const main_part, const char *const args)
{
qualify_filter(main_part, args, "verbose", "syscall");
}
static void
qualify_raw(const char *const main_part, const char *const args)
{
qualify_filter(main_part, args, "raw", "syscall");
}
static void
qualify_inject_common(const char *const main_part, const char *const args,
const bool fault_tokens_only,
const char *const description)
{
struct inject_opts *opts = xmalloc(sizeof(struct inject_opts));
char *copy = xstrdup(args);
struct filter_action *action;
struct filter *filter;
action = find_or_add_action(fault_tokens_only ? "fault" : "inject");
filter = create_filter(action, "syscall");
parse_filter(filter, main_part, true);
set_qualify_mode(action);
parse_inject_common_args(copy, opts, fault_tokens_only, true);
if (!opts->data.flags)
error_msg_and_die("invalid %s argument '%s'", description,
args ? args : "");
free(copy);
set_filter_action_priv_data(action, opts);
}
static void
qualify_fault(const char *const main_part, const char *const args)
{
qualify_inject_common(main_part, args, true, "fault");
}
static void
qualify_inject(const char *const main_part, const char *const args)
{
qualify_inject_common(main_part, args, false, "inject");
}
static const struct qual_options {
const char *name;
void (*qualify)(const char *);
void (*qualify)(const char *, const char *);
} qual_options[] = {
{ "trace", qualify_trace },
{ "t", qualify_trace },
@ -398,36 +373,19 @@ static const struct qual_options {
};
void
qualify(const char *str)
parse_qualify_action(const char *action_name, const char *main_part,
const char *args)
{
const struct qual_options *opt = qual_options;
const struct qual_options *opt = NULL;
for (unsigned int i = 0; i < ARRAY_SIZE(qual_options); ++i) {
const char *name = qual_options[i].name;
const size_t len = strlen(name);
const char *val = str_strip_prefix_len(str, name, len);
if (val == str || *val != '=')
continue;
str = val + 1;
opt = &qual_options[i];
break;
if (!strcmp(action_name, qual_options[i].name)) {
opt = &qual_options[i];
break;
}
}
opt->qualify(str);
}
unsigned int
qual_flags(const unsigned int scno)
{
return (is_number_in_set_array(scno, trace_set, current_personality)
? QUAL_TRACE : 0)
| (is_number_in_set_array(scno, abbrev_set, current_personality)
? QUAL_ABBREV : 0)
| (is_number_in_set_array(scno, verbose_set, current_personality)
? QUAL_VERBOSE : 0)
| (is_number_in_set_array(scno, raw_set, current_personality)
? QUAL_RAW : 0)
| (is_number_in_set_array(scno, inject_set, current_personality)
? QUAL_INJECT : 0);
if (!opt)
error_msg_and_die("invalid filter action '%s'", action_name);
opt->qualify(main_part ? main_part : "", args);
}

View File

@ -59,8 +59,6 @@ alloc_number_set_array(unsigned int nmemb) ATTRIBUTE_MALLOC;
extern void
free_number_set_array(struct number_set *, unsigned int nmemb);
extern struct number_set *read_set;
extern struct number_set *write_set;
extern struct number_set *signal_set;
#endif /* !STRACE_NUMBER_SET_H */

View File

@ -70,6 +70,8 @@ upathmatch(struct tcb *const tcp, const kernel_ulong_t upath,
static bool
fdmatch(struct tcb *tcp, int fd, struct path_set *set)
{
if (fd < 0)
return false;
char path[PATH_MAX + 1];
int n = getfdpath(tcp, fd, path, sizeof(path));
@ -91,7 +93,7 @@ storepath(const char *path, struct path_set *set)
xgrowarray(set->paths_selected, &set->size,
sizeof(set->paths_selected[0]));
set->paths_selected[set->num_selected++] = path;
set->paths_selected[set->num_selected++] = xstrdup(path);
}
/*
@ -143,9 +145,17 @@ pathtrace_select_set(const char *path, struct path_set *set)
storepath(rpath, set);
}
typedef bool (*match_fd_func)(struct tcb *, int, void *);
static
bool fdmatch_fd_func(struct tcb *tcp, int fd, void *data)
{
return fdmatch(tcp, fd, (struct path_set *) data);
}
static bool
match_xselect_args(struct tcb *tcp, const kernel_ulong_t *args,
struct path_set *set)
match_xselect_args(struct tcb *tcp, match_fd_func func,
const kernel_ulong_t *args, struct path_set *set)
{
/* Kernel truncates arg[0] to int, we do the same. */
int nfds = (int) args[0];
@ -167,7 +177,7 @@ match_xselect_args(struct tcb *tcp, const kernel_ulong_t *args,
j = next_set_bit(fds, j, nfds);
if (j < 0)
break;
if (fdmatch(tcp, j, set)) {
if (func(tcp, j, set)) {
free(fds);
return true;
}
@ -178,25 +188,15 @@ match_xselect_args(struct tcb *tcp, const kernel_ulong_t *args,
return false;
}
/*
* Return true if syscall accesses a selected path
* (or if no paths have been specified for tracing).
*/
/* Match fd with func. */
bool
pathtrace_match_set(struct tcb *tcp, struct path_set *set)
match_fd_common(struct tcb *tcp, match_fd_func func, void *data)
{
const struct_sysent *s;
const struct_sysent *s = tcp->s_ent;
s = tcp->s_ent;
if (!(s->sys_flags & (TRACE_FILE | TRACE_DESC | TRACE_NETWORK)))
if (!(s->sys_flags & (TRACE_DESC | TRACE_NETWORK)))
return false;
/*
* Check for special cases where we need to do something
* other than test arg[0].
*/
switch (s->sen) {
case SEN_dup2:
case SEN_dup3:
@ -205,49 +205,17 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
case SEN_sendfile64:
case SEN_tee:
/* fd, fd */
return fdmatch(tcp, tcp->u_arg[0], set) ||
fdmatch(tcp, tcp->u_arg[1], set);
case SEN_execveat:
case SEN_faccessat:
case SEN_fchmodat:
case SEN_fchownat:
case SEN_fstatat64:
case SEN_futimesat:
case SEN_inotify_add_watch:
case SEN_mkdirat:
case SEN_mknodat:
case SEN_name_to_handle_at:
case SEN_newfstatat:
case SEN_openat:
case SEN_readlinkat:
case SEN_statx:
case SEN_unlinkat:
case SEN_utimensat:
/* fd, path */
return fdmatch(tcp, tcp->u_arg[0], set) ||
upathmatch(tcp, tcp->u_arg[1], set);
case SEN_link:
case SEN_mount:
case SEN_pivotroot:
/* path, path */
return upathmatch(tcp, tcp->u_arg[0], set) ||
upathmatch(tcp, tcp->u_arg[1], set);
case SEN_quotactl:
case SEN_symlink:
/* x, path */
return upathmatch(tcp, tcp->u_arg[1], set);
return func(tcp, tcp->u_arg[0], data) ||
func(tcp, tcp->u_arg[1], data);
case SEN_copy_file_range:
case SEN_linkat:
case SEN_renameat2:
case SEN_renameat:
/* fd, path, fd, path */
return fdmatch(tcp, tcp->u_arg[0], set) ||
fdmatch(tcp, tcp->u_arg[2], set) ||
upathmatch(tcp, tcp->u_arg[1], set) ||
upathmatch(tcp, tcp->u_arg[3], set);
case SEN_splice:
/* fd, x, fd */
return func(tcp, tcp->u_arg[0], data) ||
func(tcp, tcp->u_arg[2], data);
#if HAVE_ARCH_OLD_MMAP
case SEN_old_mmap:
@ -258,7 +226,7 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
kernel_ulong_t *args =
fetch_indirect_syscall_args(tcp, tcp->u_arg[0], 6);
return args && fdmatch(tcp, args[4], set);
return args && func(tcp, args[4], data);
}
#endif /* HAVE_ARCH_OLD_MMAP */
@ -267,22 +235,15 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
case SEN_mmap_pgoff:
case SEN_ARCH_mmap:
/* x, x, x, x, fd */
return fdmatch(tcp, tcp->u_arg[4], set);
return func(tcp, tcp->u_arg[4], data);
case SEN_symlinkat:
/* x, fd, path */
return fdmatch(tcp, tcp->u_arg[1], set) ||
upathmatch(tcp, tcp->u_arg[2], set);
case SEN_copy_file_range:
case SEN_splice:
/* fd, x, fd, x, x, x */
return fdmatch(tcp, tcp->u_arg[0], set) ||
fdmatch(tcp, tcp->u_arg[2], set);
/* x, fd, x */
return func(tcp, tcp->u_arg[1], data);
case SEN_epoll_ctl:
/* x, x, fd, x */
return fdmatch(tcp, tcp->u_arg[2], set);
return func(tcp, tcp->u_arg[2], data);
case SEN_fanotify_mark:
@ -290,21 +251,22 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
/* x, x, mask (64 bit), fd, path */
unsigned long long mask = 0;
int argn = getllval(tcp, &mask, 2);
return fdmatch(tcp, tcp->u_arg[argn], set) ||
upathmatch(tcp, tcp->u_arg[argn + 1], set);
return func(tcp, tcp->u_arg[argn], data);
}
#if HAVE_ARCH_OLD_SELECT
case SEN_oldselect:
{
kernel_ulong_t *args =
fetch_indirect_syscall_args(tcp, tcp->u_arg[0], 5);
return args && match_xselect_args(tcp, args, set);
return args && match_xselect_args(tcp, func, args, data);
}
#endif
case SEN_pselect6:
case SEN_select:
return match_xselect_args(tcp, tcp->u_arg, set);
return match_xselect_args(tcp, func, tcp->u_arg, data);
case SEN_poll:
case SEN_ppoll:
{
@ -325,16 +287,21 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
for (cur = start; cur < end; cur += sizeof(fds)) {
if (umove(tcp, cur, &fds))
break;
if (fdmatch(tcp, fds.fd, set))
if (func(tcp, fds.fd, data))
return true;
}
return false;
}
/*
* These have TRACE_DESCRIPTOR or TRACE_NETWORK set,
* but they don't have any file descriptor to test.
*/
case SEN_accept4:
case SEN_accept:
case SEN_bpf:
case SEN_creat:
case SEN_epoll_create:
case SEN_epoll_create1:
case SEN_eventfd2:
@ -346,8 +313,7 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
case SEN_mq_getsetattr:
case SEN_mq_notify:
case SEN_mq_open:
case SEN_mq_timedreceive:
case SEN_mq_timedsend:
case SEN_open:
case SEN_perf_event_open:
case SEN_pipe:
case SEN_pipe2:
@ -360,10 +326,84 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
case SEN_timerfd_gettime:
case SEN_timerfd_settime:
case SEN_userfaultfd:
/*
* These have TRACE_FILE or TRACE_DESCRIPTOR or TRACE_NETWORK set,
* but they don't have any file descriptor or path args to test.
*/
return false;
}
return func(tcp, tcp->u_arg[0], data);
}
/*
* Return true if syscall accesses a selected path
* (or if no paths have been specified for tracing).
*/
bool
pathtrace_match_set(struct tcb *tcp, struct path_set *set)
{
const struct_sysent *s;
s = tcp->s_ent;
if (!(s->sys_flags & (TRACE_FILE | TRACE_DESC | TRACE_NETWORK)))
return false;
if (match_fd_common(tcp, fdmatch_fd_func, set))
return true;
if (!(s->sys_flags & TRACE_FILE))
return false;
/*
* Check for special cases where we need to do something
* other than test arg[0].
*/
switch (s->sen) {
case SEN_execveat:
case SEN_faccessat:
case SEN_fchmodat:
case SEN_fchownat:
case SEN_fstatat64:
case SEN_futimesat:
case SEN_inotify_add_watch:
case SEN_mkdirat:
case SEN_mknodat:
case SEN_name_to_handle_at:
case SEN_newfstatat:
case SEN_openat:
case SEN_quotactl:
case SEN_readlinkat:
case SEN_symlink:
case SEN_statx:
case SEN_unlinkat:
case SEN_utimensat:
/* x, path */
return upathmatch(tcp, tcp->u_arg[1], set);
case SEN_link:
case SEN_mount:
case SEN_pivotroot:
/* path, path */
return upathmatch(tcp, tcp->u_arg[0], set) ||
upathmatch(tcp, tcp->u_arg[1], set);
case SEN_linkat:
case SEN_renameat2:
case SEN_renameat:
/* x, path, x, path */
return upathmatch(tcp, tcp->u_arg[1], set) ||
upathmatch(tcp, tcp->u_arg[3], set);
case SEN_symlinkat:
/* x, x, path */
return upathmatch(tcp, tcp->u_arg[2], set);
case SEN_fanotify_mark:
{
/* x, x, mask (64 bit), fd, path */
unsigned long long mask = 0;
int argn = getllval(tcp, &mask, 2);
return upathmatch(tcp, tcp->u_arg[argn + 1], set);
}
case SEN_printargs:
return false;
}
@ -371,12 +411,5 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
* Our fallback position for calls that haven't already
* been handled is to just check arg[0].
*/
if (s->sys_flags & TRACE_FILE)
return upathmatch(tcp, tcp->u_arg[0], set);
if (s->sys_flags & (TRACE_DESC | TRACE_NETWORK))
return fdmatch(tcp, tcp->u_arg[0], set);
return false;
return upathmatch(tcp, tcp->u_arg[0], set);
}

View File

@ -407,10 +407,15 @@ and
Summarise the time difference between the beginning and end of
each system call. The default is to summarise the system time.
.SS Filtering
.LP
.B strace
has two types of expressions with different syntax: set-based
expressions and filter-based expressions.
.SS Set-based expression
.TP 12
.BI "\-e " expr
A qualifying expression which modifies which events to trace
or how to trace them. The format of the expression is:
Set-based expression modifies which events to trace
or how to trace them. The format of the expression is:
.RS 15
.IP
[\,\fIqualifier\/\fB=\fR][\fB!\fR][\fB?\fR]\,\fIvalue1\/\fR[\fB,\fR[\fB?\fR]\,\fIvalue2\/\fR]...
@ -760,6 +765,269 @@ This is equivalent to more generic
option set to
.IR ENOSYS .
.SS Filter-based expression
.TP 12
.BI "\-e " expr
Filter-based expression modifies which actions are applied
to syscalls and conditions when actions
are applied. The format of the expression is:
.RS 15
.IP
[\,\fIaction\/\fB(\fR]\fIfilter_expression\/\fR[\fB;\fR\fIarg1\/\fR[\fB;\fR\fIarg2\/\fR...]][\fB)\fR]
.RE
.IP
where
.I action
is one of
.BR trace ,
.BR abbrev ,
.BR verbose ,
.BR raw ,
.BR read ,
.BR write ,
.BR fault ,
.BR inject ,
or
.B stacktrace
and
.I arg
is an action-dependent argument. The default action is
.BR trace .
.I arg
can be specified only when action is specified.
.I arg
is ignored if the action does not take any arguments.
.TP
\,\fIfilter_expression\fR
The filter expression consits of one or more \fIfilters\fR.
\fIFilters\fR consist of a \fIfilter_name\fR and filter-dependent argument.
Complex expressions are built up by using
the operators \fBand, or\fR and \fBnot\fR
(or their short versions: \fB&&, ||\fR and \fB!\fR)
to combine \fIfilters\fR. Allowable filters are:
.TP
\fBsyscall\fR \fIsyscall-specifier-set\fR
Match system calls by name or syscall number. The format of
\fIsyscall-specifier-set\fR is:
.RS 15
.IP
\fIsyscall-specifier1\fR[\fB,\fR\fIsyscall-specifier2\fR...]
.RE
.IP
where \fIsyscall-specifier\fR can be a system call name,
a class of system calls or
regular expression.
In addition, the special values
.B all
and
.B none
have the obvious meanings.
.TP
\fBsyscall\fR \fIsyscall1\fR[\fB,\fR\fIsyscall2\fR...]
Match only the specified set of system calls. For example,
.BR syscall\ open,close,read,write
means to only match those four system calls.
.TP
\fBsyscall\fR %\fIclass\fR
where
.I class
is one of
.BR file ,
.BR process ,
.BR network ,
.BR signal ,
.BR ipc ,
.BR desc ,
.BR memory ,
.BR stat ,
.BR lstat ,
.BR fstat ,
.BR %stat ,
.BR statfs ,
.BR fstatfs ,
.BR %statfs .
These classes are similar to classes, defined in set-based expression's
section (\fB-e trace=%\fR\fIclass\fR).
.TP
\fBsyscall\fR /\fIregex\fR
Match only those system calls that match the
.IR regex .
You can use
.B POSIX
Extended Regular Expression syntax (see
.BR regex (7)).
.TP
\fBfd\fR \fIfd1\fR[\fB,\fR\fIfd2\fR...]
Match system calls by file descriptor. Only system calls that have fd argument
are matched (system calls from \fB%desc\fR, \fB%network\fR classes and
mq_timedsend and mq_timedreceive system calls).
.TP
\fBpath\fR \fIpath\fR
Match system calls by path. Only system calls that have fd or path argument
are matched (system calls from \fB%desc\fR, \fB%network\fR, \fB%file\fR classes
and mq_timedsend and mq_timedreceive system calls).
.IP
Special characters (spaces, parentheses, operator characters) in filter
arguments can be escaped with \'\fB\\\fR\'.
.TP
\fB\-e\ trace(\fR\,\fIfilter_expression\fB)\fR
Trace only those system calls that match the \fIfilter_expression\fR.
.TP
\fB\-e\ abbrev(\fR\,\fIfilter_expression\fB)\fR
Abbreviate the output from printing each member of large structures
for those system calls that match the \fIfilter_expression\fR.
.TP
\fB\-e\ verbose(\fR\,\fIfilter_expression\fB)\fR
Dereference structures for those system calls
that match the \fIfilter_expression\fR.
.TP
\fB\-e\ raw(\fR\,\fIfilter_expression\fB)\fR
Print raw, undecoded arguments for those system calls
that match the \fIfilter_expression\fR.
This option has the effect of causing all arguments to be printed
in hexadecimal. This is mostly useful if you don't trust the
decoding or you need to know the actual numeric value of an
argument.
.TP
\fB\-e\ read(\fR\,\fIfilter_expression\fB)\fR
Perform a full hexadecimal and ASCII dump of all the data read by
those system calls that match the \fIfilter_expression\fR.
.TP
\fB\-e\ write(\fR\,\fIfilter_expression\fB)\fR
Perform a full hexadecimal and ASCII dump of all the data written by
those system calls that match the \fIfilter_expression\fR.
.TP
\fB\-e\ stacktrace(\fR\,\fIfilter_expression\fB)\fR
Print the execution stack trace of the traced processes after
those system calls that match the \fIfilter_expression\fR.
This action is available only if
.B strace
is built with libunwind.
.TP
\fB\-e\ inject(\fR\,\fIfilter_expression\fR[;\fBerror\fR=\,\fIerrno\/\fR|;\fBretval\fR=\,\fIvalue\/\fR][;\fBsignal\fR=\,\fIsig\/\fR][;\fBwhen\fR=\,\fIexpr\/\fR]\fB)\fR
Perform syscall tampering for those system calls
that match the \fIfilter_expression\fR.
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
or a numeric value within 1..\fBSIGRTMAX\fR range,
that signal is delivered on entering every syscall
that match the \fIfilter_expression\fR.
If ;\fBsignal\fR=\,\fIsig\/\fR option is specified without
;\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 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 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
that match the \fIfilter_expression\fR.
The format of the subexpression is one of the following:
.RS
.IP "" 2
.I first
.RS 4
For every syscall from the
.IR set ,
perform an injection for the syscall invocation number
.I first
only.
.RE
.IP "" 2
\fIfirst\/\fB+\fR
.RS 4
For every syscall from the
.IR set ,
perform injections for the syscall invocation number
.I first
and all subsequent invocations.
.RE
.IP "" 2
\fIfirst\/\fB+\fIstep\fR
.RS 4
For every syscall from the
.IR set ,
perform injections for syscall invocations number
.IR first ,
.IR first + step ,
.IR first + step + step ,
and so on.
.RE
.RE
.IP
For example, to fail each third and subsequent chdir syscalls with
.BR ENOENT ,
use
\fB\-e\ inject\fR\fB(\fR\,\fIsyscall chdir\/\fR;\fBerror\fR=\,\fIENOENT\/\fR;\fBwhen\fR=\,\fI3\/\fB+\fR\fB)\fR.
The valid range for numbers
.I first
and
.I step
is 1..65535.
An injection expression can contain only one
.BR error =
or
.BR retval =
specification.
If an injection expression contains multiple
.BR when =
specifications, the last one takes precedence.
Accounting of syscalls that are subject to injection
is done per syscall and per tracee.
.TP
\fB\-e\ fault(\fR\,\fIfilter_expression\fR[;\fBerror\fR=\,\fIerrno\/\fR][;\fBwhen\fR=\,\fIexpr\/\fR]\fB)\fR
Perform syscall fault injection for those system calls
that match the \fIfilter_expression\fR.
This is equivalent to more generic
\fB\-e\ inject\fR= expression with default value of
.I errno
option set to
.IR ENOSYS .
.IP
Multiple actions of the same type(except \fBinject\fR and \fBfault\fR actions)
are combined into one action with \fBor\fR operator. For example,
\fB\-e trace(syscall open) \-e trace(syscall close)\fR is equal to
\fB\-e trace(syscall open or syscall close)\fR.
Only one inject or fault action can be applied to one syscall. If two or more
actions try to tamper the same syscall, last of them is applied.
.TP
.BI "\-P " path
Trace only system calls accessing

View File

@ -66,7 +66,7 @@ extern int optind;
extern char *optarg;
#ifdef ENABLE_STACKTRACE
/* if this is true do the stack trace for every system call */
/* if this is true do the initialization of stack tracing mechanism */
bool stack_trace_enabled;
#endif
@ -1586,13 +1586,10 @@ init(int argc, char *argv[])
shared_log = stderr;
set_sortby(DEFAULT_SORTBY);
set_personality(DEFAULT_PERSONALITY);
qualify("trace=all");
qualify("abbrev=all");
qualify("verbose=all");
#if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
# error Bug in DEFAULT_QUAL_FLAGS
#endif
qualify("signal=all");
filtering_parse("signal=all");
while ((c = getopt(argc, argv, "+"
#ifdef ENABLE_STACKTRACE
"k"
@ -1632,7 +1629,7 @@ init(int argc, char *argv[])
daemonized_tracer = 1;
break;
case 'e':
qualify(optarg);
filtering_parse(optarg);
break;
case 'E':
if (putenv(optarg) < 0)
@ -1657,7 +1654,7 @@ init(int argc, char *argv[])
break;
#ifdef ENABLE_STACKTRACE
case 'k':
stack_trace_enabled = true;
filtering_parse("stacktrace(syscall all)");
break;
#endif
case 'o':
@ -1700,7 +1697,7 @@ init(int argc, char *argv[])
username = optarg;
break;
case 'v':
qualify("abbrev=none");
filtering_parse("abbrev=none");
break;
case 'V':
print_version();
@ -1733,6 +1730,7 @@ init(int argc, char *argv[])
break;
}
}
filtering_parsing_finish();
argv += optind;
argc -= optind;
@ -2414,6 +2412,8 @@ trace_syscall(struct tcb *tcp, unsigned int *sig)
case 0:
return 0;
case 1:
if (!tcp->qual_flg)
filter_syscall(tcp);
res = syscall_entering_trace(tcp, sig);
}
syscall_entering_finish(tcp, res);

View File

@ -304,7 +304,6 @@ decode_socket_subcall(struct tcb *tcp)
return;
tcp->scno = scno;
tcp->qual_flg = qual_flags(scno);
tcp->s_ent = &sysent[scno];
unsigned int i;
@ -344,7 +343,6 @@ decode_ipc_subcall(struct tcb *tcp)
}
tcp->scno = SYS_ipc_subcall + call;
tcp->qual_flg = qual_flags(tcp->scno);
tcp->s_ent = &sysent[tcp->scno];
const unsigned int n = tcp->s_ent->nargs;
@ -361,7 +359,6 @@ decode_syscall_subcall(struct tcb *tcp)
if (!scno_is_valid(tcp->u_arg[0]))
return;
tcp->scno = tcp->u_arg[0];
tcp->qual_flg = qual_flags(tcp->scno);
tcp->s_ent = &sysent[tcp->scno];
memmove(&tcp->u_arg[0], &tcp->u_arg[1],
sizeof(tcp->u_arg) - sizeof(tcp->u_arg[0]));
@ -389,7 +386,7 @@ dumpio(struct tcb *tcp)
if (fd < 0)
return;
if (is_number_in_set(fd, write_set)) {
if (dump_write(tcp)) {
switch (tcp->s_ent->sen) {
case SEN_write:
case SEN_pwrite:
@ -416,7 +413,7 @@ dumpio(struct tcb *tcp)
if (syserror(tcp))
return;
if (is_number_in_set(fd, read_set)) {
if (dump_read(tcp)) {
switch (tcp->s_ent->sen) {
case SEN_read:
case SEN_pread:
@ -471,8 +468,6 @@ static void get_error(struct tcb *, const bool);
static int arch_set_error(struct tcb *);
static int arch_set_success(struct tcb *);
struct inject_opts *inject_vec[SUPPORTED_PERSONALITIES];
static struct inject_opts *
tcb_inject_opts(struct tcb *tcp)
{
@ -484,14 +479,6 @@ tcb_inject_opts(struct tcb *tcp)
static long
tamper_with_syscall_entering(struct tcb *tcp, unsigned int *signo)
{
if (!tcp->inject_vec[current_personality]) {
tcp->inject_vec[current_personality] =
xcalloc(nsyscalls, sizeof(**inject_vec));
memcpy(tcp->inject_vec[current_personality],
inject_vec[current_personality],
nsyscalls * sizeof(**inject_vec));
}
struct inject_opts *opts = tcb_inject_opts(tcp);
if (!opts || opts->first == 0)
@ -669,7 +656,7 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
}
#ifdef ENABLE_STACKTRACE
if (stack_trace_enabled) {
if (stacktrace(tcp)) {
if (tcp->s_ent->sys_flags & STACKTRACE_CAPTURE_ON_ENTER)
unwind_tcb_capture(tcp);
}
@ -912,7 +899,7 @@ syscall_exiting_trace(struct tcb *tcp, struct timespec *ts, int res)
line_ended();
#ifdef ENABLE_STACKTRACE
if (stack_trace_enabled)
if (stacktrace(tcp))
unwind_tcb_print(tcp);
#endif
return 0;
@ -1188,7 +1175,8 @@ get_scno(struct tcb *tcp)
if (scno_is_valid(tcp->scno)) {
tcp->s_ent = &sysent[tcp->scno];
tcp->qual_flg = qual_flags(tcp->scno);
/* Clear qual_flg to distinguish valid syscall from printargs */
tcp->qual_flg = 0;
} else {
struct sysent_buf *s = xcalloc(1, sizeof(*s));

2
tests/.gitignore vendored
View File

@ -84,6 +84,8 @@ fflush
file_handle
file_ioctl
filter-unavailable
filtering_fd
filtering_path
finit_module
flock
fork-f

View File

@ -109,6 +109,8 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
execve-v \
execveat-v \
filter-unavailable \
filtering_fd \
filtering_path \
fork-f \
fsync-y \
getpid \
@ -303,7 +305,12 @@ MISC_TESTS = \
detach-sleeping.test \
detach-stopped.test \
filter-unavailable.test \
filtering_action-syntax.test \
filtering_expression-empty.test \
filtering_expression-syntax.test \
filtering_fd-syntax.test \
filtering_fd.test \
filtering_path.test \
filtering_syscall-syntax.test \
fflush.test \
get_regs.test \
@ -384,6 +391,7 @@ EXTRA_DIST = \
ipc.sh \
ksysent.sed \
lstatx.c \
mmap_name.sh \
match.awk \
net.expected \
netlink_sock_diag-v.sh \
@ -420,6 +428,7 @@ EXTRA_DIST = \
strace.supp \
sun_path.expected \
syntax.sh \
tampering-syntax.sh \
trace_fstat.in \
trace_fstatfs.in \
trace_lstat.in \

View File

@ -0,0 +1,95 @@
#!/bin/sh
#
# Check common action syntax.
#
# Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
# 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=.}/syntax.sh"
for arg in '' ' ' ' '\
not \
not_action \
notAction \
notaction \
; do
check_e "invalid filter action '$arg'" -e "$arg=open"
done
for arg in '' ' ' ' '; do
check_e "invalid filter action 'not_action'" -e "$arg"not_action=
check_e "invalid filter action 'not_action'" -e "$arg"not_action\(\)
done
for arg in not_action \
notAction \
notaction \
and "&&" \
or "||" \
; do
check_e "invalid filter action '$arg'" -e "$arg(syscall open)"
done
check_e "invalid arguments position ';':0" -e \;
check_e "invalid arguments position 'not(syscall open;)':16" -e "not(syscall open;)"
check_e "invalid arguments position 'trace((syscall open;arg))':19" \
-e "trace((syscall open;arg))"
check_e "missing ')' in 'not (syscall open'" -e "not (syscall open"
check_e "missing ')' in 'not (syscall open\)'" -e "not (syscall open\)"
check_e "missing ')' in 'trace(syscall open'" -e "trace(syscall open"
check_e "missing ')' in 'trace(syscall open;args'" -e "trace(syscall open;args"
check_e "missing ')' in 'trace(syscall open;args\)'" -e "trace(syscall open;args\)"
check_e "unexpected ')' at '! syscall open)':14" -e "! syscall open)"
check_e "unexpected ')' at '!(syscall open))':15" -e "!(syscall open))"
check_e "unexpected ')' at 'trace(syscall open))':19" -e "trace(syscall open))"
check_e "unexpected ';' at 'trace(syscall open;arg);':23" -e "trace(syscall open;arg);"
check_e "unexpected '&' at 'trace(syscall open)&':19" -e "trace(syscall open)&"
check_e "unexpected '&' at 'trace(syscall open) &':20" -e "trace(syscall open) &"
for arg in trace \
read \
write \
raw \
abbrev \
verbose \
; do
cat > "$EXP" << __EOF__
$STRACE_EXE: $arg action takes no arguments, ignored arguments 'no_args'
$STRACE_EXE: must have PROG [ARGS] or -p PID
Try '$STRACE_EXE -h' for more information.
__EOF__
check_exit_status_and_stderr -e $arg=none:no_args
check_exit_status_and_stderr -e "$arg(syscall none;no_args)"
done
cat > "$EXP" << __EOF__
$STRACE_EXE: signal action takes no arguments, ignored arguments 'no_args'
$STRACE_EXE: must have PROG [ARGS] or -p PID
Try '$STRACE_EXE -h' for more information.
__EOF__
check_exit_status_and_stderr -e signal=none:no_args

View File

@ -0,0 +1,47 @@
#!/bin/sh
#
# Check simple filter-based expressions.
#
# Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
# 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=.}/syntax.sh"
echo "+++ exited with 0 +++" > "$EXP"
for arg in "syscall none" \
"not syscall all" \
"syscall all and not syscall all" \
"syscall all and not syscall all or syscall none" \
"(syscall all and not syscall all) or syscall none" \
"not syscall all and (syscall all or syscall none)" \
"not syscall all and (syscall all or syscall chdir)" \
"syscall none and not syscall all" \
"syscall none or not syscall all" \
; do
run_strace -e "$arg" true
match_diff "$LOG" "$EXP"
run_strace -e "trace($arg)" true
match_diff "$LOG" "$EXP"
done

View File

@ -0,0 +1,77 @@
#!/bin/sh
#
# Check filter-based expression syntax.
#
# Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
# 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=.}/syntax.sh"
check_expression()
{
for action in trace \
inject \
fault \
read \
write \
raw \
abbrev \
verbose \
; do
check_e "$1" -e "$action($2)"
done
}
check_expression "invalid operator at 'or (syscall open)':1" "or (syscall open)"
check_expression "invalid operator at '||(syscall open)':1" "||(syscall open)"
check_expression "invalid operator at 'and (syscall open)':2" "and (syscall open)"
check_expression "invalid operator at '&&(syscall open)':1" "&&(syscall open)"
check_expression "invalid operator at 'syscall open not syscall close':12" \
"syscall open not syscall close"
check_expression "invalid operator at 'syscall open ! syscall close':10" \
"syscall open ! syscall close"
for arg in notsyscall \
not_syscall \
notSyscall \
invalid \
; do
check_expression "invalid filter '$arg'" "$arg open"
check_expression "invalid filter '$arg'" "syscall open and $arg open"
check_expression "invalid filter '$arg'" "syscall open or $arg open"
done
check_expression "invalid filter 'not_'" "not_ syscall open"
for arg in "syscall " \
"(syscall)" \
"syscall open or" \
"syscall open or not" \
"syscall open or syscall" \
; do
check_expression "unfinished filter expression '$arg'" "$arg"
done
check_expression "stack overflow (expression is too complex)" \
"(((((((((((((((((((((((((((((((((syscall open)))))))))))))))))))))))))))))))))"

View File

@ -30,6 +30,13 @@
. "${srcdir=.}/syntax.sh"
check_fd_new()
{
[ -z "$2" ] || check_e "invalid descriptor '$1'" -e "trace(fd $2)"
[ -z "$2" ] || check_e "invalid descriptor '$1'" -e "fd $2"
[ -z "$2" ] || check_e "invalid descriptor '$1'" -e"fd $2"
}
check_fd_qualify()
{
check_e "invalid descriptor '$1'" -e"read=$2"
@ -39,6 +46,8 @@ check_fd_qualify()
}
for arg in '' , ,, ,,, ; do
check_fd_new "$arg" "$arg"
check_fd_new "!" "!$arg"
check_fd_qualify "$arg" "$arg"
check_fd_qualify "!$arg" "!$arg"
done
@ -48,6 +57,10 @@ for arg in -1 -42 \
2147483648 \
4294967296 \
; do
check_fd_new "$arg" "$arg"
check_fd_new "$arg" "1,$arg"
check_fd_new "$arg" "$arg,1"
check_fd_new "!$arg" "!$arg"
check_fd_qualify "$arg" "$arg"
check_fd_qualify "$arg" "1,$arg"
check_fd_qualify "$arg" "$arg,1"
@ -57,4 +70,10 @@ done
for arg in ! all none; do
check_fd_qualify "$arg" "1,$arg"
check_fd_qualify "!$arg" "1,!$arg"
check_fd_new "$arg" "1,$arg"
check_fd_new "!$arg" "1,!$arg"
done
for arg in 1 2 all none; do
check_fd_new "!$arg" "!$arg"
done

204
tests/filtering_fd.c Normal file
View File

@ -0,0 +1,204 @@
/*
* Check decoding of non-standard fd filters
*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "tests.h"
#include <asm/unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define FD_TRACED_DEFAULT 5
#ifdef __NR_dup2
void
test_dup2(int fd_base)
{
int rc = syscall(__NR_dup2, fd_base, -1);
printf("dup2(%d, -1) = %s\n", fd_base, sprintrc(rc));
syscall(__NR_dup2, fd_base + 1, -1);
rc = syscall(__NR_dup2, -1, fd_base);
printf("dup2(-1, %d) = %s\n", fd_base, sprintrc(rc));
syscall(__NR_dup2, -1, fd_base + 1);
}
#else
void
test_dup2(int fd_base)
{
}
#endif
#ifdef __NR_linkat
void
test_linkat(int fd_base)
{
int rc = syscall(__NR_linkat, fd_base, "old", -1, "new", 0);
printf("linkat(%d, \"old\", -1, \"new\", 0) = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_linkat, fd_base + 1, "old", -1, "new", 0);
rc = syscall(__NR_linkat, -1, "old", fd_base, "new", 0);
printf("linkat(-1, \"old\", %d, \"new\", 0) = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_linkat, "old", fd_base + 1, "new", 0);
}
#else
void
test_linkat(int fd_base)
{
}
#endif
#ifdef __NR_symlinkat
void
test_symlinkat(int fd_base)
{
int rc = syscall(__NR_symlinkat, "new", fd_base, "old");
printf("symlinkat(\"new\", %d, \"old\") = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_symlinkat, "new", fd_base + 1, "old");
}
#else
void
test_symlinkat(int fd_base)
{
}
#endif
#ifdef __NR_epoll_ctl
# include <sys/epoll.h>
void
test_epoll(int fd_base)
{
int rc = syscall(__NR_epoll_ctl, -1, EPOLL_CTL_ADD, fd_base, NULL);
printf("epoll_ctl(-1, EPOLL_CTL_ADD, %d, NULL) = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_epoll_ctl, -1, EPOLL_CTL_ADD, fd_base + 1, NULL);
}
#else
void
test_epoll(int fd_base)
{
}
#endif
#if defined HAVE_SYS_FANOTIFY_H && defined HAVE_FANOTIFY_MARK && \
defined __NR_fanotify_mark
#include <sys/fanotify.h>
void
test_fanotify_mark(int fd_base)
{
int rc = fanotify_mark(-1, 0, 0, fd_base, ".");
printf("fanotify_mark(-1, 0, 0, %d, \".\") = %s\n",
fd_base, sprintrc(rc));
fanotify_mark(-1, 0, 0, fd_base + 1, ".");
}
#else
void
test_fanotify_mark(int fd_base)
{
}
#endif
#if defined __NR_select || defined __NR__newselect
# include <sys/select.h>
void
test_select(int fd_base)
{
int rc;
fd_set positive_set, negative_set;
FD_ZERO(&positive_set);
FD_SET(fd_base, &positive_set);
FD_ZERO(&negative_set);
FD_SET(fd_base + 1, &negative_set);
# ifndef __NR__newselect
rc = syscall(__NR_select, fd_base + 1, &positive_set, NULL, NULL, NULL);
printf("select(%d, [%d], NULL, NULL, NULL) = %s\n",
fd_base + 1, fd_base, sprintrc(rc));
syscall(__NR_select, fd_base + 2, &negative_set, NULL, NULL,NULL);
# else
rc = syscall(__NR__newselect, fd_base + 1, &positive_set, NULL, NULL,
NULL);
printf("_newselect(%d, [%d], NULL, NULL, NULL) = %s\n",
fd_base + 1, fd_base, sprintrc(rc));
syscall(__NR__newselect, fd_base + 2, &negative_set, NULL, NULL, NULL);
# endif
}
#else
void
test_select(int fd_base)
{
}
#endif
#ifdef __NR_poll
# include <poll.h>
void
test_poll(int fd_base)
{
struct pollfd positive_fds = {.fd = fd_base, .events = POLLIN};
struct pollfd negative_fds = {.fd = fd_base + 1, .events = POLLIN};
syscall(__NR_poll, &positive_fds, 1, 1);
printf("poll([{fd=%d, events=POLLIN}], 1, 1) = 1 "
"([{fd=%d, revents=POLLNVAL}])\n", fd_base, fd_base);
syscall(__NR_poll, &negative_fds, 1, 1);
}
#else
void
test_poll(int fd_base)
{
}
#endif
int
main(int argc, char **argv)
{
const char *const name = argc > 1 ? argv[1] : "mmap";
long tmp_fd_base = argc > 2 ? strtol(argv[2], NULL, 10) :
FD_TRACED_DEFAULT;
int fd_base = (tmp_fd_base > 0 && tmp_fd_base < 1023) ?
(int) tmp_fd_base : FD_TRACED_DEFAULT;
test_dup2(fd_base);
test_linkat(fd_base);
mmap(NULL, 0, PROT_NONE, MAP_FILE, fd_base, 0);
printf("%s(NULL, 0, PROT_NONE, MAP_FILE, %d, 0) = -1 EBADF (%m)\n",
name, fd_base);
test_symlinkat(fd_base);
test_epoll(fd_base);
test_fanotify_mark(fd_base);
test_select(fd_base);
test_poll(fd_base);
puts("+++ exited with 0 +++");
return 0;
}

17
tests/filtering_fd.test Executable file
View File

@ -0,0 +1,17 @@
#!/bin/sh
# Check fd filtering
. "${srcdir=.}/mmap_name.sh"
fd_base=5
get_mmap_name "&& fd $fd_base"
for arg in "trace(fd $fd_base)" \
"trace(syscall %desc,%network and fd $fd_base)" \
; do
run_prog "../$NAME" "$mmap" "$fd_base" > /dev/null
run_strace -a12 -e "$arg" $args > "$EXP"
match_diff "$LOG" "$EXP"
done

285
tests/filtering_path.c Normal file
View File

@ -0,0 +1,285 @@
/*
* Check decoding of non-standard path filters
*
* Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
* 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.
*/
#include "tests.h"
#include <asm/unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#define FD_TRACED_DEFAULT 5
#ifdef __NR_dup2
void
test_dup2(int fd_base)
{
int rc = syscall(__NR_dup2, fd_base, -1);
printf("dup2(%d, -1) = %s\n", fd_base, sprintrc(rc));
syscall(__NR_dup2, fd_base + 1, -1);
rc = syscall(__NR_dup2, -1, fd_base);
printf("dup2(-1, %d) = %s\n", fd_base, sprintrc(rc));
syscall(__NR_dup2, -1, fd_base + 1);
}
#else
void
test_dup2(int fd_base)
{
}
#endif
#ifdef __NR_linkat
void
test_linkat(const char *path, int fd_base)
{
int rc = syscall(__NR_linkat, fd_base, "old", -1, "new", 0);
printf("linkat(%d, \"old\", -1, \"new\", 0) = %s\n",
fd_base, sprintrc(rc));
rc = syscall(__NR_linkat, -1, "old", fd_base, "new", 0);
printf("linkat(-1, \"old\", %d, \"new\", 0) = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_linkat, fd_base + 1, "old", fd_base + 1, "new", 0);
rc = syscall(__NR_linkat, -1, path, -1, "new", 0);
printf("linkat(-1, \"%s\", -1, \"new\", 0) = %s\n", path, sprintrc(rc));
rc = syscall(__NR_linkat, -1, "old", -1, path, 0);
printf("linkat(-1, \"old\", -1, \"%s\", 0) = %s\n", path, sprintrc(rc));
syscall(__NR_linkat, -1, "old", -1, "new", 0);
}
#else
void
test_linkat(const char *path, int fd_base)
{
}
#endif
#ifdef __NR_symlinkat
void
test_symlinkat(const char *path, int fd_base)
{
int rc = syscall(__NR_symlinkat, "new", fd_base, "old");
printf("symlinkat(\"new\", %d, \"old\") = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_symlinkat, "new", fd_base + 1, "old");
rc = syscall(__NR_symlinkat, "new", -1, path);
printf("symlinkat(\"new\", -1, \"%s\") = %s\n", path, sprintrc(rc));
syscall(__NR_symlinkat, "new", -1, "old");
syscall(__NR_symlinkat, path, -1, "old");
}
#else
void
test_symlinkat(const char *path, int fd_base)
{
}
#endif
#ifdef __NR_epoll_ctl
# include <sys/epoll.h>
void
test_epoll(int fd_base)
{
int rc = syscall(__NR_epoll_ctl, -1, EPOLL_CTL_ADD, fd_base, NULL);
printf("epoll_ctl(-1, EPOLL_CTL_ADD, %d, NULL) = %s\n",
fd_base, sprintrc(rc));
syscall(__NR_epoll_ctl, -1, EPOLL_CTL_ADD, fd_base + 1, NULL);
}
#else
void
test_epoll(int fd_base)
{
}
#endif
#if defined HAVE_SYS_FANOTIFY_H && defined HAVE_FANOTIFY_MARK && \
defined __NR_fanotify_mark
# include <sys/fanotify.h>
void
test_fanotify_mark(const char *path, int fd_base)
{
int rc = fanotify_mark(-1, 0, 0, fd_base, ".");
printf("fanotify_mark(-1, 0, 0, %d, \".\") = %s\n",
fd_base, sprintrc(rc));
fanotify_mark(-1, 0, 0, fd_base + 1, ".");
rc = fanotify_mark(-1, 0, 0, -1, path);
printf("fanotify_mark(-1, 0, 0, FAN_NOFD, \"%s\") = %s\n",
path, sprintrc(rc));
fanotify_mark(-1, 0, 0, -1, "not_file");
}
#else
void
test_fanotify_mark(const char *path, int fd_base)
{
}
#endif
#if defined __NR_select || defined __NR__newselect
# include <sys/select.h>
void
test_select(int fd_base)
{
fd_set positive_set, negative_set;
FD_ZERO(&positive_set);
FD_SET(fd_base, &positive_set);
FD_ZERO(&negative_set);
FD_SET(fd_base + 1, &negative_set);
# ifndef __NR__newselect
syscall(__NR_select, fd_base + 1, &positive_set, NULL, NULL, NULL);
printf("select(%d, [%d], NULL, NULL, NULL) = 1 (in [%d])\n",
fd_base + 1, fd_base, fd_base);
syscall(__NR_select, fd_base + 2, &negative_set, NULL, NULL,NULL);
# else
syscall(__NR__newselect, fd_base + 1, &positive_set, NULL, NULL,
NULL);
printf("_newselect(%d, [%d], NULL, NULL, NULL) = 1 (in [%d])\n",
fd_base + 1, fd_base, fd_base);
syscall(__NR__newselect, fd_base + 2, &negative_set, NULL, NULL, NULL);
# endif
}
#else
void
test_select(int fd_base)
{
}
#endif
#ifdef __NR_poll
# include <poll.h>
void
test_poll(int fd_base)
{
struct pollfd positive_fds = {.fd = fd_base, .events = POLLIN};
struct pollfd negative_fds = {.fd = fd_base + 1, .events = POLLIN};
syscall(__NR_poll, &positive_fds, 1, 1);
printf("poll([{fd=%d, events=POLLIN}], 1, 1) = 1 "
"([{fd=%d, revents=POLLIN}])\n", fd_base, fd_base);
syscall(__NR_poll, &negative_fds, 1, 1);
}
#else
void
test_poll(int fd_base)
{
}
#endif
#ifdef __NR_faccessat
void
test_faccessat(const char *path, int fd_base)
{
int rc = syscall(__NR_faccessat, -1, path, 0, 0);
printf("faccessat(-1, \"%s\", F_OK) = %s\n", path, sprintrc(rc));
syscall(__NR_faccessat, -1, "not_file", 0, 0);
rc = syscall(__NR_faccessat, fd_base, "not_file", 0, 0);
printf("faccessat(%d, \"not_file\", F_OK) = %s\n", fd_base, sprintrc(rc));
syscall(__NR_faccessat, fd_base + 1, "not_file", 0, 0);
}
#else
void
test_faccessat(const char *path, int fd_base)
{
}
#endif
#ifdef __NR_link
void
test_link(const char *path, int fd_base)
{
int rc;
rc = syscall(__NR_link, path, NULL);
printf("link(\"%s\", NULL) = %s\n",
path, sprintrc(rc));
rc = syscall(__NR_link, NULL, path);
printf("link(NULL, \"%s\") = %s\n",
path, sprintrc(rc));
syscall(__NR_link, "old", "new");
}
#else
void
test_link(const char *path, int fd_base)
{
}
#endif
static const char *path = "path_trace test.sample";
#ifdef __NR_open
int
main(int argc, char **argv)
{
const char *const name = argc > 1 ? argv[1] : "mmap";
long tmp_fd_base = argc > 2 ? strtol(argv[2], NULL, 10) :
FD_TRACED_DEFAULT;
int fd_base = (tmp_fd_base > 0 && tmp_fd_base < 1023) ?
(int) tmp_fd_base : FD_TRACED_DEFAULT;
int tmp_fd;
assert((tmp_fd = syscall(__NR_open, path, O_RDONLY | O_CREAT, 0600)) >= 0);
printf("open(\"%s\", O_RDONLY|O_CREAT, 0600) = %d\n", path, tmp_fd);
assert(dup2(tmp_fd, fd_base) == fd_base);
printf("dup2(%d, %d) = %d\n", tmp_fd, fd_base, fd_base);
assert(close(tmp_fd) == 0);
printf("close(%d) = 0\n", tmp_fd);
test_dup2(fd_base);
test_linkat(path, fd_base);
test_symlinkat(path, fd_base);
test_epoll(fd_base);
test_fanotify_mark(path, fd_base);
test_select(fd_base);
test_poll(fd_base);
mmap(NULL, 0, PROT_NONE, MAP_FILE, fd_base, 0);
printf("%s(NULL, 0, PROT_NONE, MAP_FILE, %d, 0) = -1 "
"EINVAL (Invalid argument)\n", name, fd_base);
test_faccessat(path, fd_base);
test_link(path, fd_base);
test_linkat(path, fd_base);
puts("+++ exited with 0 +++");
return 0;
}
#else
SKIP_MAIN_UNDEFINED("__NR_open")
#endif

18
tests/filtering_path.test Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
# Check fd filtering
. "${srcdir=.}/mmap_name.sh"
fd_base=5
path='path_trace\ test.sample'
get_mmap_name "&& path $path"
for arg in "trace(path $path)" \
"trace(syscall %file,%desc,%network and path $path)" \
; do
run_prog "../$NAME" "$mmap" "$fd_base" > /dev/null
run_strace -a9 -e "$arg" $args > "$EXP"
match_diff "$LOG" "$EXP"
done

View File

@ -70,9 +70,23 @@ check_syscall()
check_e "invalid system call '$1'" -e fault="$2:when=4"
}
# Check different variations of new syntax.
check_syscall_new()
{
if [ -n "$2" ]; then
check_e "invalid system call '$1'" -e "syscall $2"
check_e "invalid system call '$1'" -e "(syscall $2)"
check_e "invalid system call '$1'" -e "((syscall $2))"
check_e "invalid system call '$1'" -e "((syscall none) or syscall $2)"
check_e "invalid system call '$1'" -e "((syscall all) and syscall $2)"
check_e "invalid system call '$1'" -e "trace(syscall $2)"
fi
}
for arg in '' , ,, ,,, ; do
check_syscall "$arg" "$arg"
check_syscall "!$arg" "!$arg"
check_syscall_new "$arg" "$arg"
done
for arg in -1 -2 -3 -4 -5 \
@ -89,10 +103,21 @@ for arg in -1 -2 -3 -4 -5 \
check_syscall "$arg" "$arg"
check_syscall "$arg" "!$arg"
check_syscall "$arg" "1,$arg"
check_syscall_new "$arg" "$arg"
done
for arg in '!chdir' none all; do
check_syscall "$arg" "1,$arg"
check_syscall_new "$arg" "1,$arg"
done
for arg in desc \
file \
memory \
process \
network \
; do
check_syscall_new "$arg" "$arg"
done
# invalid syscall, multiple syscalls
@ -112,6 +137,8 @@ for arg in %desc \
check_syscall nonsense "!$arg,nonsense"
check_syscall nonsense "nonsense,$arg"
check_syscall nonsense "!nonsense,$arg"
check_syscall_new nonsense "$arg,nonsense"
check_syscall_new nonsense "nonsense,$arg"
done
check_e_using_grep 'regcomp: \+id: [[:alpha:]].+' -e trace='/+id'
@ -119,3 +146,8 @@ check_e_using_grep 'regcomp: \*id: [[:alpha:]].+' -e trace='/*id'
check_e_using_grep 'regcomp: \{id: [[:alpha:]].+' -e trace='/{id'
check_e_using_grep 'regcomp: \(id: [[:alpha:]].+' -e trace='/(id'
check_e_using_grep 'regcomp: \[id: [[:alpha:]].+' -e trace='/[id'
check_e_using_grep 'regcomp: \+id: [[:alpha:]].+' -e 'trace(syscall /+id)'
check_e_using_grep 'regcomp: \*id: [[:alpha:]].+' -e 'trace(syscall /*id)'
check_e_using_grep 'regcomp: \{id: [[:alpha:]].+' -e 'trace(syscall /{id)'
check_e_using_grep 'regcomp: \(id: [[:alpha:]].+' -e 'trace(syscall /\(id)'
check_e_using_grep 'regcomp: \[id: [[:alpha:]].+' -e 'trace(syscall /[id)'

View File

@ -29,30 +29,16 @@
# (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"
. "${srcdir=.}/mmap_name.sh"
check_prog grep
check_prog sed
run_prog > /dev/null
xlat_opt=""
[ "$#" -gt 0 ] && xlat_opt="-X$1"
align=20
[ "$#" -gt 1 ] && align="$2"
syscall=
for n in mmap mmap2; do
$STRACE -e$n -h > /dev/null && syscall=$syscall,$n
done
run_strace -e$syscall $args > /dev/null
if grep '^mmap(NULL, 0, \(0 /* PROT_NONE */\|0\|PROT_NONE\),' < "$LOG" > /dev/null; then
mmap=mmap
elif grep '^mmap2(NULL, 0, \(0 \/\* PROT_NONE \*\/\|0\|PROT_NONE\),' < "$LOG" > /dev/null; then
mmap=mmap2
else
dump_log_and_fail_with "mmap/mmap2 not found in $STRACE $args output"
fi
get_mmap_name ""
syscall=$mmap,madvise,mlockall,mprotect,mremap,msync,munmap

51
tests/mmap_name.sh Normal file
View File

@ -0,0 +1,51 @@
#!/bin/sh
#
# Define mmap testing helper function.
#
# Copyright (c) 2017 Nikolay Marchuk <marchuk.nikolay.a@gmail.com>
# 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"
get_mmap_name()
{
check_prog grep
run_prog > /dev/null
local filter="$1"; shift
local syscall=
for n in mmap mmap2; do
$STRACE -e$n -h > /dev/null && syscall=$syscall,$n
done
run_strace -e "trace(syscall $syscall $filter)" $args > /dev/null
if grep '^mmap(NULL, 0, \(0 /* PROT_NONE */\|0\|PROT_NONE\),' < "$LOG" > /dev/null; then
mmap=mmap
elif grep '^mmap2(NULL, 0, \(0 \/\* PROT_NONE \*\/\|0\|PROT_NONE\),' < "$LOG" > /dev/null; then
mmap=mmap2
else
dump_log_and_fail_with "mmap/mmap2 not found in $STRACE $args output"
fi
}

View File

@ -43,12 +43,23 @@ test_with()
}
test_with -eexit,exit_group -efault=exit_group:error=ENOSYS ../answer
test_with -e "trace(syscall exit,exit_group)" \
-e "fault(syscall exit_group;error=ENOSYS)" ../answer
test_with -eexit,exit_group -efault=exit_group:error=ENOSYS \
-efault=\!process:error=1 ../answer
test_with -e "trace(syscall exit,exit_group)" \
-e "fault(syscall exit_group;error=ENOSYS)" \
-e "fault(!syscall %process;error=1)" ../answer
test_with -eexit,exit_group -efault=all:error=ENOSYS \
-efault=exit:error=1:when=2+ ../answer
test_with -e "trace(syscall exit,exit_group)" \
-e "fault(syscall all;error=ENOSYS)" \
-e "fault(syscall exit;error=1;when=2+)" ../answer
test_with -eexit,exit_group -efault=exit_group:error=ENOSYS \
-efault=\!%desc,%file,%memory,%process,%signal,%network,%ipc:error=1 ../answer
test_with -e "trace(syscall exit,exit_group)" \
-e "fault(syscall exit_group;error=ENOSYS)" \
-e "fault(!syscall %desc,%file,%memory,%process,%signal,%network,%ipc;error=1)" ../answer

View File

@ -3,6 +3,7 @@
# Check -e fault= syntax.
#
# Copyright (c) 2016-2018 Dmitry V. Levin <ldv@altlinux.org>
# Copyright (c) 2017 The strace developers.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -27,70 +28,49 @@
# (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"
. "${srcdir=.}/tampering-syntax.sh"
#
# F
# F+
# F+S
fail_with()
{
dump_log_and_fail_with \
"strace -e fault=$* failed to handle an argument error properly"
}
for arg in 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 \
chdir:retval=0 \
chdir:signal=1 \
chdir:error=1:error=2 \
counter=7
for arg in 42 \
invalid \
error= \
error=invalid_error_name \
error=-1 \
error=-2 \
error=3+ \
error=4096 \
; do
$STRACE -e fault="$arg" true 2> "$LOG" &&
fail_with "$arg"
LC_ALL=C grep -F 'invalid fault argument' < "$LOG" > /dev/null ||
fail_with "$arg"
check_tampering_syntax fault chdir "$arg"
check_tampering_syntax fault chdir "$arg" "when=$counter"
counter=$((counter + 1))
done
for arg in when= \
when=0 \
when=-1 \
when=-2+ \
when=-3+0 \
when=4- \
when=5+- \
when=6++ \
when=7+0 \
when=8+-1 \
when=9+1+ \
when=65536 \
when=1+65536 \
; do
check_tampering_syntax fault chdir "$arg"
check_tampering_syntax fault chdir "$arg" "error=$counter"
counter=$((counter + 1))
done
check_tampering_syntax fault chdir "retval=0"
check_tampering_syntax fault chdir "signal=1"
check_tampering_syntax fault chdir "error=1" "error=2"
exit 0

View File

@ -48,29 +48,46 @@ check_fault_injection()
procs=$1; shift
extra="$*"
local when=
local when_qualify=
local when_new=
if [ -z "$first$step" ]; then
first=1
step=1
else
case "$step" in
'') when=":when=$first"; step=0 ;;
+) when=":when=$first+"; step=1 ;;
*) when=":when=$first+$step" ;;
'')
when_qualify=":when=$first"
when_new=";when=$first"
step=0
;;
+)
when_qualify=":when=$first+"
when_new=";when=$first+"
step=1
;;
*)
when_qualify=":when=$first+$step"
when_new=";when=$first+$step"
;;
esac
fi
local error=
local error_qualify=
local error_new=
local raw=reg
set --
case "$err" in
'') ;;
[123456789]*)
error=":error=$err"
error_qualify=":error=$err"
error_new=";error=$err"
raw=raw
set -- -e raw=all
;;
*) error=":error=$err" ;;
*)
error_qualify=":error=$err"
error_new=";error=$err"
;;
esac
outexp="$NAME.out.exp"
@ -79,7 +96,21 @@ check_fault_injection()
outpid="$NAME.pid"
run_strace -a11 -ff -e trace=$trace \
"$@" -e fault=$fault$when$error $extra \
"$@" -e fault=$fault$when_qualify$error_qualify $extra \
../$NAME $raw "$err" "$first" "$step" $N \
"$procs" "$outexp" "$outgot" "$outout" "$outpid"
for i in $(seq 0 $((procs - 1)) )
do
pid=$(cat "$outpid.$i")
match_diff "$outout.$i" "$LOG.$pid"
match_diff "$outexp.$i" "$outgot.$i"
done
# New syntax
run_strace -a11 -ff -e "trace(syscall $trace)" \
"$@" -e "fault(syscall $fault$when_new$error_new)" $extra \
../$NAME $raw "$err" "$first" "$step" $N \
"$procs" "$outexp" "$outgot" "$outout" "$outpid"
@ -93,7 +124,7 @@ check_fault_injection()
}
for err in '' ENOSYS 22 einval; do
for fault in writev desc,51; do
for fault in writev %desc,51; do
check_fault_injection \
writev $fault "$err" '' '' 1 -efault=chdir
check_fault_injection \

View File

@ -6,3 +6,8 @@
run_strace -a12 -echdir,exit_group -einject=chdir:error=ENOENT:signal=USR1 \
"../$NAME"
match_diff
run_strace -a12 -e "syscall chdir,exit_group" \
-e "inject(syscall chdir;error=ENOENT;signal=USR1)" \
"../$NAME"
match_diff

View File

@ -14,6 +14,11 @@ check_injection()
run_strace -a12 -e$syscall -einject="$syscall:retval=$rval" "$@" \
../qual_inject-retval "$rval" > "$EXP"
match_diff "$LOG" "$EXP"
run_strace -a12 -e "syscall $syscall" \
-e "inject(syscall $syscall;retval=$rval)" "$@" \
../qual_inject-retval "$rval" > "$EXP"
match_diff "$LOG" "$EXP"
}
check_injection 0

View File

@ -6,3 +6,8 @@
run_strace -a12 -echdir,exit_group -einject=chdir:signal=USR1 \
../$NAME
match_diff
run_strace -a12 -e "syscall chdir,exit_group" \
-e "inject(syscall chdir;signal=USR1)" \
../$NAME
match_diff

View File

@ -3,6 +3,7 @@
# Check -e inject= syntax.
#
# Copyright (c) 2016-2018 Dmitry V. Levin <ldv@altlinux.org>
# Copyright (c) 2017 The strace developers.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -27,86 +28,62 @@
# (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"
. "${srcdir=.}/tampering-syntax.sh"
#
# F
# F+
# F+S
check_tampering_syntax inject 42
check_tampering_syntax inject chdir
fail_with()
{
dump_log_and_fail_with \
"strace -e inject=$* failed to handle an argument error properly"
}
for arg in 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 \
chdir:retval=a \
chdir:retval=0b \
chdir:retval=0x10000000000000000 \
chdir:signal=0 \
chdir:signal=129 \
chdir:signal=1:signal=2 \
chdir:signal=1:retval=0:signal=2 \
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 \
chdir:delay_enter=-1 \
chdir:delay_exit=-2 \
chdir:delay_enter=1:delay_enter=2 \
chdir:delay_exit=3:delay_exit=4 \
chdir:delay_enter=5:delay_exit=6:delay_enter=7 \
chdir:delay_exit=8:delay_enter=9:delay_exit=10 \
counter=7
for arg in 42 \
invalid \
error= \
error=invalid_error_name \
error=-1 \
error=-2 \
error=3+ \
error=4096 \
; 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"
check_tampering_syntax inject chdir "$arg"
check_tampering_syntax inject chdir "$arg" "when=$counter"
counter=$((counter + 1))
done
for arg in when= \
when=0 \
when=-1 \
when=-2+ \
when=-3+0 \
when=4- \
when=5+- \
when=6++ \
when=7+0 \
when=8+-1 \
when=9+1+ \
when=65536 \
when=1+65536 \
; do
check_tampering_syntax inject chdir "$arg"
check_tampering_syntax inject chdir "$arg" "error=$counter"
counter=$((counter + 1))
done
check_tampering_syntax inject chdir retval=a
check_tampering_syntax inject chdir retval=0b
check_tampering_syntax inject chdir retval=0x10000000000000000
check_tampering_syntax inject chdir signal=0
check_tampering_syntax inject chdir signal=129
check_tampering_syntax inject chdir signal=1 signal=2
check_tampering_syntax inject chdir signal=1 retval=0 signal=2
check_tampering_syntax inject chdir retval=0 retval=1
check_tampering_syntax inject chdir error=1 error=2
check_tampering_syntax inject chdir retval=0 error=1
check_tampering_syntax inject chdir error=1 retval=0
check_tampering_syntax inject chdir retval=0 signal=1 error=1
check_tampering_syntax inject chdir delay_enter=-1
check_tampering_syntax inject chdir delay_exit=-2
check_tampering_syntax inject chdir delay_enter=1 delay_enter=2
check_tampering_syntax inject chdir delay_exit=3 delay_exit=4
check_tampering_syntax inject chdir delay_enter=5 delay_exit=6 delay_enter=7
check_tampering_syntax inject chdir delay_exit=8 delay_enter=9 delay_exit=10
exit 0

64
tests/tampering-syntax.sh Normal file
View File

@ -0,0 +1,64 @@
#!/bin/sh
#
# Define inject/fault syntax testing primitives.
#
# Copyright (c) 2016 Dmitry V. Levin <ldv@altlinux.org>
# Copyright (c) 2016-2017 The strace developers.
# 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=.}/syntax.sh"
check_tampering_syntax()
{
local action syscall argument_qualify argument_new
action=$1; shift
syscall=$1; shift
if [ -n "$1" ]; then
argument_qualify="$1"
argument_new="$1"
shift
else
check_e "invalid $action argument ''" \
-e "$action=$syscall" true
check_e "invalid $action argument ''" \
-e "$action(syscall $syscall)" true
return
fi
for arg in "$@"
do
[ -z "$arg" ] && break
argument_qualify="$argument_qualify:$arg"
argument_new="$argument_new;$arg"
shift
done
check_e "invalid $action argument '$argument_qualify'" \
-e "$action=$syscall:$argument_qualify" \
true
check_e "invalid $action argument '$argument_new'" \
-e "$action(syscall $syscall;$argument_new)" \
true
}

View File

@ -319,3 +319,9 @@ unwind_tcb_capture(struct tcb *tcp)
tcp->unwind_queue);
}
}
void
apply_stacktrace(struct tcb *tcp, void *_priv_data)
{
tcp->qual_flg |= QUAL_STACKTRACE;
}