diff options
author | Eugene Syromyatnikov <evgsyr@gmail.com> | 2022-07-07 18:10:57 +0200 |
---|---|---|
committer | Eugene Syromyatnikov <evgsyr@gmail.com> | 2022-08-11 14:34:17 +0200 |
commit | 1521d93b17e911f88935561abda20d82bed169cd (patch) | |
tree | edade5f61f48037012ce0ca907f859937befddd9 | |
parent | edf0518768523edc16492029fa9fece9700c022d (diff) | |
download | strace-1521d93b17e911f88935561abda20d82bed169cd.tar.gz |
[wip] Add support for specifying path filters
-rw-r--r-- | doc/strace.1.in | 104 | ||||
-rw-r--r-- | src/defs.h | 57 | ||||
-rw-r--r-- | src/pathtrace.c | 35 | ||||
-rw-r--r-- | src/print_utils.h | 8 | ||||
-rw-r--r-- | src/strace.c | 95 | ||||
-rw-r--r-- | src/util.c | 110 |
6 files changed, 384 insertions, 25 deletions
diff --git a/doc/strace.1.in b/doc/strace.1.in index 262d618a1..fdc7f11ee 100644 --- a/doc/strace.1.in +++ b/doc/strace.1.in @@ -51,6 +51,7 @@ strace \- trace system calls and signals .OP \-s strsize .OP \-X format .OM \-P path +.OM \-H path_filter .OM \-p pid .OP \-\-seccomp\-bpf .if '@ENABLE_SECONTEXT_FALSE@'#' .OP \-\-secontext\fR[=\fIformat\fR] @@ -73,6 +74,7 @@ strace \- trace system calls and signals .OP \-S sortby .OP \-U columns .OM \-P path +.OM \-H path_filter .OM \-p pid .OP \-\-seccomp\-bpf .BR "" { @@ -717,11 +719,107 @@ Trace system calls that returned but strace failed to fetch the error status. Trace system calls for which strace detached before the return. .RE .TP +.BR "\-H " [ \fIqualifier\fR\fB:\fR ]... \fIpath_filter\fR +.TQ +.BR "\-\-path\-filter " [ \fIqualifier\fR\fB:\fR ]... \fIpath_filter\fR +Add a path tracing filter entry. +It restricts the printing of the trace only to syscalls that operate +on file paths and/or descriptors +(that is, the syscalls that are included in either +.BR %file ", " %desc ", or " %network +syscall tracing class) +and conform to at least one of the supplied path tracing specifications. +The set of qualifiers at the beginning of the filter specification affect +the way a certain path tracing filter specification entry is applied: +.RS +.TP 19 +.BR path\-str ", " pathstr +Match the syscalls that use the string provided in +.I path_filter +as a path string. +.TQ +.BR fd\-deleted +Match the syscalls that operate on the file descriptor that is associated +with path provided in +.I path_filter +and the path +.I is +unlinked from the file descriptor. +.TQ +.BR fd\-not\-deleted +Match the syscalls that operate on the file descriptor that is associated +with path provided in +.I path_filter +and the path +.I is not +unlinked from the file descriptor. +.RE +.IP +If neither of +.BR pathstr ", " fd-deleted ", or " fd-not-deleted +qualifiers has been provided, all three are assumed. +.IP +.I path_filter +is a byte string in which the following escape sequences are recognised: +.RS +.TP 4 +.B \e\e +Backslash (ASCII code 92). +.TQ +.B \e: +Colon (ASCII code 58). +.TQ +.B \ea +Alert (ASCII code 7). +.TQ +.B \eb +Backspace (ASCII code 8). +.TQ +.B \ef +Form feed (ASCII code 12). +.TQ +.B \en +New line (ASCII code 10). +.TQ +.B \er +Carriage return (ASCII code 13). +.TQ +.B \et +Horizontal tab (ASCII code 9). +.TQ +.B \ev +Vertical tab (ASCII code 11). +.TQ +.BR \e0 ", " \e0\fIn\fR ", " \e0\fInn\fR ", or " \e0\fInnn\fR +Byte with octal value +.IR nnn . +The sequence ends after specification of three digits +or on the first character that is not an octal digit. +.TQ +.BR \ex ", " \ex\fIn\fR ", or " \ex\fInn\fR +Byte with hexadecimal value +.IR nn . +The sequence ends after specification of two digits +or on the first character that is not an hexadecimal digit. +.RE +.IP +It is advisable to enquote the +.I path_filter +string in single quotes in the shell invocations +to avoid the need of double escaping. +.IP +Multiple +.B \-H +options can be used to specify multiple path tracing filter entries. +.TP .BI "\-P " path .TQ .BR "\-\-trace\-path" = \fIpath\fR Trace only system calls accessing -.IR path . +.I path +(equivalent to +.BR \-H escape( \fIpath\fI ), +where escape() escapes all non-printable characters and ":"). Multiple .B \-P options can be used to specify several paths. @@ -863,8 +961,8 @@ Suppress messages about process exits .TQ .B path-resolution Suppress messages about resolution of paths provided via the -.B \-P -option +.BR \-P and \-H +options .RB (\[dq] "Requested path \[dq]...\[dq] resolved into \[dq]...\[dq]" \[dq]). .TQ .B personality diff --git a/src/defs.h b/src/defs.h index f90763dd5..e42a00730 100644 --- a/src/defs.h +++ b/src/defs.h @@ -514,8 +514,21 @@ extern int Tflag_width; extern bool iflag; extern bool count_wallclock; +enum path_trace_flag_bits { + PTF_PATH_STR_BIT, + PTF_FD_NOT_DELETED_BIT, + PTF_FD_DELETED_BIT, +}; + +enum path_trace_flags { + FLAG(PTF_PATH_STR), + FLAG(PTF_FD_NOT_DELETED), + FLAG(PTF_FD_DELETED), +}; + struct path_set_item { const char *path; + enum path_trace_flags flags; }; /* are we filtering traces based on paths? */ @@ -776,13 +789,14 @@ extern long getrval2(struct tcb *); extern const char *signame(const int); extern const char *sprintsigname(const int); -extern void pathtrace_select_set(const char *, struct path_set *); +extern void pathtrace_select_set(const char *, enum path_trace_flags flags, + struct path_set *); extern bool pathtrace_match_set(struct tcb *, struct path_set *); static inline void -pathtrace_select(const char *path) +pathtrace_select(const char *path, enum path_trace_flags flags) { - return pathtrace_select_set(path, &global_path_set); + return pathtrace_select_set(path, flags, &global_path_set); } static inline bool @@ -846,6 +860,43 @@ str_strip_prefix_len(const char *str, const char *prefix, size_t prefix_len) # define STR_STRIP_PREFIX(str, prefix) \ str_strip_prefix_len((str), (prefix), sizeof(prefix) - 1) +/** + * Compare two strings of a known maximum size. + */ +extern int strnncmp(const char *s1, const char *s2, size_t n1, size_t n2); + +/** + * Un-escapes a string that is escaped using the following rules: + * * A backslash followed by '\', 'a', 'b', 'f', 'n', 'r', 't', 'v', + * is translated into '\', '\a', '\b', '\f', '\n', '\r', '\t', '\v'. + * * \0, \0n, \0nn, or \0nnn (up to the first character that is not 0-7) + * is translated into a byte with octal code 0nnn. + * * \x, \xn, or \xnn (up to the first character that is not in 0-9,A-F,a-f) + * is translated into a byte with hexadecimal code 0xnn (this deviates + * from the C standard that allows unlimited number of digits in the \x + * escaping notation). + * * A backslash followed by a character from escape_chars is translated + * into the character. + * * Any other character or end-of-string are considered an error. + * + * instr and outstr can point at the same memory, as un-escaping only shrinks + * the string and doesn't perform any lookback. + * + * instr parsing terminates after an \0 or reaching instr+size; + * \0 is appended to outstr unless there's no room. + * + * @return Returns one of the following: + * * 0 - everything has been been processed. + * * positive number NUM - finished early (\0 occurred in instr) + * at position NUM. + * * negative number NUM - incorrect escaped character at position NUM. + * * INT_MIN - size is too big (larger than INT_MAX or wraps around + * the instr). + */ +extern int string_unescape(const char *instr, char *outstr, + const unsigned int size, const char *escape_chars, + unsigned int *outsize); + /** String is '\0'-terminated. */ # define QUOTE_0_TERMINATED 0x01 /** Do not emit leading and ending '"' characters. */ diff --git a/src/pathtrace.c b/src/pathtrace.c index f94f2e60d..6836816ce 100644 --- a/src/pathtrace.c +++ b/src/pathtrace.c @@ -21,14 +21,19 @@ struct path_set global_path_set; +enum { PTF_DELETED_MASK = PTF_PATH_STR | PTF_FD_DELETED | PTF_FD_NOT_DELETED }; + /* * Return true if specified path matches one that we're tracing. */ static bool -pathmatch(const char *path, struct path_set *set) +pathmatch(const char *path, struct path_set *set, enum path_trace_flags flags) { for (unsigned int i = 0; i < set->num_selected; ++i) { - if (strcmp(path, set->paths_selected[i].path) == 0) + if (strcmp(path, set->paths_selected[i].path)) + continue; + + if (flags & set->paths_selected[i].flags & PTF_DELETED_MASK) return true; } return false; @@ -44,7 +49,7 @@ upathmatch(struct tcb *const tcp, const kernel_ulong_t upath, char path[PATH_MAX + 1]; return umovestr(tcp, upath, sizeof(path), path) > 0 && - pathmatch(path, set); + pathmatch(path, set, PTF_PATH_STR); } /* @@ -54,9 +59,12 @@ static bool fdmatch(struct tcb *tcp, int fd, struct path_set *set) { char path[PATH_MAX + 1]; - int n = getfdpath(tcp, fd, path, sizeof(path)); + bool deleted; + int n = getfdpath_pid(tcp->pid, fd, path, sizeof(path), &deleted); - return n >= 0 && pathmatch(path, set); + return n >= 0 && pathmatch(path, set, + deleted ? PTF_FD_DELETED + : PTF_FD_NOT_DELETED); } /* @@ -64,17 +72,19 @@ fdmatch(struct tcb *tcp, int fd, struct path_set *set) * Specifying NULL will delete all paths. */ static void -storepath(const char *path, struct path_set *set) +storepath(const char *path, enum path_trace_flags flags, struct path_set *set) { - if (pathmatch(path, set)) - return; /* already in table */ + debug_func_msg("adding '%s' with flags %#x", path, flags); if (set->num_selected >= set->size) set->paths_selected = xgrowarray(set->paths_selected, &set->size, sizeof(set->paths_selected[0])); - set->paths_selected[set->num_selected++].path = path; + set->paths_selected[set->num_selected].path = path; + set->paths_selected[set->num_selected].flags = + flags | (!(flags & PTF_DELETED_MASK) ? PTF_DELETED_MASK : 0); + set->num_selected++; } int @@ -157,11 +167,12 @@ getfdpath_pid(pid_t pid, int fd, char *buf, unsigned bufsize, bool *deleted) * version of the path. Specifying NULL will delete all paths. */ void -pathtrace_select_set(const char *path, struct path_set *set) +pathtrace_select_set(const char *path, enum path_trace_flags flags, + struct path_set *set) { char *rpath; - storepath(path, set); + storepath(path, flags, set); rpath = realpath(path, NULL); @@ -189,7 +200,7 @@ pathtrace_select_set(const char *path, struct path_set *set) free(path_quoted); free(rpath_quoted); } - storepath(rpath, set); + storepath(rpath, flags, set); } static bool diff --git a/src/print_utils.h b/src/print_utils.h index 8b899a3b2..f3424373f 100644 --- a/src/print_utils.h +++ b/src/print_utils.h @@ -23,6 +23,14 @@ is_print(uint8_t c) return (c >= ' ') && (c < 0x7f); } +static inline bool +is_hex(uint8_t c) +{ + return ((c >= '0') && (c < '9')) || + ((c >= 'A') && (c <= 'F')) || + ((c >= 'a') && (c <= 'f')); +} + /* Character printing functions */ /** @param unabbrev Whether to always print \ooo instead of \[[o]o]o. */ diff --git a/src/strace.c b/src/strace.c index 3f5a00a0a..0c295d6b2 100644 --- a/src/strace.c +++ b/src/strace.c @@ -280,11 +280,13 @@ usage(void) printf("\ Usage: strace [-ACdffhi" K_OPT "qqrtttTvVwxxyyzZ] [-I N] [-b execve] [-e EXPR]...\n\ [-a COLUMN] [-o FILE] [-s STRSIZE] [-X FORMAT] [-O OVERHEAD]\n\ - [-S SORTBY] [-P PATH]... [-p PID]... [-U COLUMNS] [--seccomp-bpf]\n"\ + [-S SORTBY] [-P PATH]... [-H PATH_FILTER]... [-p PID]...\n\ + [-U COLUMNS] [--seccomp-bpf]\n"\ SECONTEXT_OPT "\ { -p PID | [-DDD] [-E VAR=VAL]... [-u USERNAME] PROG [ARGS] }\n\ or: strace -c[dfwzZ] [-I N] [-b execve] [-e EXPR]... [-O OVERHEAD]\n\ - [-S SORTBY] [-P PATH]... [-p PID]... [-U COLUMNS] [--seccomp-bpf]\n\ + [-S SORTBY] [-P PATH]... [-H PATH_FILTER] [-p PID]...\n\ + [-U COLUMNS] [--seccomp-bpf]\n\ { -p PID | [-DDD] [-E VAR=VAL]... [-u USERNAME] PROG [ARGS] }\n\ \n\ General:\n\ @@ -335,6 +337,13 @@ Filtering:\n\ -e status=SET, --status=SET\n\ print only system calls with the return statuses in SET\n\ statuses: successful, failed, unfinished, unavailable, detached\n\ + -H [QUALIFIER:]...PATH_STR, --path-filter=[QUALIFIER:]...PATH_STR\n\ + specify a path filter entry\n\ + qualifiers: path-str, pathstr (PATH_STR is used as a string),\n\ + fd-deleted (fd is unlinked from PATH_STR),\n\ + fd-not-deleted (fd that is linked to PATH_STR)\n\ + path_str: string with \\\\. \\:, \\a, \\b, \\f, \\n, \\r, \\t, \\v, \\0nnn, \\xnn\n\ + escape sequences.\n\ -P PATH, --trace-path=PATH\n\ trace accesses to PATH\n\ -z, --successful-only\n\ @@ -2137,7 +2146,8 @@ struct pathtrace { }; static void -add_path_trace(struct pathtrace *pt, const char *path) +add_path_trace(struct pathtrace *pt, const char *path, + enum path_trace_flags flags) { if (pt->count >= pt->size) { pt->paths = xgrowarray(pt->paths, &pt->size, @@ -2145,9 +2155,74 @@ add_path_trace(struct pathtrace *pt, const char *path) } pt->paths[pt->count].path = path; + pt->paths[pt->count].flags = flags; pt->count++; } +/* Checks if a string is equal to some expected string literal */ +#define CHECK_STR(str_, chk_, sz_) \ + (!strnncmp((str_), (chk_), (sz_), sizeof(chk_) - 1)) + +static void +parse_path_filter_arg(struct pathtrace *pt, char *optarg) +{ + char *arg = optarg; + enum path_trace_flags flags = 0; + + while (true) { + char *search_arg = arg; + char *pos; + while ((pos = strchr(search_arg, ':'))) { + if ((pos > search_arg) && (pos[-1] == '\\')) { + search_arg = pos + 1; + continue; + } + break; + } + + if (!pos) + break; + + pos += 1; + + if (CHECK_STR(arg, "pathstr:", pos - arg)) { + flags |= PTF_PATH_STR; + } else if (CHECK_STR(arg, "path-str:", pos - arg)) { + flags |= PTF_PATH_STR; + } else if (CHECK_STR(arg, "fd-deleted:", pos - arg)) { + flags |= PTF_FD_DELETED; + } else if (CHECK_STR(arg, "fd-not-deleted:", pos - arg)) { + flags |= PTF_FD_NOT_DELETED; + } else { + error_msg_and_die("invalid path trace filter qualifier:" + " '%*s'", (int) (pos - arg), arg); + } + + arg = pos; + } + + unsigned int argsz; + size_t arglen = strlen(arg); + int ret = string_unescape(arg, arg, arglen, ":", &argsz); + + if (ret == INT_MIN) { + error_msg_and_die("path trace filter argument is too big" + " (size is %zu)", arglen); + } else if (ret < 0) { + error_msg_and_die("invalid escaping: \\%c at position %d", + arg[-ret], -ret); + } + + arglen = strlen(arg); + if (arglen < argsz) { + error_msg_and_die("embedded '\\0' in the path trace filter" + " argument (length %zu < size %u)", + arglen, argsz); + } + + add_path_trace(pt, arg, flags); +} + /* * Initialization part of main() was eating much stack (~0.5k), * which was unused after init. @@ -2234,7 +2309,7 @@ init(int argc, char *argv[]) #endif static const char optstring[] = - "+a:Ab:cCdDe:E:fFhiI:kno:O:p:P:qrs:S:tTu:U:vVwxX:yYzZ"; + "+a:Ab:cCdDe:E:fFhH:iI:kno:O:p:P:qrs:S:tTu:U:vVwxX:yYzZ"; enum { GETOPT_SECCOMP = 0x100, @@ -2277,6 +2352,7 @@ init(int argc, char *argv[]) { "output-separately", no_argument, 0, GETOPT_OUTPUT_SEPARATELY }, { "help", no_argument, 0, 'h' }, + { "path-filter", required_argument, 0, 'H' }, { "instruction-pointer", no_argument, 0, 'i' }, { "interruptible", required_argument, 0, 'I' }, { "stack-traces", no_argument, 0, 'k' }, @@ -2404,6 +2480,9 @@ init(int argc, char *argv[]) case 'h': usage(); break; + case 'H': + parse_path_filter_arg(&pathtrace, optarg); + break; case 'i': iflag = 1; break; @@ -2435,7 +2514,7 @@ init(int argc, char *argv[]) process_opt_p_list(optarg); break; case 'P': - add_path_trace(&pathtrace, optarg); + add_path_trace(&pathtrace, optarg, 0); break; case 'q': qflag_short++; @@ -2768,8 +2847,10 @@ init(int argc, char *argv[]) "take effect. " "See status qualifier for more complex filters."); - for (size_t cnt = 0; cnt < pathtrace.count; ++cnt) - pathtrace_select(pathtrace.paths[cnt].path); + for (size_t cnt = 0; cnt < pathtrace.count; ++cnt) { + pathtrace_select(pathtrace.paths[cnt].path, + pathtrace.paths[cnt].flags); + } free(pathtrace.paths); acolumn_spaces = xmalloc(acolumn + 1); diff --git a/src/util.c b/src/util.c index c4c4bc0ef..ee54be574 100644 --- a/src/util.c +++ b/src/util.c @@ -838,6 +838,116 @@ proc_status_get_id_list(int proc_pid, int *id_buf, size_t id_buf_size, return n; } +int +strnncmp(const char *s1, const char *s2, size_t n1, size_t n2) +{ + size_t n = MIN(n1, n2); + + int ret = strncmp(s1, s2, n); + if (ret) + return ret; + + if ((n2 == n1) || (n2 < n1 && !s1[n2]) || (n2 > n1 && !s2[n1])) + return 0; + + return n1 < n2 ? -256 : 256; +} + +int +string_unescape(const char *instr, char *outstr, const unsigned int size, + const char *escape_chars, unsigned int *outsize) +{ + const char *inpos = instr; + char *outpos = outstr; + enum { + NORMAL, + ESC, + ESC_OCT, + ESC_HEX, + } state = NORMAL; + size_t cnt = 0; + size_t val = 0; + + if ((size > INT_MAX) || ((uintptr_t) instr + size < (uintptr_t) instr)) + return INT_MIN; + + while (inpos < instr + size) { + switch (state) { + case NORMAL: + if (!*inpos) + goto out; + if (*inpos == '\\') { + state = ESC; + break; + } + + *outpos++ = *inpos; + break; + + case ESC: + switch (*inpos) { + case '\\': *outpos++ = '\\'; break; + case 'a': *outpos++ = '\a'; break; + case 'b': *outpos++ = '\b'; break; + case 'f': *outpos++ = '\f'; break; + case 'n': *outpos++ = '\n'; break; + case 'r': *outpos++ = '\r'; break; + case 't': *outpos++ = '\t'; break; + case 'v': *outpos++ = '\v'; break; + case '0': state = ESC_OCT; cnt = val = 0; break; + case 'x': state = ESC_HEX; cnt = val = 0; break; + default: + if (strchr(escape_chars, *inpos)) + *outpos++ = *inpos; + else + return -(inpos - instr); + } + break; + + case ESC_OCT: + if (cnt >= 3 || *inpos < '0' || *inpos > '7') { + *outpos++ = val; + state = NORMAL; + inpos -= 1; + break; + } + val = val * 8 + (*inpos - '0'); + cnt++; + break; + + case ESC_HEX: + if (cnt >= 2 || !is_hex(*inpos)) + state = NORMAL; + else if (*inpos >= '0' && *inpos <= '9') + val = val * 16 + (*inpos - '0'); + else if (*inpos >= 'A' && *inpos <= 'F') + val = val * 16 + (*inpos + 10 - 'A'); + else if (*inpos >= 'a' && *inpos <= 'f') + val = val * 16 + (*inpos + 10 - 'a'); + else + state = NORMAL; + + if (state == NORMAL) { + *outpos++ = val; + inpos -= 1; + } else { + cnt++; + } + + break; + } + + inpos += 1; + } +out: + if (outpos - outstr < size) + *outpos = '\0'; + if (outsize) + *outsize = outpos - outstr; + + return inpos - instr < size ? inpos - instr : 0; +} + /* * Quote string `instr' of length `size' * Write up to (3 + `size' * 4) bytes to `outstr' buffer. |