Skip to content

Commit

Permalink
Merge pull request #8 from aquasecurity/event-filter
Browse files Browse the repository at this point in the history
Add event filter
  • Loading branch information
lizrice committed Nov 13, 2019
2 parents c691511 + 31f1a58 commit 69f490d
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 33 deletions.
18 changes: 16 additions & 2 deletions start.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,26 @@
# arguments
import argparse
import sys
import re

from tracee.container_tracer import EventMonitor
from tracee.container_tracer import EventMonitor, syscalls, sysevents

examples = """examples:
./start.py -v
"""

class EventsToTraceAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
events = re.split('\W+', values)
for e in events:
if e not in syscalls and e not in sysevents and e != "all":
parser.error("Invalid event {0}".format(e))

if "all" in events:
events = syscalls + sysevents

setattr(namespace, self.dest, events)


def parse_args(input_args):
parser = argparse.ArgumentParser(
Expand All @@ -25,7 +38,8 @@ def parse_args(input_args):
help=argparse.SUPPRESS)
parser.add_argument("-j", "--json", action="store_true",
help="save events in json format")
# args = parser.parse_args()
parser.add_argument("-e", "--events-to-trace", default = syscalls + sysevents, action=EventsToTraceAction,
help="trace only the specified events and syscalls (default: trace all)")
return parser.parse_args(input_args)


Expand Down
27 changes: 26 additions & 1 deletion test_container_tracer.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/python
import ctypes
import unittest
import tracee
import start

from tracee.container_tracer import EventMonitor
from tracee.container_tracer import EventMonitor, syscalls, sysevents


class TestEventMonitor(unittest.TestCase):
Expand Down Expand Up @@ -141,6 +142,30 @@ def test_get_sockaddr_from_buf(self):
self.assertEqual(test_case["expected_sock_type"], sockaddr_str, "returned sock_type should be equal")
self.assertEqual(test_case["expected_cur_off"], em.cur_off, "current offset should be 2")

def test_event_filter(self):

test_cases = [
{
"name": "default to all events",
"args": [],
"expected_events": syscalls + sysevents
},
{
"name": "include all events",
"args": ["-e", "all"],
"expected_events": syscalls + sysevents
},
{
"name": "specify one",
"args": ["-e", "open"],
"expected_events": ["open"]
}
]

for test_case in test_cases:
em = tracee.container_tracer.EventMonitor(start.parse_args(test_case["args"]))
self.assertEqual(len(em.events_to_trace), len(test_case["expected_events"]), test_case["name"])


if __name__ == '__main__':
unittest.main()
90 changes: 60 additions & 30 deletions tracee/container_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,12 @@
"connect", "accept", "accept4", "bind", "getsockname", "prctl", "ptrace",
"process_vm_writev", "process_vm_readv", "init_module", "finit_module", "delete_module",
"symlink", "symlinkat", "getdents", "getdents64", "creat", "open", "openat"]
sysevents = ["cap_capable", "do_exit"]

# We always need kprobes for execve[at] so that we capture the new PID namespace,
# and do_exit so we clean up
essential_syscalls = ["execve", "execveat"]
essential_sysevents = ["do_exit"]

class EventType(object):
EVENT_ARG = 0
Expand Down Expand Up @@ -679,6 +684,27 @@ def open_flags_to_str(flags):

return f_str

# Given the list of event names the user wants to trace, get_kprobes() returns the
# - syscalls we want kprobes for
# - events we want kprobes for
# Includes the essential kprobes needed for Tracee to work
def get_kprobes(events):
sc = essential_syscalls
se = essential_sysevents
for e in events:
if e in syscalls:
sc.append(e)
elif e in sysevents:
se.append(e)
# Argument parsing should have already checked that the event names are good
else:
raise ValueError("Bad event name {0}".format(e))

# Dedupe the lists in case the essential syscalls / sysevents were specified
sc = list(set(sc))
se = list(set(se))
return sc, se


class EventMonitor:

Expand All @@ -692,6 +718,7 @@ def __init__(self, args):
self.bpf = None
self.json = args.json
self.ebpf = args.ebpf
self.events_to_trace = args.events_to_trace

def init_bpf(self):
bpf_text = load_bpf_program().replace("MAXARG", str(MAX_ARGS))
Expand All @@ -704,13 +731,15 @@ def init_bpf(self):
self.bpf = BPF(text=bpf_text)

# attaching kprobes
for syscall in syscalls:
sk, se = get_kprobes(self.events_to_trace)

for syscall in sk:
syscall_fnname = self.bpf.get_syscall_fnname(syscall)
self.bpf.attach_kprobe(event=syscall_fnname, fn_name="trace_sys_" + syscall)
self.bpf.attach_kretprobe(event=syscall_fnname, fn_name="trace_ret_sys_" + syscall)

self.bpf.attach_kprobe(event="do_exit", fn_name="trace_do_exit")
self.bpf.attach_kprobe(event="cap_capable", fn_name="trace_cap_capable")
for sysevent in se:
self.bpf.attach_kprobe(event=sysevent, fn_name="trace_" + sysevent)

if not self.json:
log.info("%-14s %-12s %-12s %-6s %-16s %-16s %-6s %-6s %-6s %-16s %s" % (
Expand Down Expand Up @@ -1026,33 +1055,34 @@ def print_event(self, cpu, data, size):
except:
return

if not self.json:
log.info("%-14f %-12d %-12d %-6d %-16s %-16s %-6d %-6d %-6d %-16d %s" % (
context.ts / 1000000.0, context.mnt_id, context.pid_id, context.uid,
eventname, comm, pid, tid, ppid, context.retval, " ".join(args)))
else: # prepare data to be consumed by ultrabox
data = dict()
data["status"] = [0]
data["raw"] = ""
data["type"] = ["apicall"]
data["time"] = context.ts / 1000000.0
data["mnt_ns"] = context.mnt_id
data["pid_ns"] = context.pid_id
data["uid"] = context.uid
data["api"] = eventname
data["process_name"] = comm
data["pid"] = pid
data["tid"] = tid
data["ppid"] = ppid
data["return_value"] = context.retval
dict_args = dict()
args_len = len(args)
for i in range(args_len):
dict_args["p" + str(i)] = args[i].rstrip('\0')
data["arguments"] = dict_args

log.info(json.dumps(data))
self.events.append(data)
if eventname in self.events_to_trace:
if not self.json:
log.info("%-14f %-12d %-12d %-6d %-16s %-16s %-6d %-6d %-6d %-16d %s" % (
context.ts / 1000000.0, context.mnt_id, context.pid_id, context.uid,
eventname, comm, pid, tid, ppid, context.retval, " ".join(args)))
else: # prepare data to be consumed by ultrabox
data = dict()
data["status"] = [0]
data["raw"] = ""
data["type"] = ["apicall"]
data["time"] = context.ts / 1000000.0
data["mnt_ns"] = context.mnt_id
data["pid_ns"] = context.pid_id
data["uid"] = context.uid
data["api"] = eventname
data["process_name"] = comm
data["pid"] = pid
data["tid"] = tid
data["ppid"] = ppid
data["return_value"] = context.retval
dict_args = dict()
args_len = len(args)
for i in range(args_len):
dict_args["p" + str(i)] = args[i].rstrip('\0')
data["arguments"] = dict_args

log.info(json.dumps(data))
self.events.append(data)

# if eventname == "do_exit" and pid == 1:
# log.info(json.dumps(events, indent=4))
Expand Down

0 comments on commit 69f490d

Please sign in to comment.