From c949f613315f19fce1fe5e6dacb1f0e5759c91c9 Mon Sep 17 00:00:00 2001 From: Slava Bacherikov Date: Thu, 20 Feb 2020 13:20:47 +0200 Subject: [PATCH] tools: execsnoop add -U and -u flags Add flags to display UID and filter by UID --- man/man8/execsnoop.8 | 25 +++++++++++++++++++-- tools/execsnoop.py | 45 +++++++++++++++++++++++++++++++++++++ tools/execsnoop_example.txt | 33 +++++++++++++++++++++++++-- 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/man/man8/execsnoop.8 b/man/man8/execsnoop.8 index aec2d70b77ec..4a88e007418a 100644 --- a/man/man8/execsnoop.8 +++ b/man/man8/execsnoop.8 @@ -2,8 +2,8 @@ .SH NAME execsnoop \- Trace new processes via exec() syscalls. Uses Linux eBPF/bcc. .SH SYNOPSIS -.B execsnoop [\-h] [\-T] [\-t] [\-x] [\-q] [\-n NAME] [\-l LINE] -.B [\-\-max-args MAX_ARGS] [\-\-cgroupmap MAPPATH] +.B execsnoop [\-h] [\-T] [\-t] [\-x] [\-\-cgroupmap CGROUPMAP] [\-u USER] +.B [\-q] [\-n NAME] [\-l LINE] [\-U] [\-\-max-args MAX_ARGS] .SH DESCRIPTION execsnoop traces new processes, showing the filename executed and argument list. @@ -28,9 +28,15 @@ Print usage message. \-T Include a time column (HH:MM:SS). .TP +\-U +Include UID column. +.TP \-t Include a timestamp column. .TP +\-u USER +Filter by UID (or username) +.TP \-x Include failed exec()s .TP @@ -59,6 +65,18 @@ Trace all exec() syscalls, and include timestamps: # .B execsnoop \-t .TP +Display process UID: +# +.B execsnoop \-U +.TP +Trace only UID 1000: +# +.B execsnoop \-u 1000 +.TP +Trace only processes launched by root and display UID column: +# +.B execsnoop \-Uu root +.TP Include failed exec()s: # .B execsnoop \-x @@ -86,6 +104,9 @@ Time of exec() return, in HH:MM:SS format. TIME(s) Time of exec() return, in seconds. .TP +UID +User ID +.TP PCOMM Parent process/command name. .TP diff --git a/tools/execsnoop.py b/tools/execsnoop.py index 57dfab291065..26cbce660479 100755 --- a/tools/execsnoop.py +++ b/tools/execsnoop.py @@ -24,14 +24,35 @@ import argparse import re import time +import pwd from collections import defaultdict from time import strftime + +def parse_uid(user): + try: + result = int(user) + except ValueError: + try: + user_info = pwd.getpwnam(user) + except KeyError: + raise argparse.ArgumentTypeError( + "{0!r} is not valid UID or user entry".format(user)) + else: + return user_info.pw_uid + else: + # Maybe validate if UID < 0 ? + return result + + # arguments examples = """examples: ./execsnoop # trace all exec() syscalls ./execsnoop -x # include failed exec()s ./execsnoop -T # include time (HH:MM:SS) + ./execsnoop -U # include UID + ./execsnoop -u 1000 # only trace UID 1000 + ./execsnoop -u user # get user UID and trace only them ./execsnoop -t # include timestamps ./execsnoop -q # add "quotemarks" around arguments ./execsnoop -n main # only print command lines containing "main" @@ -50,6 +71,8 @@ help="include failed exec()s") parser.add_argument("--cgroupmap", help="trace cgroups in this BPF map only") +parser.add_argument("-u", "--uid", type=parse_uid, metavar='USER', + help="trace this UID only") parser.add_argument("-q", "--quote", action="store_true", help="Add quotemarks (\") around arguments." ) @@ -59,6 +82,8 @@ parser.add_argument("-l", "--line", type=ArgString, help="only print commands where arg contains this line (regex)") +parser.add_argument("-U", "--print-uid", action="store_true", + help="print UID column") parser.add_argument("--max-args", default="20", help="maximum number of arguments parsed and displayed, defaults to 20") parser.add_argument("--ebpf", action="store_true", @@ -81,6 +106,7 @@ struct data_t { u32 pid; // PID as in the userspace term (i.e. task->tgid in kernel) u32 ppid; // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel) + u32 uid; char comm[TASK_COMM_LEN]; enum event_type type; char argv[ARGSIZE]; @@ -114,6 +140,11 @@ const char __user *const __user *__argv, const char __user *const __user *__envp) { + + u32 uid = bpf_get_current_uid_gid() & 0xffffffff; + + UID_FILTER + #if CGROUPSET u64 cgroupid = bpf_get_current_cgroup_id(); if (cgroupset.lookup(&cgroupid) == NULL) { @@ -164,7 +195,11 @@ struct data_t data = {}; struct task_struct *task; + u32 uid = bpf_get_current_uid_gid() & 0xffffffff; + UID_FILTER + data.pid = bpf_get_current_pid_tgid() >> 32; + data.uid = uid; task = (struct task_struct *)bpf_get_current_task(); // Some kernels, like Ubuntu 4.13.0-generic, return 0 @@ -182,6 +217,12 @@ """ bpf_text = bpf_text.replace("MAXARG", args.max_args) + +if args.uid: + bpf_text = bpf_text.replace('UID_FILTER', + 'if (uid != %s) { return 0; }' % args.uid) +else: + bpf_text = bpf_text.replace('UID_FILTER', '') if args.cgroupmap: bpf_text = bpf_text.replace('CGROUPSET', '1') bpf_text = bpf_text.replace('CGROUPPATH', args.cgroupmap) @@ -202,6 +243,8 @@ print("%-9s" % ("TIME"), end="") if args.timestamp: print("%-8s" % ("TIME(s)"), end="") +if args.print_uid: + print("%-6s" % ("UID"), end="") print("%-16s %-6s %-6s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS")) class EventType(object): @@ -251,6 +294,8 @@ def print_event(cpu, data, size): printb(b"%-9s" % strftime("%H:%M:%S").encode('ascii'), nl="") if args.timestamp: printb(b"%-8.3f" % (time.time() - start_ts), nl="") + if args.print_uid: + printb(b"%-6d" % event.uid, nl="") ppid = event.ppid if event.ppid > 0 else get_ppid(event.pid) ppid = b"%d" % ppid if ppid > 0 else b"?" argv_text = b' '.join(argv[event.pid]).replace(b'\n', b'\\n') diff --git a/tools/execsnoop_example.txt b/tools/execsnoop_example.txt index 618bcf65eac9..a90d00794006 100644 --- a/tools/execsnoop_example.txt +++ b/tools/execsnoop_example.txt @@ -85,11 +85,32 @@ with an externally created map. For more details, see docs/filtering_by_cgroups.md +The -U option include UID on output: + +# ./execsnoop -U + +UID PCOMM PID PPID RET ARGS +1000 ls 171318 133702 0 /bin/ls --color=auto +1000 w 171322 133702 0 /usr/bin/w + +The -u options filters output based process UID. You also can use username as +argument, in that cause UID will be looked up using getpwnam (see man 3 getpwnam). + +# ./execsnoop -Uu 1000 +UID PCOMM PID PPID RET ARGS +1000 ls 171335 133702 0 /bin/ls --color=auto +1000 man 171340 133702 0 /usr/bin/man getpwnam +1000 bzip2 171341 171340 0 /bin/bzip2 -dc +1000 bzip2 171342 171340 0 /bin/bzip2 -dc +1000 bzip2 171345 171340 0 /bin/bzip2 -dc +1000 manpager 171355 171340 0 /usr/bin/manpager +1000 less 171355 171340 0 /usr/bin/less USAGE message: # ./execsnoop -h -usage: execsnoop [-h] [-T] [-t] [-x] [-q] [-n NAME] [-l LINE] [--max-args MAX_ARGS] +usage: execsnoop.py [-h] [-T] [-t] [-x] [--cgroupmap CGROUPMAP] [-u USER] [-q] + [-n NAME] [-l LINE] [-U] [--max-args MAX_ARGS] Trace exec() syscalls @@ -98,11 +119,15 @@ optional arguments: -T, --time include time column on output (HH:MM:SS) -t, --timestamp include timestamp on output -x, --fails include failed exec()s - -q, --quote Add quotemarks (") around arguments + --cgroupmap CGROUPMAP + trace cgroups in this BPF map only + -u USER, --uid USER trace this UID only + -q, --quote Add quotemarks (") around arguments. -n NAME, --name NAME only print commands matching this name (regex), any arg -l LINE, --line LINE only print commands where arg contains this line (regex) + -U, --print-uid print UID column --max-args MAX_ARGS maximum number of arguments parsed and displayed, defaults to 20 @@ -110,7 +135,11 @@ examples: ./execsnoop # trace all exec() syscalls ./execsnoop -x # include failed exec()s ./execsnoop -T # include time (HH:MM:SS) + ./execsnoop -U # include UID + ./execsnoop -u 1000 # only trace UID 1000 + ./execsnoop -u root # get root UID and trace only this ./execsnoop -t # include timestamps ./execsnoop -q # add "quotemarks" around arguments ./execsnoop -n main # only print command lines containing "main" ./execsnoop -l tpkg # only print command where arguments contains "tpkg" + ./execsnoop --cgroupmap ./mappath # only trace cgroups in this BPF map