Detaillierte Analyse von Seccomp BPF

Einführung in seccomp

Secommp (SECure COMPuting) ist ein Sicherheitsmodul, das in Version 2.6.12 des Linux-Kernels eingeführt wurde und hauptsächlich dazu verwendet wird, die für einen bestimmten Prozess verfügbaren Systemaufrufe (Systemaufrufe) einzuschränken. Es wurde ursprünglich im CPUShare-Projekt verwendet und ermöglichte es Benutzern, ihre ungenutzten CPU-Zyklen zu vermieten, um nicht vertrauenswürdigen Code auszuführen. Diese Funktion selbst ist keine Sandbox (Sandbox), sondern lediglich ein Mechanismus zur Reduzierung der Gefährdung des Linux-Kernels, der ein wichtiger Bestandteil beim Aufbau einer sicheren Sandbox ist.

Mehrere Modi von seccomp:
Strikter Modus: Dies ist der strengste Modus, der es dem Prozess nur erlaubt, den angegebenen Systemaufruf aufzurufen. Wenn der Prozess versucht, andere Systemaufrufe aufzurufen, wird der Prozess beendet.
Filtermodus: Dies ist ein Whitelist-ähnlicher Modus, der es ermöglicht, festzulegen, welche Systemaufrufe erlaubt und welche verboten sind. Wenn ein Prozess versucht, einen nicht autorisierten Systemaufruf aufzurufen, wird dies vom Prozess selbst verarbeitet.
Protokollmodus: Dieser Modus ähnelt dem Filtermodus. Wenn ein Prozess jedoch versucht, einen nicht autorisierten Systemaufruf aufzurufen, zeichnet der Kernel ein Protokoll zur Überwachung auf.
Benachrichtigungsmodus: Dieser Modus ähnelt dem Protokollmodus. Wenn der Prozess jedoch versucht, einen nicht autorisierten Systemaufruf aufzurufen, sendet der Kernel ein SIGSYS-Signal an den Prozess, das vom Prozess selbst verarbeitet wird.

Seccomp BPF

Seccomp BPF (Berkeley Packet Filter) ist eine vom Linux-Kernel bereitgestellte Funktion, um die Systemaufrufe zu begrenzen, die ein Prozess durchführen kann. Es ermöglicht einem Prozess, zur Laufzeit ein BPF-Filterprogramm zu installieren, um die Systemaufrufe des Prozesses zu steuern und so die Sicherheit des gesamten Systems zu verbessern. Der Seccomp BPF-Filter verwendet eine dem Berkeley Packet Filter ähnliche Sprache, um die Regeln und Filter für bestimmte Systemaufrufe zu beschreiben und die Parameter der Systemaufrufe zu überprüfen und zu ändern. Im Vergleich zur herkömmlichen Filtermethode für Systemaufrufe arbeitet der Seccomp BPF-Filter im Kernelbereich, sodass die Belegung von Systemressourcen reduziert werden kann und das Filterprogramm selbst einfacher zu schreiben und zu warten ist

BPF definiert eine virtuelle Maschine (VM), die innerhalb des Kernels implementiert werden kann.

BPF-Programm

BPF-Programme werden hauptsächlich in filter.h und bpf_common.h im Linux-Kernel implementiert. Zu den Hauptdatenstrukturen gehören die folgenden:
Linux v5.18.4/include/uapi/linux/filte.h -> sock_fprog

struct sock_fprog {
    
        /* Required for SO_ATTACH_FILTER. */
    unsigned short        len;    /* BPF指令的数量 */
    struct sock_filter __user *filter;  /*指向BPF数组的指针 */
};

Diese Struktur zeichnet die Anzahl der Filterregeln und die Startposition des Regelarrays auf, und das Filterfeld zeigt auf die spezifischen Regeln. Die Form jeder Regel ist wie folgt:

Linux v5.18.4/include/uapi/linux/filte.h -> sock_filter

struct sock_filter {
    
        /* Filter block */
    __u16    code;   /* 过滤指令 */
    __u8    jt;    /* 为真时跳转 */
    __u8    jf;    /* 为假时跳转 */
    __u32    k;      /* 被操作值 */
};

BPF-Anweisungen können manuell geschrieben werden, Entwickler haben jedoch symbolische Konstanten und zwei praktische Makros BPF_STMT und BPF_JUMP definiert, mit denen sich BPF-Regeln bequem schreiben lassen.
Linux v5.18.4/include/uapi/linux/filte.h -> BPF_STMT&BPF_JUMP

/*
 * Macros for filter block array initializers.
 */
#ifndef BPF_STMT
#define BPF_STMT(code, k) {
      
       (unsigned short)(code), 0, 0, k }
#endif
#ifndef BPF_JUMP
#define BPF_JUMP(code, k, jt, jf) {
      
       (unsigned short)(code), jt, jf, k }
#endif

BPF-Opcodes

bpf_common.h

Befehlstyp Operation:
BPF_LD: Leseoperation
BPF_LDX: erweiterte Leseoperation
BPF_ST: Speicheroperation
BPF_STX: erweiterte Speicheroperation
BPF_ALU: arithmetisch-logische Operation
BPF_JMP: Sprungoperation
BPF_RET: Rückgabeoperation
BPF_MISC: andere Operationen

Datengrößendefinition
BPF_W: 32 Bits
BPF_H: 16 Bits
BPF_B: 8 Bits
BPF_DW: 64 Bits (nur eBPF)

Modusdefinition
BPF_IMM: Direkter Datenmodus
BPF_ABS: Absoluter Adressierungsmodus
BPF_IND: Indirekter Adressierungsmodus
BPF_MEM: Speicheradressierungsmodus
BPF_LEN: Datenlängenmodus
BPF_MSH: Datenverschiebungsmodus

Arithmetisch-logische Operation
BPF_ADD: Addition
BPF_SUB: Subtraktion
BPF_MUL: Multiplikation
BPF_DIV : Division
BPF_OR: Bitweises ODER BPF_AND: Bitweises UND BPF_LSH:
Linksverschiebung BPF_RSH : Rechtsverschiebung BPF_NEG: Negation BPF_MOD: Modulo BPF_XOR: Exklusives ODER




Sprungoperation
BPF_JA: Unbedingter Sprung
BPF_JEQ: Gleichheitssprung
BPF_JGT: Größer-als-Sprung
BPF_JGE: Größer-gleich-Sprung
BPF_JSET: Bitsatzsprung

Quelldefinitionen:
BPF_K: unmittelbare Quelle
BPF_X: Registerquelle

Operanden können mit ODER- (|) oder Plus- (+) Operationen kombiniert werden

BPF_STMT

BPF_STMT
BPF_STMT hat zwei Parameter, Opcode (Code) und Wert (k), zum Beispiel:

BPF_STMT(BPF_LD | BPF_W | BPF_ABS,(offsetof(struct seccomp_data, arch)))

Der Opcode besteht hier aus drei Anweisungen: BPF_LD: Erstellen einer BPF-Ladeoperation; BPF_W: Die Operandengröße beträgt ein Wort, BPF_ABS: Verwenden Sie den absoluten Offset, dh verwenden Sie den Wert in der Anweisung als Offset des Datenbereichs, der der Offset zwischen dem Architekturfeld und dem Datenbereich ist. offsetof() generiert den Offset des gewünschten Feldes im Datenbereich.
Die Funktion dieser Anweisung besteht darin, die Architekturnummer in den Akkumulator zu laden.

BPF_JUMP

Es gibt vier Parameter in BPF_JUMP: Opcode, Wert (k), Sprung für wahr (jt) und Sprung für falsch (jf). Beispiel: Die Sprungadresse ist hier eine relative Adresse, dh die Adresse relativ zum PC-Wert von BPF. Es ist zu beachten, dass BPF eine virtuelle Maschine definiert, die im Kernel implementiert werden
kann

BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K ,AUDIT_ARCH_X86_64 , 1, 0)

BPF_JMP | BPF JEQ erstellt eine Jump-for-Equality-Anweisung, die den Wert in der Anweisung (d. h. den zweiten Parameter AUDIT_ARCH_X86_64) mit dem Wert im Akkumulator (BPF_K) vergleicht. Bestimmen Sie, ob es gleich ist, das heißt, wenn die Architektur x86-64 ist, überspringen Sie die nächste Anweisung (jt = 1, was bedeutet, dass der Test wahr ist, und überspringen Sie eine Anweisung), andernfalls wird die nächste Anweisung ausgeführt (jf = 0, was bedeutet, dass, wenn der Test falsch ist, 0 Anweisungen übersprungen werden, dh die nächste Anweisung weiter ausgeführt wird).

BPF-bezogene Daten

Sobald seccomp-BPF für ein Programm konfiguriert ist, durchläuft jeder Systemaufruf den Seccomp-Filter, was sich in gewissem Maße auf die Leistung des Systems auswirkt. Darüber hinaus gibt der Seccomp-Filter einen Wert an den Kernel zurück, der angibt, ob der Systemaufruf zulässig ist. Der Rückgabewert ist ein 32-Bit-Wert, von dem die wichtigsten 16 Bits (SECCOMP_RET_ACTION-Maske) die Operation angeben, die der Kernel ausführen soll, und die anderen Bits (SECCOMP_RET_DATA-Maske) verwendet werden, um die mit der Operation verbundenen Daten zurückzugeben. Der Rückgabewerttyp ist in
seccomp.h definiert.

SECCOMP_RET_ALLOW:允许系统调用通过。
SECCOMP_RET_KILL_PROCESS:杀死整个进程,即结束进程。
SECCOMP_RET_KILL_THREAD:杀死线程,即终止当前线程。
SECCOMP_RET_KILL:与 SECCOMP_RET_KILL_THREAD 含义相同,只是别名。
SECCOMP_RET_TRAP:禁止并强制引发 SIGSYS 信号(与 SIGILL、SIGABRT 类似)。
SECCOMP_RET_ERRNO:返回一个 errno 错误码。
SECCOMP_RET_USER_NOTIF:通知用户空间的监听程序,即向用户态传递信息。
SECCOMP_RET_TRACE:不允许并将事件传递给跟踪器(tracee)。
SECCOMP_RET_LOG:记录事件到内核日志中。

Jedes seccomp-BPF-Programm verwendet die Struktur seccomp_data als Eingabeparameter, die in seccomp.h definiert ist

struct seccomp_data {
    
    
  int nr ;                    /* 系统调用号(依赖于体系架构) */
  __u32 arch ;                /* 架构(如AUDIT_ARCH_X86_64) */
  __u64 instruction_pointer ; /* CPU指令指针 */
  __u64 args [6];             /* 系统调用参数,最多有6个参数 */
};

Verwendung von Seccomp BPF

Bevor Sie den Verwendungsprozess einführen, müssen Sie Prctl() einführen

Prctl

#include <sys/prctl.h>

int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

Es gibt zwei Optionen im Zusammenhang mit seccomp: PR_SET_NO_NEW_PRIVS() und PR_SET_SECCOMP().

  • PR_SET_NO_NEW_PRIVS(): Es handelt sich um eine Funktion, die nach Linux 3.5 eingeführt wurde. Wenn ein Prozess oder untergeordneter Prozess das PR_SET_NO_NEW_PRIVS-Attribut festlegt, kann er nicht auf einige Vorgänge zugreifen, die nicht gemeinsam genutzt werden können, wie z. B. setuid, chroot usw. Das Programm, das seccomp-BPF konfiguriert, muss CAP_SYS_ADMIN in den Funktionen haben, oder das Programm hat das Attribut no_new_privs definiert. Wenn Sie dies nicht tun, ist der Seccomp-Schutz ungültig, wenn Nicht-Root-Benutzer das Programm verwenden. Nach dem Setzen des PR_SET_NO_NEW_PRIVS-Bits kann sichergestellt werden, dass Seccomp für alle Benutzer funktioniert.

    prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
    

    Wenn der zweite Parameter auf 1 gesetzt ist, kann dieser Vorgang sicherstellen, dass seccomp für alle Benutzer funktioniert und der untergeordnete Prozess, dh der Prozess nach execve, weiterhin den Einschränkungen von seccomp unterliegt.

  • R_SET_SECCOMP(): Setzt seccomp für den Prozess; die übliche Form ist wie folgt

    prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
    

    Der Parameter SECCOMP_MODE_FILTER gibt den Filtermodus des eingestellten Seccomp an. Wenn er auf SECCOMP_MODE_STRICT eingestellt ist, bedeutet dies den strikten Modus. Wenn es sich um den Filtermodus handelt, wird das entsprechende Systemaufruflimit durch die Struktur &prog (Struktur sock_fprog oben erwähnt) definiert.

Seccomp-Modus

Im Allgemeinen gibt es zwei Arten von striktem Modus und Filtermodus: Der strikte Modus ist nicht sehr praktisch, und der Filtermodus wird hauptsächlich verwendet.

Seccomp-Filtermodus

Code-Framework für den Ausführungsprozess
Fügen Sie hier eine Bildbeschreibung ein
:

	struct sock_filter filter[] = {
    
    
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_fork, 3, 0),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_clone, 2, 0),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_vfork, 1, 0),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    };
    struct sock_fprog prog = {
    
    
        .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])), // 规则条数
        .filter = filter,                                            // 结构体数组指针
    };

    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); // 设置NO_NEW_PRIVS                
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); // 加载seccomp过滤器

Fall:
Verhindern Sie, dass der Fork-Systemaufruf untergeordnete Prozesse erzeugt

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>

void configure_seccomp()
{
    
    
    printf("Configuring seccomp\n");
    prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}

int main()
{
    
    
    struct sock_filter filter[] = {
    
    
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_fork, 3, 0),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_clone, 2, 0),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_vfork, 1, 0),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    };
    struct sock_fprog prog = {
    
    
        .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])), // 规则条数
        .filter = filter,                                            // 结构体数组指针
    };

    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); // 设置NO_NEW_PRIVS
                                            
    // 加载seccomp过滤器
    if(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
    
    
        perror("加载seccomp 失败");
        exit(-1);
    }
    

    pid_t pid = fork();
    if (pid < 0)
    {
    
    
        printf("Error: Failed to create child process\n");
    }
    else if (pid == 0)
    {
    
    
        // 这是子进程的代码
        printf("Hello from child process!\n");
    }
    else
    {
    
    
        // 这是父进程的代码
        printf("Hello from parent process!\n");
    }

    printf("hello");

    return 0;
}

Referenz

Seccomp BPF und Containersicherheit

Guess you like

Origin blog.csdn.net/x646602196/article/details/130137830