Compare commits

...

17 Commits

Author SHA1 Message Date
6ab67a54de travis: use the previous image version ...
... in attempt to figure out why it no longer works.
2017-12-23 01:02:34 +00:00
Nikolay Marchuk
cb1f64ef3a Update NEWS 2017-12-21 13:12:36 +00:00
Nikolay Marchuk
af319a087d Add description of new filtering syntax
* strace.1.in (.SS Filtering): Add description of new filtering syntax.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
85aac65364 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
fb0ff98257 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
7ac26b2274 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
077bac4a1f 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
37f33a4686 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
302f2d16d0 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
2de045936f 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
7d93fbe00e 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
70a8481f6d 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
59b2dbbdbd 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
dfc18a8a98 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
f00b9df5cb tests: improve syscall set syntax testing
* tests/filtering_syscall-syntax.test: New file.
* tests/options-syntax.test: Move syscall set syntax testing to
filtering_syscall-syntax.test.
* tests/qual_fault-syntax.test: Likewise.
* tests/qual_inject-syntax.test: Likewise.
* tests/Makefile.am (MISC_TESTS): Add filtering_syscall-syntax.test.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
ae02a6cbb8 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.
2017-12-21 13:12:36 +00:00
Nikolay Marchuk
4c2d68c14b 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.
2017-12-21 13:12:36 +00:00
40 changed files with 3245 additions and 514 deletions

View File

@ -2,6 +2,8 @@ language: c
dist: trusty
group: deprecated-2017Q4
before_install: ./travis-install.sh
script: ./travis-build.sh

View File

@ -88,6 +88,7 @@ strace_SOURCES = \
affinity.c \
aio.c \
alpha.c \
basic_actions.c \
basic_filters.c \
bind.c \
bjm.c \
@ -136,7 +137,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.
Old syntax is still supported.
* Improvements
* Implemented decoding of netlink descriptor attributes as file descriptors.
* Implemented decoding of hugetlb page size selection flags.

130
basic_actions.c Normal file
View File

@ -0,0 +1,130 @@
/*
* 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 *
parse_null(const char *str)
{
return NULL;
}
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 *buf = xstrdup(str);
parse_inject_common_args(buf, opts, fault_tokens_only, false);
if (!opts->data.flags)
error_msg_and_die("invalid %s argument '%s'",
description, str ? str : "");
free(buf);
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

@ -29,6 +29,7 @@
#include "defs.h"
#include "number_set.h"
#include "filter.h"
#include "syscall.h"
#include <regex.h>
static bool
@ -95,7 +96,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;
@ -126,6 +127,8 @@ lookup_class(const char *s)
unsigned int i;
for (i = 0; i < ARRAY_SIZE(syscall_class); ++i) {
if (!qualify_mode && *s != '%')
continue;
if (strcmp(s, syscall_class[i].name) == 0) {
return syscall_class[i].value;
}
@ -135,9 +138,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;
@ -180,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;
@ -192,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 +206,7 @@ qualify_syscall(const char *token, struct number_set *set)
*/
void
qualify_syscall_tokens(const char *const str, struct number_set *const set,
const char *const name)
bool qualify_mode)
{
/* Clear all sets. */
clear_number_set_array(set, SUPPORTED_PERSONALITIES);
@ -213,10 +216,15 @@ qualify_syscall_tokens(const char *const str, struct number_set *const set,
* of the remaining specification.
*/
const char *s = str;
handle_inversion:
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) {
@ -227,8 +235,8 @@ handle_inversion:
*/
return;
} else if (strcmp(s, "all") == 0) {
s = "!none";
goto handle_inversion;
invert_number_set_array(set, SUPPORTED_PERSONALITIES);
return;
}
/*
@ -245,25 +253,52 @@ handle_inversion:
for (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 %s '%s'", name, token);
error_msg_and_die("invalid system call '%s'", token);
}
}
free(copy);
if (!done) {
error_msg_and_die("invalid %s '%s'", name, str);
error_msg_and_die("invalid system call '%s'", str);
}
}
void *
parse_syscall_filter(const char *str, bool qualify_mode)
{
struct number_set *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)
{
struct number_set *set = priv_data;
return is_number_in_set_array(tcp->scno, set, current_personality);
}
void
free_syscall_filter(void *priv_data)
{
struct number_set *set = priv_data;
free_number_set_array(set, 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);
@ -273,10 +308,15 @@ qualify_tokens(const char *const str, struct number_set *const set,
* of the remaining specification.
*/
const char *s = str;
handle_inversion:
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) {
@ -287,8 +327,8 @@ handle_inversion:
*/
return;
} else if (strcmp(s, "all") == 0) {
s = "!none";
goto handle_inversion;
invert_number_set_array(set, 1);
return;
}
/*
@ -319,3 +359,79 @@ handle_inversion:
error_msg_and_die("invalid %s '%s'", name, str);
}
}
void *
parse_fd_filter(const char *str, bool qualify_mode)
{
struct number_set *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) {
struct number_set *set = data;
if (fd < 0)
return false;
return is_number_in_set(fd, set);
}
bool
run_fd_filter(struct tcb *tcp, void *priv_data)
{
struct number_set *set = priv_data;
/*
* mq_timedsend and mq_timedreceive are not marked as descriptor
* syscalls, but they can be dumped with -e read/write.
*/
switch (tcp->s_ent->sen) {
case SEN_mq_timedsend:
case SEN_mq_timedreceive:
return is_fd_in_set(tcp, tcp->u_arg[0], set);
}
return match_fd_common(tcp, &is_fd_in_set, set);
}
void
free_fd_filter(void *priv_data)
{
struct number_set *set = priv_data;
free_number_set_array(set, 1);
return;
}
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)
{
struct path_set *set = priv_data;
return pathtrace_match_set(tcp, set);
}
void
free_path_filter(void *priv_data)
{
struct path_set *set = priv_data;
unsigned int i;
for (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

@ -251,6 +251,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 USE_LIBUNWIND
# define QUAL_STACKTRACE 0x080 /* do the stack trace */
#endif
#define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
@ -260,8 +265,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 USE_LIBUNWIND
# 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)
@ -371,7 +381,7 @@ extern struct path_set {
extern unsigned xflag;
extern unsigned followfork;
#ifdef USE_LIBUNWIND
/* 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;
@ -647,8 +657,9 @@ print_struct_statfs64(struct tcb *, kernel_ulong_t addr, kernel_ulong_t size);
extern void print_ifindex(unsigned int);
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 \

130
filter.c Normal file
View File

@ -0,0 +1,130 @@
/*
* 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)
{
unsigned int i;
for (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)
{
unsigned int i;
for (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,
unsigned int filters_left)
{
unsigned int i;
for (i = 0; i < *nfilters - filters_left; ++i)
free_filter(*filters + i);
for (i = 0; i < filters_left; ++i)
(*filters)[i] = (*filters)[*nfilters - filters_left + i];
*filters = xreallocarray(*filters, filters_left, sizeof(struct filter));
*nfilters = filters_left;
}
void
set_filter_priv_data(struct filter *filter, void *priv_data)
{
if (filter)
filter->priv_data = priv_data;
}

View File

@ -30,11 +30,93 @@
#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);
string_to_uint_func func, const char *name,
bool qualify_mode);
void qualify_syscall_tokens(const char *str, struct number_set *set,
const char *name);
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,
unsigned int filters_left);
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 parse_filter_action(const char *, const char *, const char *);
void set_qualify_mode(struct filter_action *, unsigned int);
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 *, unsigned int);
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. */
DECL_FILTER_ACTION_PARSER(null);
DECL_FILTER_ACTION_PARSER(inject);
DECL_FILTER_ACTION_PARSER(fault);
#undef DECL_FILTER_ACTION_PARSER
#endif /* !STRACE_FILTER_H */

240
filter_action.c Normal file
View File

@ -0,0 +1,240 @@
/*
* 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 USE_LIBUNWIND
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;
unsigned int i;
/* 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 (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)
{
unsigned int i;
for (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 USE_LIBUNWIND
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);
unsigned int i;
if (!type)
error_msg_and_die("invalid filter action '%s'", name);
/* If action takes arguments, add new action */
if (type->parse_args != &parse_null)
return add_action(type);
for (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 == &parse_null)
error_msg("%s action takes no arguments, ignored arguments "
"'%s'", action->type->name, args);
action->priv_data = action->type->parse_args(args);
}
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, unsigned int filters_left)
{
set_filters_qualify_mode(&action->filters, &action->nfilters,
filters_left);
set_expression_qualify_mode(action->expr, filters_left);
}
void
filter_syscall(struct tcb *tcp)
{
unsigned int i;
tcp->qual_flg |= default_flags;
for (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;
}

469
filter_expression.c Normal file
View File

@ -0,0 +1,469 @@
/*
* 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,
unsigned int filters_left)
{
unsigned int i;
if (!expr)
error_msg_and_die("invalid expression");
reallocate_expression(expr, 2 * filters_left - 1);
for (i = 0; i < filters_left; ++i) {
expr->tokens[i].type = TOK_VARIABLE;
expr->tokens[i].data.variable_id = i;
}
for (; i < 2 * filters_left - 1; ++i) {
expr->tokens[i].type = TOK_OPERATOR;
expr->tokens[i].data.operator_id = OP_AND;
}
}
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;
unsigned int i;
for (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 *buf = xstrdup(str);
struct filter *cur_filter = NULL;
char *filter_name = NULL;
char *filter_arg = NULL;
int op;
char *p;
unsigned int pos = 0;
for (p = buf; *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(buf);
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 *buf = xstrdup(str);
char *p;
for (p = buf; *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'",
buf);
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 */
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 = buf;
parse_qualify_action(action_name, main_part, args);
break;
case F_BEGIN:
main_part = begin;
/* fall through */
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(buf);
}

View File

@ -31,16 +31,8 @@
#include "number_set.h"
#include "filter.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)
{
@ -154,166 +146,170 @@ parse_inject_token(const char *const token, struct inject_opts *const fopts,
return true;
}
static char *
parse_inject_expression(const char *const s, char **buf,
struct inject_opts *const fopts,
const bool fault_tokens_only)
void
parse_inject_common_args(char *str, struct inject_opts *const opts,
const bool fault_tokens_only, bool qualify_mode)
{
char *saveptr = NULL;
char *name = NULL;
char *token;
const char *delim = qualify_mode ? ":" : ";";
*buf = xstrdup(s);
for (token = strtok_r(*buf, ":", &saveptr); token;
token = strtok_r(NULL, ":", &saveptr)) {
if (!name)
name = token;
else if (!parse_inject_token(token, fopts, fault_tokens_only))
goto parse_error;
}
if (name)
return name;
parse_error:
free(*buf);
return *buf = NULL;
}
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, "system call");
}
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, "system call");
}
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, "system call");
}
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, "system call");
}
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
};
char *buf = NULL;
char *name = parse_inject_expression(str, &buf, &opts, fault_tokens_only);
if (!name) {
error_msg_and_die("invalid %s '%s'", description, str);
for (token = strtok_r(str, delim, &saveptr); 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, or signal 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 = -ENOSYS;
opts.data.flags |= INJECT_F_RETVAL;
opts->data.rval = -ENOSYS;
opts->data.flags |= INJECT_F_RETVAL;
} else {
/* in inject= syntax this is not allowed. */
error_msg_and_die("invalid %s '%s'", description, str);
return;
}
}
}
struct number_set *tmp_set =
alloc_number_set_array(SUPPORTED_PERSONALITIES);
qualify_syscall_tokens(name, tmp_set, description);
static void
qualify_read(const char *const main_part, const char *const args)
{
struct filter_action *action = find_or_add_action("read");
struct filter *filter = create_filter(action, "fd");
parse_filter(filter, main_part, true);
if (args)
error_msg("read action takes no arguments, ignored arguments "
"'%s'", args);
set_qualify_mode(action, 1);
}
static void
qualify_write(const char *const main_part, const char *const args)
{
struct filter_action *action = find_or_add_action("write");
struct filter *filter = create_filter(action, "fd");
parse_filter(filter, main_part, true);
if (args)
error_msg("write action takes no arguments, ignored arguments "
"'%s'", args);
set_qualify_mode(action, 1);
}
static void
qualify_signals(const char *const main_part, const char *const args)
{
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_trace(const char *const main_part, const char *const args)
{
struct filter_action *action = find_or_add_action("trace");
struct filter *filter = create_filter(action, "syscall");
parse_filter(filter, main_part, true);
if (args)
error_msg("trace action takes no arguments, ignored arguments "
"'%s'", args);
set_qualify_mode(action, 1);
}
static void
qualify_abbrev(const char *const main_part, const char *const args)
{
struct filter_action *action = find_or_add_action("abbrev");
struct filter *filter = create_filter(action, "syscall");
parse_filter(filter, main_part, true);
if (args)
error_msg("abbrev action takes no arguments, ignored arguments "
"'%s'", args);
set_qualify_mode(action, 1);
}
static void
qualify_verbose(const char *const main_part, const char *const args)
{
struct filter_action *action = find_or_add_action("verbose");
struct filter *filter = create_filter(action, "syscall");
parse_filter(filter, main_part, true);
if (args)
error_msg("verbose action takes no arguments, ignored arguments"
" '%s'", args);
set_qualify_mode(action, 1);
}
static void
qualify_raw(const char *const main_part, const char *const args)
{
struct filter_action *action = find_or_add_action("raw");
struct filter *filter = create_filter(action, "syscall");
parse_filter(filter, main_part, true);
if (args)
error_msg("raw action takes no arguments, ignored arguments "
"'%s'", args);
set_qualify_mode(action, 1);
}
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 *buf = 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, 1);
parse_inject_common_args(buf, opts, fault_tokens_only, true);
if (!opts->data.flags)
error_msg_and_die("invalid %s argument '%s'", description,
args ? args : "");
free(buf);
/*
* Initialize inject_vec accourding to tmp_set.
* Merge tmp_set into inject_set.
*/
unsigned int p;
for (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]));
}
unsigned int i;
for (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);
set_filter_action_priv_data(action, opts);
}
static void
qualify_fault(const char *const str)
qualify_fault(const char *const main_part, const char *const args)
{
qualify_inject_common(str, true, "fault argument");
qualify_inject_common(main_part, args, true, "fault");
}
static void
qualify_inject(const char *const str)
qualify_inject(const char *const main_part, const char *const args)
{
qualify_inject_common(str, false, "inject argument");
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 },
@ -337,37 +333,20 @@ 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;
unsigned int i;
for (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

@ -69,6 +69,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));
@ -90,7 +92,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);
}
/*
@ -142,25 +144,23 @@ pathtrace_select_set(const char *path, struct path_set *set)
storepath(rpath, set);
}
/*
* 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)
typedef bool (*match_fd_func)(struct tcb *, int, void *);
static
bool fdmatch_fd_func(struct tcb *tcp, int fd, void *data)
{
const struct_sysent *s;
return fdmatch(tcp, fd, (struct path_set *) data);
}
s = tcp->s_ent;
/* Match fd with func. */
bool
match_fd_common(struct tcb *tcp, match_fd_func func, void *data)
{
const struct_sysent *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:
@ -169,49 +169,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);
case SEN_old_mmap:
#if defined(S390)
@ -222,22 +190,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:
@ -245,9 +206,10 @@ 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);
}
case SEN_oldselect:
case SEN_pselect6:
case SEN_select:
@ -302,7 +264,7 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
j = next_set_bit(fds, j, nfds);
if (j < 0)
break;
if (fdmatch(tcp, j, set)) {
if (func(tcp, j, data)) {
free(fds);
return true;
}
@ -332,14 +294,19 @@ 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_bpf:
case SEN_creat:
case SEN_epoll_create:
case SEN_epoll_create1:
case SEN_eventfd2:
@ -348,6 +315,7 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
case SEN_inotify_init:
case SEN_inotify_init1:
case SEN_memfd_create:
case SEN_open:
case SEN_perf_event_open:
case SEN_pipe:
case SEN_pipe2:
@ -355,13 +323,85 @@ pathtrace_match_set(struct tcb *tcp, struct path_set *set)
case SEN_socket:
case SEN_socketpair:
case SEN_timerfd_create:
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;
}
@ -369,12 +409,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

@ -354,10 +354,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]...
@ -677,6 +682,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

@ -57,7 +57,7 @@ extern int optind;
extern char *optarg;
#ifdef USE_LIBUNWIND
/* 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
@ -1570,13 +1570,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 USE_LIBUNWIND
"k"
@ -1641,7 +1638,7 @@ init(int argc, char *argv[])
show_fd_path++;
break;
case 'v':
qualify("abbrev=none");
filtering_parse("abbrev=none");
break;
case 'V':
print_version();
@ -1656,7 +1653,7 @@ init(int argc, char *argv[])
error_opt_arg(c, optarg);
break;
case 'e':
qualify(optarg);
filtering_parse(optarg);
break;
case 'o':
outfname = optarg;
@ -1687,7 +1684,7 @@ init(int argc, char *argv[])
break;
#ifdef USE_LIBUNWIND
case 'k':
stack_trace_enabled = true;
filtering_parse("stacktrace(syscall all)");
break;
#endif
case 'E':
@ -1704,6 +1701,7 @@ init(int argc, char *argv[])
break;
}
}
filtering_parsing_finish();
argv += optind;
argc -= optind;
@ -2410,6 +2408,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

@ -342,7 +342,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;
@ -382,7 +381,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;
@ -399,7 +397,6 @@ decode_mips_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]));
@ -428,7 +425,7 @@ dumpio(struct tcb *tcp)
if (fd < 0)
return;
if (is_number_in_set(fd, read_set)) {
if (dump_read(tcp)) {
switch (tcp->s_ent->sen) {
case SEN_read:
case SEN_pread:
@ -451,7 +448,7 @@ dumpio(struct tcb *tcp)
return;
}
}
if (is_number_in_set(fd, write_set)) {
if (dump_write(tcp)) {
switch (tcp->s_ent->sen) {
case SEN_write:
case SEN_pwrite:
@ -537,8 +534,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)
{
@ -550,14 +545,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)
@ -696,7 +683,7 @@ syscall_entering_trace(struct tcb *tcp, unsigned int *sig)
}
#ifdef USE_LIBUNWIND
if (stack_trace_enabled) {
if (stacktrace(tcp)) {
if (tcp->s_ent->sys_flags & STACKTRACE_CAPTURE_ON_ENTER)
unwind_capture_stacktrace(tcp);
}
@ -741,7 +728,7 @@ syscall_exiting_decode(struct tcb *tcp, struct timeval *ptv)
gettimeofday(ptv, NULL);
#ifdef USE_LIBUNWIND
if (stack_trace_enabled) {
if (stacktrace(tcp)) {
if (tcp->s_ent->sys_flags & STACKTRACE_INVALIDATE_CACHE)
unwind_cache_invalidate(tcp);
}
@ -961,7 +948,7 @@ syscall_exiting_trace(struct tcb *tcp, struct timeval tv, int res)
line_ended();
#ifdef USE_LIBUNWIND
if (stack_trace_enabled)
if (stacktrace(tcp))
unwind_print_stacktrace(tcp);
#endif
return 0;
@ -1195,7 +1182,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

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

View File

@ -95,6 +95,8 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
execve-v \
execveat-v \
filter-unavailable \
filtering_fd \
filtering_path \
fork-f \
getpid \
getppid \
@ -267,6 +269,13 @@ 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 \
interactive_block.test \
@ -341,6 +350,7 @@ EXTRA_DIST = \
ipc_msgbuf.expected \
ksysent.sed \
lstatx.c \
mmap_name.sh \
match.awk \
net.expected \
netlink_sock_diag-v.sh \
@ -373,6 +383,7 @@ EXTRA_DIST = \
struct_flock.c \
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)))))))))))))))))))))))))))))))))"

76
tests/filtering_fd-syntax.test Executable file
View File

@ -0,0 +1,76 @@
#!/bin/sh
#
# Check fd set parsing 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_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"
check_e "invalid descriptor '$1'" -e "write=$2"
}
for arg in '' , ,, ,,, ; do
check_fd_new "$arg" "$arg"
check_fd_new "!" "!$arg"
check_fd_qualify "$arg" "$arg"
check_fd_qualify "!$arg" "!$arg"
done
for arg in -1 -42 \
not_fd \
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"
check_fd_qualify "$arg" "!$arg"
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

@ -0,0 +1,130 @@
#!/bin/sh
#
# Check syscall set parsing syntax.
#
# Copyright (c) 2016-2017 Dmitry V. Levin <ldv@altlinux.org>
# 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_syscall()
{
[ -z "$2" ] || check_e "invalid system call '$1'" -e"$2"
check_e "invalid system call '$1'" -e "$2"
check_e "invalid system call '$1'" -etrace="$2"
check_e "invalid system call '$1'" -e trace="$2"
check_e "invalid system call '$1'" -e abbrev="$2"
check_e "invalid system call '$1'" -e verbose="$2"
check_e "invalid system call '$1'" -e raw="$2"
check_e "invalid system call '$1'" -e inject="$2"
check_e "invalid system call '$1'" -e inject="$2:when=3"
check_e "invalid system call '$1'" -e fault="$2"
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 : :: ::: ; do
check_syscall "" "$arg"
check_syscall "!" "!$arg"
done
for arg in -1 -2 -3 -4 -5 \
invalid_syscall_name \
0x 0y \
32767 \
2147483647 \
2147483648 \
4294967295 \
4294967296 \
/non_syscall \
% %not_a_class \
; do
check_syscall "$arg" "$arg"
check_syscall "$arg" "!$arg"
check_syscall_new "$arg" "$arg"
done
for arg in desc \
file \
memory \
process \
network \
; do
check_syscall_new "$arg" "$arg"
done
# invalid syscall, multiple syscalls
for arg in %desc \
%file \
%memory \
%process \
%network \
chdir \
1 \
?32767 \
?invalid \
?%not_a_class \
?/non_syscall \
; do
check_syscall nonsense "$arg,nonsense"
check_syscall nonsense "nonsense,$arg"
check_syscall_new nonsense "$arg,nonsense"
check_syscall_new nonsense "nonsense,$arg"
done
for arg in \! \!open none all -1; do
check_syscall "$arg" "1,$arg"
check_syscall_new "$arg" "1,$arg"
done
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='/[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,25 +29,11 @@
# (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
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, PROT_NONE,' < "$LOG" > /dev/null; then
mmap=mmap
elif grep '^mmap2(NULL, 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, PROT_NONE,' < "$LOG" > /dev/null; then
mmap=mmap
elif grep '^mmap2(NULL, 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

@ -37,34 +37,6 @@ check_e "Invalid process id: 'a'" -p 1,a
check_e "Syscall 'chdir' for -b isn't supported" -b chdir
check_e "Syscall 'chdir' for -b isn't supported" -b execve -b chdir
check_e "invalid system call '-1'" -e-1
check_e "invalid system call '-2'" -e -2
check_e "invalid system call '-3'" -etrace=-3
check_e "invalid system call '-4'" -e trace=-4
check_e "invalid system call '-5'" -e trace=1,-5
check_e "invalid system call '/non_syscall'" -e trace=/non_syscall
check_e "invalid system call '2147483647'" -e 2147483647
check_e "invalid system call '2147483648'" -e 2147483648
check_e "invalid system call '4294967295'" -e 4294967295
check_e "invalid system call '4294967296'" -e 4294967296
check_e "invalid descriptor '-1'" -eread=-1
check_e "invalid descriptor '-42'" -ewrite=-42
check_e "invalid descriptor '2147483648'" -eread=2147483648
check_e "invalid descriptor '4294967296'" -ewrite=4294967296
check_e "invalid descriptor 'foo'" -eread=foo
check_e "invalid descriptor ''" -ewrite=
check_e "invalid descriptor ','" -eread=,
check_e "invalid descriptor '!'" -ewrite='!'
check_e "invalid descriptor '!'" -eread='0,!'
check_e "invalid descriptor '!,'" -ewrite='!,'
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='/[id'
check_e_using_grep 'exec: File *name too long' "$(printf '%4096s' ' ')"
check_h 'must have PROG [ARGS] or -p PID'

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-2017 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,85 +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 '' , ,, ,,, : :: ::: \! \!, \!: \
invalid_syscall_name \
invalid_syscall_name:when=3 \
-1 \!-1 \
-1:when=4 \
-2 \
-2:when=5 \
32767 \!32767 \
32767:when=6 \
chdir:42 \!chdir:42 \
chdir:42:when=7 \
chdir:invalid \
chdir:invalid:when=8 \
chdir:error= \
chdir:error=:when=10 \
chdir:error=invalid_error_name \
chdir:error=invalid_error_name:when=11 \
chdir:error=-1 \
chdir:error=-1:when=12 \
chdir:error=-2 \
chdir:error=-2:when=13 \
chdir:error=3+ \
chdir:error=3+:when=14 \
chdir:error=4096 \
chdir:error=4096:when=15 \
chdir:when=7:error=invalid_error_name \
chdir:when= \
chdir:when=:error=19 \
chdir:when=0 \
chdir:when=0:error=20 \
chdir:when=-1 \
chdir:when=-1:error=21 \
chdir:when=-2+ \
chdir:when=-2+:error=22 \
chdir:when=-3+0 \
chdir:when=-3+0:error=23 \
chdir:when=4- \
chdir:when=4-:error=24 \
chdir:when=5+- \
chdir:when=5+-:error=25 \
chdir:when=6++ \
chdir:when=6++:error=26 \
chdir:when=7+0 \
chdir:when=7+0:error=27 \
chdir:when=8+-1 \
chdir:when=8+-1:error=28 \
chdir:when=9+1+ \
chdir:when=9+1+:error=29 \
chdir:when=65536 \
chdir:when=65536:error=30 \
chdir:when=1+65536 \
chdir:when=1+65536:error=31 \
file,nonsense \
\!desc,nonsense \
chdir,nonsense \
\!chdir,nonsense \
1,nonsense \
\!1,nonsense \
chdir:retval=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-2017 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,94 +28,54 @@
# (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 '' , ,, ,,, : :: ::: \! \!, \!: \
invalid_syscall_name \
invalid_syscall_name:when=3 \
-1 \!-1 \
-1:when=4 \
-2 \
-2:when=5 \
32767 \!32767 \
32767:when=6 \
42 \
chdir \
chdir:42 \!chdir:42 \
chdir:42:when=7 \
chdir:invalid \
chdir:invalid:when=8 \
chdir:error= \
chdir:error=:when=10 \
chdir:error=invalid_error_name \
chdir:error=invalid_error_name:when=11 \
chdir:error=-1 \
chdir:error=-1:when=12 \
chdir:error=-2 \
chdir:error=-2:when=13 \
chdir:error=3+ \
chdir:error=3+:when=14 \
chdir:error=4096 \
chdir:error=4096:when=15 \
chdir:when=7:error=invalid_error_name \
chdir:when= \
chdir:when=:error=19 \
chdir:when=0 \
chdir:when=0:error=20 \
chdir:when=-1 \
chdir:when=-1:error=21 \
chdir:when=-2+ \
chdir:when=-2+:error=22 \
chdir:when=-3+0 \
chdir:when=-3+0:error=23 \
chdir:when=4- \
chdir:when=4-:error=24 \
chdir:when=5+- \
chdir:when=5+-:error=25 \
chdir:when=6++ \
chdir:when=6++:error=26 \
chdir:when=7+0 \
chdir:when=7+0:error=27 \
chdir:when=8+-1 \
chdir:when=8+-1:error=28 \
chdir:when=9+1+ \
chdir:when=9+1+:error=29 \
chdir:when=65536 \
chdir:when=65536:error=30 \
chdir:when=1+65536 \
chdir:when=1+65536:error=31 \
file,nonsense \
\!desc,nonsense \
chdir,nonsense \
\!chdir,nonsense \
1,nonsense \
\!1,nonsense \
chdir:retval=-1 \
chdir:signal=0 \
chdir:signal=129 \
chdir: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 \
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=-1"
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"
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

@ -581,3 +581,9 @@ unwind_capture_stacktrace(struct tcb *tcp)
debug_func_msg("tcp=%p, queue=%p", tcp, tcp->queue->head);
}
}
void
apply_stacktrace(struct tcb *tcp, void *_priv_data)
{
tcp->qual_flg |= QUAL_STACKTRACE;
}