Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python
      2 # @lint-avoid-python-3-compatibility-imports
      3 #
      4 # capable   Trace security capabilitiy checks (cap_capable()).
      5 #           For Linux, uses BCC, eBPF. Embedded C.
      6 #
      7 # USAGE: capable [-h] [-v] [-p PID]
      8 #
      9 # ToDo: add -s for kernel stacks.
     10 #
     11 # Copyright 2016 Netflix, Inc.
     12 # Licensed under the Apache License, Version 2.0 (the "License")
     13 #
     14 # 13-Sep-2016   Brendan Gregg   Created this.
     15 
     16 from __future__ import print_function
     17 from bcc import BPF
     18 import argparse
     19 from time import strftime
     20 import ctypes as ct
     21 
     22 # arguments
     23 examples = """examples:
     24     ./capable             # trace capability checks
     25     ./capable -v          # verbose: include non-audit checks
     26     ./capable -p 181      # only trace PID 181
     27 """
     28 parser = argparse.ArgumentParser(
     29     description="Trace security capability checks",
     30     formatter_class=argparse.RawDescriptionHelpFormatter,
     31     epilog=examples)
     32 parser.add_argument("-v", "--verbose", action="store_true",
     33     help="include non-audit checks")
     34 parser.add_argument("-p", "--pid",
     35     help="trace this PID only")
     36 args = parser.parse_args()
     37 debug = 0
     38 
     39 # capabilities to names, generated from (and will need updating):
     40 # awk '/^#define.CAP_.*[0-9]$/ { print "    " $3 ": \"" $2 "\"," }' \
     41 #     include/uapi/linux/capability.h
     42 capabilities = {
     43     0: "CAP_CHOWN",
     44     1: "CAP_DAC_OVERRIDE",
     45     2: "CAP_DAC_READ_SEARCH",
     46     3: "CAP_FOWNER",
     47     4: "CAP_FSETID",
     48     5: "CAP_KILL",
     49     6: "CAP_SETGID",
     50     7: "CAP_SETUID",
     51     8: "CAP_SETPCAP",
     52     9: "CAP_LINUX_IMMUTABLE",
     53     10: "CAP_NET_BIND_SERVICE",
     54     11: "CAP_NET_BROADCAST",
     55     12: "CAP_NET_ADMIN",
     56     13: "CAP_NET_RAW",
     57     14: "CAP_IPC_LOCK",
     58     15: "CAP_IPC_OWNER",
     59     16: "CAP_SYS_MODULE",
     60     17: "CAP_SYS_RAWIO",
     61     18: "CAP_SYS_CHROOT",
     62     19: "CAP_SYS_PTRACE",
     63     20: "CAP_SYS_PACCT",
     64     21: "CAP_SYS_ADMIN",
     65     22: "CAP_SYS_BOOT",
     66     23: "CAP_SYS_NICE",
     67     24: "CAP_SYS_RESOURCE",
     68     25: "CAP_SYS_TIME",
     69     26: "CAP_SYS_TTY_CONFIG",
     70     27: "CAP_MKNOD",
     71     28: "CAP_LEASE",
     72     29: "CAP_AUDIT_WRITE",
     73     30: "CAP_AUDIT_CONTROL",
     74     31: "CAP_SETFCAP",
     75     32: "CAP_MAC_OVERRIDE",
     76     33: "CAP_MAC_ADMIN",
     77     34: "CAP_SYSLOG",
     78     35: "CAP_WAKE_ALARM",
     79     36: "CAP_BLOCK_SUSPEND",
     80     37: "CAP_AUDIT_READ",
     81 }
     82 
     83 # define BPF program
     84 bpf_text = """
     85 #include <uapi/linux/ptrace.h>
     86 #include <linux/sched.h>
     87 
     88 struct data_t {
     89    // switch to u32s when supported
     90    u64 pid;
     91    u64 uid;
     92    int cap;
     93    int audit;
     94    char comm[TASK_COMM_LEN];
     95 };
     96 
     97 BPF_PERF_OUTPUT(events);
     98 
     99 int kprobe__cap_capable(struct pt_regs *ctx, const struct cred *cred,
    100     struct user_namespace *targ_ns, int cap, int audit)
    101 {
    102     u32 pid = bpf_get_current_pid_tgid();
    103     FILTER1
    104     FILTER2
    105 
    106     u32 uid = bpf_get_current_uid_gid();
    107     struct data_t data = {.pid = pid, .uid = uid, .cap = cap, .audit = audit};
    108     bpf_get_current_comm(&data.comm, sizeof(data.comm));
    109     events.perf_submit(ctx, &data, sizeof(data));
    110 
    111     return 0;
    112 };
    113 """
    114 if args.pid:
    115     bpf_text = bpf_text.replace('FILTER1',
    116         'if (pid != %s) { return 0; }' % args.pid)
    117 if not args.verbose:
    118     bpf_text = bpf_text.replace('FILTER2', 'if (audit == 0) { return 0; }')
    119 bpf_text = bpf_text.replace('FILTER1', '')
    120 bpf_text = bpf_text.replace('FILTER2', '')
    121 if debug:
    122     print(bpf_text)
    123 
    124 # initialize BPF
    125 b = BPF(text=bpf_text)
    126 
    127 TASK_COMM_LEN = 16    # linux/sched.h
    128 
    129 class Data(ct.Structure):
    130     _fields_ = [
    131         ("pid", ct.c_ulonglong),
    132         ("uid", ct.c_ulonglong),
    133         ("cap", ct.c_int),
    134         ("audit", ct.c_int),
    135         ("comm", ct.c_char * TASK_COMM_LEN)
    136     ]
    137 
    138 # header
    139 print("%-9s %-6s %-6s %-16s %-4s %-20s %s" % (
    140     "TIME", "UID", "PID", "COMM", "CAP", "NAME", "AUDIT"))
    141 
    142 # process event
    143 def print_event(cpu, data, size):
    144     event = ct.cast(data, ct.POINTER(Data)).contents
    145 
    146     if event.cap in capabilities:
    147         name = capabilities[event.cap]
    148     else:
    149         name = "?"
    150     print("%-9s %-6d %-6d %-16s %-4d %-20s %d" % (strftime("%H:%M:%S"),
    151         event.uid, event.pid, event.comm.decode('utf-8', 'replace'),
    152         event.cap, name, event.audit))
    153 
    154 # loop with callback to print_event
    155 b["events"].open_perf_buffer(print_event)
    156 while 1:
    157     b.perf_buffer_poll()
    158