Ist die Geschäftskontinuität nicht Teil der Sicherheit?
Andrea Covello
Das kann das neue Kernel-Feature
Aufgrund der vielen Additionen zu BPF wird es oft als eBPF (extended BPF) betitelt. Wenn es um das Tracen von Events im Kernelspace geht, ist BPF effizienter als traditionelle Event-Tracing-Methoden, weil es direkt jede Kernelfunktion instrumentieren kann. Somit stehen BPF unzählige Quellen für Events zur Verfügung. BPF läuft in einer Sandbox im Kernel. Diese stellt sicher, dass die BPF-Applikation dem System keinen Schaden über ihre Applikationslogik zufügt. Das gibt BPF eine gute Eignung für das Monitoren von Events oder dem Troubleshooten auf Produktionssystemen. Ein Risiko, welches weiterhin existiert, ist ein negativer Einfluss auf die Systemperformance, wenn eine grosse Anzahl an Events gemonitored werden.
Eine existierende Sammlung von Utilities welche BPF verwenden, kann im bpfcc-tools
paket für Debian gefunden werden. opensnoop
ist ein nützliches Tool, welches darin enthalten ist. Damit kann das Öffnen von Dateien systemweit oder für ein einzelnes Programm gemonitored werden.
Ein weiteres nützliches Paket ist tcpretrans
, welches TCP-Retransmits monitored. Eine gängige Methode ohne BPF TCP-Retransmits festzustellen, ist das Erstellen und die Analyse eines Packet Captures mit einem Tool wie tcpdump. Mit BPF, können wir eine Kprobe an die Kernelfunktion hängen, welche bei TCP-Retransmits aufgerufen wird, was eine weitaus elegantere Lösung ist. Aus tcpretrans:
# initialize BPF b = BPF(text=bpf_text) b.attach_kprobe(event="tcp_retransmit_skb", fn_name="trace_retransmit")
Die Funktion trace_retransmit
ist eine vorher definierte C-Funktion in der Sourcedatei. Sie sammelt Informationen zum Retransmit und wird mit jedem Aufruf der Kernelfunktion ausgeführt.
Programme direkt in eBPF-Instruktionen zu schreiben ist zeitaufwendig und vergleichbar mit dem Programmieren in Assembler. Es gibt allerdings zugängliche Frontends mit unterschiedlichen Abstraktionsstufen und Möglichkeiten. In diesem Artikel wird bcc (BPF Compiler Collection) verwendet. Bcc bietet Frontends in Python und Lua. Folgend ein minimalistisches Beispiel, welches das Python Interface verwendet und gut zeigt, wie mit wenig Code der ptrace_attach
System-Call gemonitored weren kann, um eine Art von Process Injection zu erkennen.
/usr/bin/python from bcc import BPF BPF(text='int kprobe__ptrace_attach(void *ctx) { bpf_trace_printk("ptrace_attach called\\n"); return 0; }').trace_print()
Dieses Programm generiert den folgenden Output wenn ptrace_attach
aufgerufen wird:
# ./ptrace.py derusbi-6572 [003] .... 55527.716367: 0x00000001: ptrace_attach called
Der String, welcher der text
Variable zugeschrieben wird, ist eingeschränkter C-Code. kprobe__
ist ein spezieller Prefix, welcher eine Kprobe (dynamisches Tracing eines Aufrufs einer Kernelfunktion) an die Kernelfunktion mit dem selben Namen wie die Funktion anhängt. Die definierte Funktion wird dann jedes Mal ausgeführt, wenn ptrace_attach
aufgerufen wird.
bpf_trace_printk
kann als praktische Abkürzung verwendet werden, um während der Entwicklung oder zum Debuggen Daten an den Kernel /sys/kernel/debug/tracing/trace_pipe
auszugeben. Für andere Zwecke sollte diese Methode der Datenausgabe nicht verwendet werden, da trace_pipe
global geteilt ist. Der Output von bpf_trace_printk
hängt von den Einstellungen ab, die in /sys/kernel/debug/tracing/trace_options
gesetzt sind. In diesem Fall wird der Name des Tasks derusbi
, die PID 6572
, die CPU-Nummer auf der der Task läuft, IRQ-Optionen, ein Timestamp in Nanosekunden, ein von BPF generisch gesetzter Wert für den Instruction Pointer Register und unsere formattierte Nachricht dokumentiert.
Wir können einer Funktion mit attach_kprobe
in Python mehrere Kprobes anhängen. Weiterhin kann mit der Verwendung von trace_fields statt trace_print
grössere Kontrolle über die ausgegebenen Informationen erlangt werden.
from bcc import BPF # define BPF program bpf_program = """ int p_event(void *ctx) { bpf_trace_printk("traced very meaningful event!\\n"); return 0; } """ # load BPF program b = BPF(text=bpf_program) #attach kprobes for sys_clone and execve b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="p_event") b.attach_kprobe(event=b.get_syscall_fnname("execve"), fn_name="p_event") while True: try: (task, pid, cpu, flags, ts, msg) = b.trace_fields() except ValueError: continue print("%f\t%d\t%s\t%s" % (ts, pid, task, msg))
Dieses nächste Beispiel verwendet das BPF_PERF_OUTPUT()
Interface für die Ausgabe von Informationen. Das ist die präferierte Methode zum Teilen von pro Event gesammelten Daten mit dem User Space.
#!/usr/bin/python #Adapted example from https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md #as well as https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md from bcc import BPF # define BPF program bpf_program = """ #include <linux/sched.h> struct omg_data { u64 gid_pid; u32 pid; u32 gid; u64 ts; //timestamp with nanosecond precision char procname[TASK_COMM_LEN]; //holds the name of the current process }; BPF_PERF_OUTPUT(custom_event); //creates a BPF table for pushing out custom event data to user space via perf ring buffer. This is the preferred //method of pushing per-event data to user space. (might need to include <uapi/linux/ptrace.h> for this) int get_thi_dete(struct pt_regs *ctx) { struct omg_data mdata; mdata.gid_pid = bpf_get_current_pid_tgid(); mdata.pid = (u32) mdata.gid_pid; mdata.gid = mdata.gid_pid >> 32; mdata.ts = bpf_ktime_get_ns(); //gets timestamp in nanoseconds bpf_get_current_comm(&mdata.procname, sizeof(mdata.procname)); custom_event.perf_submit(ctx, &mdata, sizeof(mdata)); return 0; } """ # load BPF program b = BPF(text=bpf_program) b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="get_thi_dete") # header print("%-18s %-6s %-6s %-16s %s" % ("TIME(s)", "PID", "TID", "TASK", "MESSAGE")) # process event start = 0 def print_event(cpu, omg_data, size): global start event = b["custom_event"].event(omg_data) if start == 0: start = event.ts time_s = (float(event.ts - start)) / 1000000000 print("%-18.9f %-6d %-6d %-16s %s" % (time_s, event.gid, event.pid, event.procname, "Traced sys_clone!")) # loop with callback to print_event b["custom_event"].open_perf_buffer(print_event) while 1: b.perf_buffer_poll()
Weil wir hier kein bpt_trace_printk()
verwenden, bekommen wir auch kein vorgefertigtes Informationspaket. Das heisst, wir müssen selber Daten sammeln, welche für uns relevant sind. Wir benutzen das omg_data
Struct, um Daten vom Kernelspace zum Userspace zu schicken. BPF_PERF_OUTPUT('custom_event')
erstellt eine BPF-Table custom_event
, um unseren massgeschneiderten Datensatz zum Userspace via den Perf-Ring-Buffer zu pushen. Aus bpf.h
:
* u64 bpf_get_current_pid_tgid(void) * Return * A 64-bit integer containing the current tgid and pid, and * created as such: * *current_task*\ **->tgid << 32 \|** * *current_task*\ **->pid**.
Aus diesem Grund wird der zurückgegebene Wert herumgeshiftet. Um den Namen der momentanen Tasks zu erhalten, rufen wir bpf_get_current_comm()
auf. Aus bpf.h
:
* int bpf_get_current_comm(char *buf, u32 size_of_buf) * Description * Copy the **comm** attribute of the current task into *buf* of * *size_of_buf*. The **comm** attribute contains the name of * the executable (excluding the path) for the current task. The * *size_of_buf* must be strictly positive. On success, the * helper makes sure that the *buf* is NUL-terminated. On failure, * it is filled with zeroes.
perf_submit
gibt den Event für den Userspace via dem Perf-Ring-Buffer frei. print_event
ist die Python Funktion welche hier Events vom custom_event
Stream einliest. b.perf_buffer_poll()
wartet auf Events. Dieser Aufruf ist blockend.
Die neuen Additionen zu BPF erlauben es, kompakte, wirkungsvolle und performante Tracing-Programme zu schreiben. Nun bleibt erforderlich, geeignete Use-Cases zu finden, welche das Potential von BPF ausschöpfen. Obwohl BPF zur Laufzeit in den Kernel geladen und im Kernelspace ausgeführt wird, ist das Risiko eines negativen Einflusses auf andere Systemkomponenten dank Sandboxing und statischer Code-Analyse durch den Kernel eingeschränkt.
Unsere Spezialisten kontaktieren Sie gern!
Andrea Covello
Michèle Trebo
Lucie Hoffmann
Yann Santschi
Unsere Spezialisten kontaktieren Sie gern!