Fragen? Antworten! Siehe auch: Alternativlos
Die Idee ist, dass ein Prozess Seccomp anschalten kann, das ist dann auch nicht revidierbar, und ab dann kann er nur noch read, write, exit und sigreturn machen, und read und write geht auch nur auf vorher schon geöffnete Deskriptoren. An sich keine doofe Idee, mal abgesehen von dem Verkaufen-Aspekt, aber hat sich m.W. nie in der Realität für irgendwas durchsetzen können. Es war doch ein bisschen zu restriktiv. Das Original-Seccomp war von 2005.
Und wo wir gerade bei Geschichtsstündchen sind, erzähle ich euch auch mal was von BPF, dem Berkeley Packet Filter. Wenn man unter Unix Netzwerktraffic sniffen will, nimmt man im Allgemeinen libpcap, die das schön abstrahiert. Die kann auch filtern. Man kennt die Syntax von tcpdump:
# tcpdump -s0 host 10.0.0.1 and tcp and not port 22Wenn man so einen Filter hat, dann will man ja nicht, dass der Kernel einem alle Pakete gibt und man selber filtert, sondern man will dem Kernel das mitteilen und der gibt einem dann nur die passenden Pakete raus. Der Mechanismus dafür heißt BPF und ist aus Programmsicht ein Array mit Bytecode-Instruktionen drin. Die haben alle eine fixe Länge, insofern eher RISC als Bytecode, aber der "Instruktionssatz" ist wunderschön archaisch. Da gibt es zwei Register, den Akkumulator und das Index-Register. Es gibt Load und Store, ALU-Operationen und Vergleiche mit Sprüngen. Ganz krude Geschichte. Hier ist mal ein Stück Beispielcode:
struct bpf_insn BPFAcceptIPSource[] = {Wer da keine feuchten Finger kriegt, ist kein echter Hacker! Und ich meine jetzt nicht nur wegen des Endianness-Problems. :-)
// Check Ethernet Protocol Word At Offset 12
BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 0, 3),// Check Source IP address At Offset 26
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 26),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x01020304, 0, 1),//Source == 1.2.3.4BPF_STMT(BPF_RET+BPF_K, (UINT)-1), // Accept.
BPF_STMT(BPF_RET+BPF_K, 0 )
};
Jedenfalls, wieso ich das erwähne: Eines Tages hat sich ein Schlaufuchs überlegt, hey, wir haben seccomp im Kernel, und wir haben BPF im Kernel, wir könnten doch einfach BPF statt auf ein Netzwerkpaket auf eine wohldefinierte Struct loslassen, wo die Syscall-Daten drinstehen, und dann kann sich der Userspace seine Regeln selbst definieren, welche Syscalls erlaubt sein sollen und welche nicht. Und genau das gibt es jetzt seit ein paar Jahren (?), und es heißt "Seccomp Filter" oder "Seccomp Mode 2".
Ich blogge das hier, weil ich sowas seit vielen Jahren haben will. Die Leute dahinter sind von Google und arbeiten an Chrome OS. Mir ist das überhaupt nur aufgefallen, dass es das jetzt gibt, weil aktuelle Versionen von Portable OpenSSH das als Sandboxing-Mechanismus benutzen — sehr cool!
Heute ergab sich erstmals für mich eine schöne Gelegenheit, das selber mal einzusetzen. Ich arbeite gerade in einem Kundenprojekt an Monitoring. Eines der in dem Projekt entstandenen Tools macht grob das selbe wie "vmstat 1", aber gibt das Ergebnis als CGI als text/event-stream aus, und auf der anderen Seite sitzt dann eine Webseite mit ein bisschen Javascript, das aus den Events SVG-Visualisierungen erzeugt. Dieses vmstat-CGI-Tool ist ein perfekter Kandidat für Seccomp-Filter, fand ich. Es öffnet turnusmäßig ein paar Dateien unter /proc und /sys, macht ein bisschen malloc, ein bisschen gettimeofday, und ruft dann write auf, um die Daten rauszuschreiben. Syscalls wie fork, execve, socket, kill oder connect muss der nie aufrufen. Also sage ich dem Kernel: Wenn dieser Prozess das doch tut, dann kille ihn sofort.
Als ich darauf per Seccomp-Filter eine Selbstbeschränkung programmiert habe, fiel mir auf, dass die bisherigen Codestücke nur auf die Syscallnummer gucken, weil das alles war, was die Chrome-OS-Leute in die Dokumentation getan haben. Aber die Struct beinhaltet auch die Syscall-Argumente. Das ist nur für den Fall nützlich, wo das keine Pointer auf Strings sind, denn denen kann man nicht hinterherlaufen aus dem BPF-Code heraus, aber man kann z.B. sowas machen wie: open() geht nur mit O_RDONLY. Dafür habe ich keinen Code gefunden, daher habe ich ihn selber geschrieben, und dachte mir, das tu ich mal in mein Blog, damit es andere Leute finden können, die möglicherweise auch mal in dieses Problem laufen. Die grobe Codestruktur von so einem Seccomp-Filter-Programm ist:
Hier ist mein Code für open():
/* we need a special case for open. * we want open to succeed, but only if it's O_RDONLY */ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_open, 0, 4), /* it's open(2). Load second argument into accumulator (first * argument is filename, second is mode). We want to check if mode * is O_RDONLY and only allow the syscall through if it is. */ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, args[1])), /* if it's O_RDONLY, skip 1 instruction */ /* slight complication: on 32-bit platforms we also pass O_LARGEFILE */ #if __WORDSIZE == 64 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, O_RDONLY, 1, 0), #else BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, O_RDONLY|O_LARGEFILE, 1, 0), #endif /* "return SECCOMP_RET_KILL", tell seccomp to kill the process */ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL), /* "return SECCOMP_RET_ALLOW", tell seccomp to allow the syscall */ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),Möge er von Hackern gefunden werden, die dieses Problem lösen möchten :-)