• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2# @lint-avoid-python-3-compatibility-imports
3#
4# execsnoop Trace new processes via exec() syscalls.
5#           For Linux, uses BCC, eBPF. Embedded C.
6#
7# USAGE: execsnoop [-h] [-T] [-t] [-x] [-q] [-n NAME] [-l LINE]
8#                  [--max-args MAX_ARGS]
9#
10# This currently will print up to a maximum of 19 arguments, plus the process
11# name, so 20 fields in total (MAXARG).
12#
13# This won't catch all new processes: an application may fork() but not exec().
14#
15# Copyright 2016 Netflix, Inc.
16# Licensed under the Apache License, Version 2.0 (the "License")
17#
18# 07-Feb-2016   Brendan Gregg   Created this.
19
20from __future__ import print_function
21from bcc import BPF
22from bcc.containers import filter_by_containers
23from bcc.utils import ArgString, printb
24import bcc.utils as utils
25import argparse
26import re
27import time
28import pwd
29from collections import defaultdict
30from time import strftime
31
32
33def parse_uid(user):
34    try:
35        result = int(user)
36    except ValueError:
37        try:
38            user_info = pwd.getpwnam(user)
39        except KeyError:
40            raise argparse.ArgumentTypeError(
41                "{0!r} is not valid UID or user entry".format(user))
42        else:
43            return user_info.pw_uid
44    else:
45        # Maybe validate if UID < 0 ?
46        return result
47
48
49# arguments
50examples = """examples:
51    ./execsnoop           # trace all exec() syscalls
52    ./execsnoop -x        # include failed exec()s
53    ./execsnoop -T        # include time (HH:MM:SS)
54    ./execsnoop -U        # include UID
55    ./execsnoop -u 1000   # only trace UID 1000
56    ./execsnoop -u user   # get user UID and trace only them
57    ./execsnoop -t        # include timestamps
58    ./execsnoop -q        # add "quotemarks" around arguments
59    ./execsnoop -n main   # only print command lines containing "main"
60    ./execsnoop -l tpkg   # only print command where arguments contains "tpkg"
61    ./execsnoop --cgroupmap mappath  # only trace cgroups in this BPF map
62    ./execsnoop --mntnsmap mappath   # only trace mount namespaces in the map
63"""
64parser = argparse.ArgumentParser(
65    description="Trace exec() syscalls",
66    formatter_class=argparse.RawDescriptionHelpFormatter,
67    epilog=examples)
68parser.add_argument("-T", "--time", action="store_true",
69    help="include time column on output (HH:MM:SS)")
70parser.add_argument("-t", "--timestamp", action="store_true",
71    help="include timestamp on output")
72parser.add_argument("-x", "--fails", action="store_true",
73    help="include failed exec()s")
74parser.add_argument("--cgroupmap",
75    help="trace cgroups in this BPF map only")
76parser.add_argument("--mntnsmap",
77    help="trace mount namespaces in this BPF map only")
78parser.add_argument("-u", "--uid", type=parse_uid, metavar='USER',
79    help="trace this UID only")
80parser.add_argument("-q", "--quote", action="store_true",
81    help="Add quotemarks (\") around arguments."
82    )
83parser.add_argument("-n", "--name",
84    type=ArgString,
85    help="only print commands matching this name (regex), any arg")
86parser.add_argument("-l", "--line",
87    type=ArgString,
88    help="only print commands where arg contains this line (regex)")
89parser.add_argument("-U", "--print-uid", action="store_true",
90    help="print UID column")
91parser.add_argument("--max-args", default="20",
92    help="maximum number of arguments parsed and displayed, defaults to 20")
93parser.add_argument("--ebpf", action="store_true",
94    help=argparse.SUPPRESS)
95args = parser.parse_args()
96
97# define BPF program
98bpf_text = """
99#include <uapi/linux/ptrace.h>
100#include <linux/sched.h>
101#include <linux/fs.h>
102
103#define ARGSIZE  128
104
105enum event_type {
106    EVENT_ARG,
107    EVENT_RET,
108};
109
110struct data_t {
111    u32 pid;  // PID as in the userspace term (i.e. task->tgid in kernel)
112    u32 ppid; // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel)
113    u32 uid;
114    char comm[TASK_COMM_LEN];
115    enum event_type type;
116    char argv[ARGSIZE];
117    int retval;
118};
119
120BPF_PERF_OUTPUT(events);
121
122static int __submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
123{
124    bpf_probe_read_user(data->argv, sizeof(data->argv), ptr);
125    events.perf_submit(ctx, data, sizeof(struct data_t));
126    return 1;
127}
128
129static int submit_arg(struct pt_regs *ctx, void *ptr, struct data_t *data)
130{
131    const char *argp = NULL;
132    bpf_probe_read_user(&argp, sizeof(argp), ptr);
133    if (argp) {
134        return __submit_arg(ctx, (void *)(argp), data);
135    }
136    return 0;
137}
138
139int syscall__execve(struct pt_regs *ctx,
140    const char __user *filename,
141    const char __user *const __user *__argv,
142    const char __user *const __user *__envp)
143{
144
145    u32 uid = bpf_get_current_uid_gid() & 0xffffffff;
146
147    UID_FILTER
148
149    if (container_should_be_filtered()) {
150        return 0;
151    }
152
153    // create data here and pass to submit_arg to save stack space (#555)
154    struct data_t data = {};
155    struct task_struct *task;
156
157    data.pid = bpf_get_current_pid_tgid() >> 32;
158
159    task = (struct task_struct *)bpf_get_current_task();
160    // Some kernels, like Ubuntu 4.13.0-generic, return 0
161    // as the real_parent->tgid.
162    // We use the get_ppid function as a fallback in those cases. (#1883)
163    data.ppid = task->real_parent->tgid;
164
165    bpf_get_current_comm(&data.comm, sizeof(data.comm));
166    data.type = EVENT_ARG;
167
168    __submit_arg(ctx, (void *)filename, &data);
169
170    // skip first arg, as we submitted filename
171    #pragma unroll
172    for (int i = 1; i < MAXARG; i++) {
173        if (submit_arg(ctx, (void *)&__argv[i], &data) == 0)
174             goto out;
175    }
176
177    // handle truncated argument list
178    char ellipsis[] = "...";
179    __submit_arg(ctx, (void *)ellipsis, &data);
180out:
181    return 0;
182}
183
184int do_ret_sys_execve(struct pt_regs *ctx)
185{
186    if (container_should_be_filtered()) {
187        return 0;
188    }
189
190    struct data_t data = {};
191    struct task_struct *task;
192
193    u32 uid = bpf_get_current_uid_gid() & 0xffffffff;
194    UID_FILTER
195
196    data.pid = bpf_get_current_pid_tgid() >> 32;
197    data.uid = uid;
198
199    task = (struct task_struct *)bpf_get_current_task();
200    // Some kernels, like Ubuntu 4.13.0-generic, return 0
201    // as the real_parent->tgid.
202    // We use the get_ppid function as a fallback in those cases. (#1883)
203    data.ppid = task->real_parent->tgid;
204
205    bpf_get_current_comm(&data.comm, sizeof(data.comm));
206    data.type = EVENT_RET;
207    data.retval = PT_REGS_RC(ctx);
208    events.perf_submit(ctx, &data, sizeof(data));
209
210    return 0;
211}
212"""
213
214bpf_text = bpf_text.replace("MAXARG", args.max_args)
215
216if args.uid:
217    bpf_text = bpf_text.replace('UID_FILTER',
218        'if (uid != %s) { return 0; }' % args.uid)
219else:
220    bpf_text = bpf_text.replace('UID_FILTER', '')
221bpf_text = filter_by_containers(args) + bpf_text
222if args.ebpf:
223    print(bpf_text)
224    exit()
225
226# initialize BPF
227b = BPF(text=bpf_text)
228execve_fnname = b.get_syscall_fnname("execve")
229b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
230b.attach_kretprobe(event=execve_fnname, fn_name="do_ret_sys_execve")
231
232# header
233if args.time:
234    print("%-9s" % ("TIME"), end="")
235if args.timestamp:
236    print("%-8s" % ("TIME(s)"), end="")
237if args.print_uid:
238    print("%-6s" % ("UID"), end="")
239print("%-16s %-7s %-7s %3s %s" % ("PCOMM", "PID", "PPID", "RET", "ARGS"))
240
241class EventType(object):
242    EVENT_ARG = 0
243    EVENT_RET = 1
244
245start_ts = time.time()
246argv = defaultdict(list)
247
248# This is best-effort PPID matching. Short-lived processes may exit
249# before we get a chance to read the PPID.
250# This is a fallback for when fetching the PPID from task->real_parent->tgip
251# returns 0, which happens in some kernel versions.
252def get_ppid(pid):
253    try:
254        with open("/proc/%d/status" % pid) as status:
255            for line in status:
256                if line.startswith("PPid:"):
257                    return int(line.split()[1])
258    except IOError:
259        pass
260    return 0
261
262# process event
263def print_event(cpu, data, size):
264    event = b["events"].event(data)
265    skip = False
266
267    if event.type == EventType.EVENT_ARG:
268        argv[event.pid].append(event.argv)
269    elif event.type == EventType.EVENT_RET:
270        if event.retval != 0 and not args.fails:
271            skip = True
272        if args.name and not re.search(bytes(args.name), event.comm):
273            skip = True
274        if args.line and not re.search(bytes(args.line),
275                                       b' '.join(argv[event.pid])):
276            skip = True
277        if args.quote:
278            argv[event.pid] = [
279                b"\"" + arg.replace(b"\"", b"\\\"") + b"\""
280                for arg in argv[event.pid]
281            ]
282
283        if not skip:
284            if args.time:
285                printb(b"%-9s" % strftime("%H:%M:%S").encode('ascii'), nl="")
286            if args.timestamp:
287                printb(b"%-8.3f" % (time.time() - start_ts), nl="")
288            if args.print_uid:
289                printb(b"%-6d" % event.uid, nl="")
290            ppid = event.ppid if event.ppid > 0 else get_ppid(event.pid)
291            ppid = b"%d" % ppid if ppid > 0 else b"?"
292            argv_text = b' '.join(argv[event.pid]).replace(b'\n', b'\\n')
293            printb(b"%-16s %-7d %-7s %3d %s" % (event.comm, event.pid,
294                   ppid, event.retval, argv_text))
295        try:
296            del(argv[event.pid])
297        except Exception:
298            pass
299
300
301# loop with callback to print_event
302b["events"].open_perf_buffer(print_event)
303while 1:
304    try:
305        b.perf_buffer_poll()
306    except KeyboardInterrupt:
307        exit()
308