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