Ich möchte den genauen Unterschied zwischen Linux-Fähigkeiten und seccomp wissen.
Ich werde das genau erklären Unterschiede unten, aber hier ist die allgemeine Erklärung:Capabilities beinhalten verschiedene Überprüfungen in Kernel-Funktionen, die durch Systemaufrufe erreichbar sind. Wenn die Prüfung fehlschlägt (d. h. dem Prozess die erforderliche Fähigkeit fehlt), wird der Systemaufruf typischerweise durchgeführt, um einen Fehler zurückzugeben. Die Überprüfung kann entweder direkt zu Beginn eines bestimmten Systemaufrufs oder tiefer im Kernel in Bereichen durchgeführt werden, die möglicherweise durch mehrere verschiedene Systemaufrufe erreichbar sind (z. B. das Schreiben in eine bestimmte privilegierte Datei).
Seccomp ist ein Syscall-Filter, der auf alle Syscalls angewendet wird, bevor sie ausgeführt werden. Ein Prozess kann einen Filter einrichten, der es ihm ermöglicht, sein Recht zur Ausführung bestimmter Systemaufrufe oder bestimmter Argumente für bestimmte Systemaufrufe zu widerrufen. Der Filter hat normalerweise die Form von eBPF-Bytecode, den der Kernel verwendet, um zu prüfen, ob ein Syscall für diesen Prozess erlaubt ist oder nicht. Sobald ein Filter angewendet wird, kann er nicht mehr gelockert, sondern nur strenger gemacht werden (vorausgesetzt, die Systemaufrufe, die für das Laden einer seccomp-Richtlinie verantwortlich sind, sind immer noch erlaubt).
Beachten Sie, dass einige Systemaufrufe weder durch seccomp noch durch Capabilities eingeschränkt werden können, da es sich nicht um echte Systemaufrufe handelt. Dies ist bei vDSO-Aufrufen der Fall, bei denen es sich um Userspace-Implementierungen mehrerer Systemaufrufe handelt, die den Kernel nicht unbedingt benötigen. Versuch, getcpu()
zu blockieren oder gettimeofday()
ist aus diesem Grund sinnlos, da ein Prozess sowieso das vDSO anstelle des nativen Syscalls verwenden wird. Zum Glück sind diese Systemaufrufe (und ihre zugehörigen virtuellen Implementierungen) weitgehend harmlos.
Es ist auch so, dass Linux-Fähigkeiten intern seccomp verwenden oder ist es umgekehrt oder beide sind völlig unterschiedlich.
Sie sind intern völlig unterschiedlich implementiert. Ich habe an anderer Stelle eine weitere Antwort auf die aktuelle Implementierung verschiedener Sandboxing-Technologien geschrieben, in der erklärt wird, wie sie sich unterscheiden und wofür sie sind.
Fähigkeiten
Viele Systemaufrufe, die privilegierte Dinge tun, können eine interne Prüfung enthalten, um sicherzustellen, dass der aufrufende Prozess über ausreichende Fähigkeiten verfügt. Der Kernel speichert die Liste der Fähigkeiten, die ein Prozess hat, und sobald ein Prozess Fähigkeiten fallen lässt, kann er sie nicht zurückbekommen. Versuchen Sie beispielsweise, in /dev/cpu/*/msr
zu schreiben schlägt fehl, es sei denn, der Prozess, der open()
aufruft Systemaufruf hat CAP_SYS_RAWIO
. Dies kann im Kernel-Quellcode gesehen werden, der für das Modifizieren von MSRs (Low-Level-CPU-Features) verantwortlich ist:
static int msr_open(struct inode *inode, struct file *file)
{
unsigned int cpu = iminor(file_inode(file));
struct cpuinfo_x86 *c;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
if (cpu >= nr_cpu_ids || !cpu_online(cpu))
return -ENXIO; /* No such CPU */
c = &cpu_data(cpu);
if (!cpu_has(c, X86_FEATURE_MSR))
return -EIO; /* MSR not supported */
return 0;
}
Einige Systemaufrufe werden überhaupt nicht ausgeführt wenn die richtige Funktion nicht vorhanden ist, z. B. vhangup()
:
SYSCALL_DEFINE0(vhangup)
{
if (capable(CAP_SYS_TTY_CONFIG)) {
tty_vhangup_self();
return 0;
}
return -EPERM;
}
Fähigkeiten können als breite Klassen privilegierter Funktionalität betrachtet werden, die selektiv von einem Prozess oder Benutzer entfernt werden können. Die spezifischen Funktionen, die Fähigkeitsprüfungen haben, variieren von Kernel-Version zu Kernel-Version, und es gibt oft Streit zwischen Kernel-Entwicklern darüber, ob eine bestimmte Funktion Fähigkeiten zum Ausführen erfordern soll oder nicht. Allgemein , das Reduzieren der Fähigkeiten eines Prozesses verbessert die Sicherheit, indem die Anzahl der privilegierten Aktionen reduziert wird, die er ausführen kann. Beachten Sie, dass einige Fähigkeiten als root-äquivalent betrachtet werden , was bedeutet, dass selbst wenn Sie alle anderen Funktionen deaktivieren, diese unter bestimmten Bedingungen verwendet werden können, um die vollen Berechtigungen wiederzuerlangen. Viele Beispiele werden vom Erfinder von grsecurity, Brad Spengler, angeführt. Ein offensichtliches Beispiel wäre CAP_SYS_MODULE
das erlaubt, beliebige Kernel-Module zu laden. Ein anderer wäre CAP_SYS_ADMIN
Dies ist eine Catch-all-Fähigkeit, die fast äquivalent zu root ist.
Modus 1 Sek.komp.
Es gibt zwei Arten von seccomp:Modus 1 (streng) und Modus 2 (Filter). Modus 1 ist extrem restriktiv und erlaubt, sobald er aktiviert ist, nur vier Syscalls. Diese Systemaufrufe sind read()
, write()
, exit()
, und rt_sigreturn()
. Ein Prozess erhält sofort den fatalen SIGKILL
Signal vom Kernel, wenn er jemals versucht, einen Systemaufruf zu verwenden, der nicht auf der Whitelist steht. Dieser Modus ist der ursprüngliche seccomp-Modus und erfordert kein Generieren und Senden von eBPF-Bytecode an den Kernel. Es wird ein spezieller Systemaufruf durchgeführt, nach dem Modus 1 für die Lebensdauer des Prozesses aktiv ist:seccomp(SECCOMP_SET_MODE_STRICT)
oder prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)
. Sobald es aktiv ist, kann es nicht mehr ausgeschaltet werden.
Es folgt ein Beispielprogramm, das sicher Bytecode ausführt, der 42:
zurückgibt#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
/* "mov al,42; ret" aka "return 42" */
static const unsigned char code[] = "\xb0\x2a\xc3";
void main(void)
{
int fd[2], ret;
/* spawn child process, connected by a pipe */
pipe(fd);
if (fork() == 0) {
close(fd[0]);
/* enter mode 1 seccomp and execute untrusted bytecode */
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
ret = (*(uint8_t(*)())code)();
/* send result over pipe, and exit */
write(fd[1], &ret, sizeof(ret));
syscall(SYS_exit, 0);
} else {
close(fd[1]);
/* read the result from the pipe, and print it */
read(fd[0], &ret, sizeof(ret));
printf("untrusted bytecode returned %d\n", ret);
}
}
Modus 1 ist der ursprüngliche Modus und wurde hinzugefügt, um es zu ermöglichen, nicht vertrauenswürdigen Bytecode für Rohberechnungen auszuführen. Ein Broker-Prozess würde ein Kind forken (und möglicherweise eine Kommunikation über Pipes einrichten), und das Kind würde seccomp aktivieren, was es daran hindert, etwas anderes zu tun, als in bereits geöffnete Dateideskriptoren zu lesen und aus ihnen zu schreiben, und sich zu beenden. Dieser untergeordnete Prozess könnte dann nicht vertrauenswürdigen Bytecode sicher ausführen. Nicht viele Leute benutzten diesen Modus, aber bevor Linus sich laut genug beschweren konnte, um ihn zu beenden, äußerte das Google Chrome-Team den Wunsch, ihn für ihren Browser zu verwenden. Dies weckte neues Interesse an seccomp und rettete es vor einem frühen Tod.
Modus 2 Sek. Komp.
Der zweite Modus, filter, auch seccomp-bpf genannt, ermöglicht es dem Prozess, eine feinkörnige Filterrichtlinie an den Kernel zu senden, die ganze Syscalls oder bestimmte Syscall-Argumente oder -Bereiche von Argumenten zulässt oder verweigert. Die Richtlinie legt auch fest, was im Falle eines Verstoßes geschieht (sollte beispielsweise der Prozess beendet oder der Systemaufruf lediglich verweigert werden?) und ob der Verstoß protokolliert werden soll oder nicht. Da Linux-Systemaufrufe in Registern gehalten werden und daher nur ganze Zahlen sein können, ist es unmöglich, den Speicherinhalt zu filtern, auf den ein Systemaufruf-Argument zeigen könnte. Beispielsweise können Sie zwar open()
verhindern nicht mit dem schreibfähigen O_RDWR
aufgerufen werden oder O_WRONLY
-Flags können Sie den geöffneten einzelnen Pfad nicht auf die Whitelist setzen. Der Grund dafür ist, dass der Pfad für seccomp nichts anderes ist als ein Zeiger auf den Speicher, der den nullterminierten Dateisystempfad enthält. Es gibt keine Möglichkeit zu garantieren, dass der Speicher, der den Pfad enthält, nicht von einem gleichgeordneten Thread zwischen dem Passieren der seccomp-Prüfung und dem Dereferenzieren des Zeigers geändert wurde, außer ihn in den Nur-Lese-Speicher zu legen und speicherbezogenen Systemaufrufen den Zugriff darauf zu verweigern. Es ist oft notwendig, LSMs wie AppArmor zu verwenden.
Dies ist ein Beispielprogramm, das Modus 2 seccomp verwendet, um sicherzustellen, dass es nur seine aktuelle PID drucken kann. Dieses Programm verwendet die libseccomp-Bibliothek, die das Erstellen von seccomp-eBPF-Filtern vereinfacht, obwohl es auch möglich ist, dies auf die harte Tour ohne eine abstrahierende Bibliothek zu tun.
#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
void main(void)
{
/* initialize the libseccomp context */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
/* allow exiting */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
/* allow getting the current pid */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
/* allow changing data segment size, as required by glibc */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
/* allow writing up to 512 bytes to fd 1 */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_EQ, 1),
SCMP_A2(SCMP_CMP_LE, 512));
/* if writing to any other fd, return -EBADF */
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(write), 1,
SCMP_A0(SCMP_CMP_NE, 1));
/* load and enforce the filters */
seccomp_load(ctx);
seccomp_release(ctx);
printf("this process is %d\n", getpid());
}
Modus 2 seccomp wurde erstellt, weil Modus 1 offensichtlich seine Grenzen hatte. Nicht jede Aufgabe kann in einen reinen Bytecode-Prozess unterteilt werden, der in einem untergeordneten Prozess ausgeführt werden und über Pipes oder Shared Memory kommunizieren könnte. Dieser Modus hat weitaus mehr Funktionen und seine Funktionalität wird weiterhin langsam erweitert. Es hat jedoch immer noch seine Nachteile. Die sichere Verwendung von Modus 2 seccomp erfordert ein tiefes Verständnis von Systemaufrufen (möchte kill()
blockieren davon ab, andere Prozesse zu töten? Schade, dass Sie Prozesse mit fcntl()
beenden können zu!). Es ist auch zerbrechlich, da Änderungen an der zugrunde liegenden libc zu einem Bruch führen können. Die glibc open()
Die Funktion verwendet zum Beispiel nicht mehr immer den Syscall dieses Namens und kann stattdessen openat()
verwenden , gegen Richtlinien verstoßen, die nur erstere auf die weiße Liste gesetzt haben.