Análisis detallado de Seccomp BPF

Introducción a seccomp

Secommp (SECure COMPuting) es un módulo de seguridad introducido en la versión 2.6.12 del kernel de Linux, se utiliza principalmente para limitar las llamadas al sistema (system call) disponibles para un determinado proceso. Originalmente se usó en el proyecto cpushare, lo que permitía a las personas alquilar sus ciclos de CPU inactivos para ejecutar código no confiable. Esta función en sí no es un sandbox (sandbox), es solo un mecanismo para reducir la exposición del kernel de Linux, que es una parte importante de la construcción de un sandbox seguro.

Varios modos de seccomp:
Modo estricto: este es el modo más estricto, que solo permite que el proceso llame a la llamada del sistema especificada. Si el proceso intenta llamar a otras llamadas del sistema, hará que el proceso finalice.
modo de filtro: este es un modo similar a una lista blanca que permite especificar qué llamadas al sistema están permitidas y cuáles están prohibidas. Si un proceso intenta invocar una llamada al sistema no autorizada, será manejado por el propio proceso.
modo de registro: este modo es similar al modo de filtro, pero cuando un proceso intenta realizar una llamada al sistema no autorizada, el kernel registrará un registro para su auditoría.
modo de notificación: este modo es similar al modo de registro, pero cuando el proceso intenta realizar una llamada al sistema no autorizada, el kernel enviará una señal SIGSYS al proceso, que es manejada por el propio proceso.

Seccomp BPF

Seccomp BPF (Berkeley Packet Filter) es una función proporcionada por el kernel de Linux para limitar las llamadas al sistema que puede realizar un proceso. Permite que un proceso instale un programa de filtro BPF en tiempo de ejecución para controlar las llamadas al sistema del proceso, mejorando así la seguridad de todo el sistema. El filtro BPF de Seccomp utiliza un lenguaje similar al filtro de paquetes de Berkeley para describir las reglas, filtros para llamadas al sistema específicas y puede verificar y modificar los parámetros de las llamadas al sistema. En comparación con el método tradicional de filtrado de llamadas al sistema, el filtro Seccomp BPF funciona en el espacio del núcleo, por lo que puede reducir la ocupación de los recursos del sistema y el programa de filtro en sí mismo es más fácil de escribir y mantener.

BPF define una máquina virtual (VM) que se puede implementar dentro del kernel.

programa BPF

Los programas BPF se implementan principalmente en filter.h y bpf_common.h en el kernel de Linux. Las principales estructuras de datos incluyen lo siguiente:
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数组的指针 */
};

Esta estructura registra el número de reglas de filtrado y la posición inicial de la matriz de reglas, y el campo de filtro apunta a las reglas específicas. La forma de cada regla es la siguiente:

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

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

Las instrucciones BPF se pueden escribir manualmente, sin embargo, los desarrolladores han definido constantes simbólicas y dos macros convenientes BPF_STMT y BPF_JUMP que se pueden usar para escribir reglas BPF convenientemente.
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

códigos de operación BPF

bpf_común.h

Operación de tipo de instrucción:
BPF_LD: operación de lectura
BPF_LDX: operación de lectura extendida
BPF_ST: operación de almacenamiento
BPF_STX: operación de almacenamiento extendida
BPF_ALU: operación lógica aritmética
BPF_JMP: operación de salto
BPF_RET: operación de retorno
BPF_MISC: otras operaciones

Definición del tamaño de datos
BPF_W: 32 bits
BPF_H: 16 bits
BPF_B: 8 bits
BPF_DW: 64 bits (solo eBPF)

Definición de modo
BPF_IMM: modo de datos inmediatos
BPF_ABS: modo de direccionamiento absoluto
BPF_IND: modo de direccionamiento indirecto
BPF_MEM: modo de direccionamiento de memoria
BPF_LEN: modo de longitud de datos
BPF_MSH: modo de desplazamiento de datos

Operación lógica aritmética
BPF_ADD: Suma
BPF_SUB: Resta
BPF_MUL: Multiplicación
BPF_DIV : División
BPF_OR: Bitwise OR BPF_AND: Bitwise AND BPF_LSH:
Desplazamiento
a la izquierda BPF_RSH
: Desplazamiento a la derecha
BPF_NEG: Negación
BPF_MOD: Modulo
BPF_XOR: OR exclusivo

Operación de salto
BPF_JA: salto incondicional
BPF_JEQ: salto de igualdad
BPF_JGT: salto mayor que
BPF_JGE: salto mayor o igual
BPF_JSET: salto de conjunto de bits

Definiciones de fuente:
BPF_K: fuente inmediata
BPF_X: fuente de registro

Los operandos se pueden combinar con operaciones o (|) o más (+)

BPF_STMT

BPF_STMT
BPF_STMT tiene dos parámetros, opcode (código) y valor (k), por ejemplo:

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

El código de operación aquí se compone de tres instrucciones, BPF_LD: construye una operación de carga BPF; BPF_W: el tamaño del operando es una palabra, BPF_ABS: usa el desplazamiento absoluto, es decir, usa el valor en la instrucción como el desplazamiento del área de datos, que es el desplazamiento entre el campo de arquitectura y el área de datos. offsetof() genera el desplazamiento del campo deseado en el área de datos.
La función de esta instrucción es cargar el número de arquitectura en el acumulador.

BPF_JUMP

Hay cuatro parámetros en BPF_JUMP: código de operación, valor (k), salto para verdadero (jt) y salto para falso (jf). Por ejemplo: la dirección de salto aquí es una dirección relativa, es decir, la dirección relativa al valor de pc de BPF. Cabe señalar que BPF define una máquina virtual que se puede implementar en el kernel
.

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

BPF_JMP | BPF JEQ crea una instrucción de salto por igualdad que compara el valor de la instrucción (es decir, el segundo parámetro AUDIT_ARCH_X86_64) con el valor del acumulador (BPF_K). Determine si es igual, es decir, si la arquitectura es x86-64, salte la siguiente instrucción (jt=1, lo que significa que la prueba es verdadera y salte una instrucción), de lo contrario, se ejecutará la siguiente instrucción (jf=0, lo que significa que si la prueba es falsa, salte 0 instrucciones, es decir, continúe ejecutando la siguiente instrucción).

Datos relacionados con BPF

Una vez que seccomp-BPF está configurado para un programa, cada llamada al sistema pasará por el filtro seccomp, lo que afectará el rendimiento del sistema hasta cierto punto. Además, el filtro Seccomp devolverá un valor al núcleo indicando si la llamada al sistema está permitida.El valor de retorno es un valor de 32 bits, de los cuales los 16 bits más importantes (máscara SECCOMP_RET_ACTION) especifican la operación que debe realizar el núcleo, y los otros bits (máscara SECCOMP_RET_DATA) se utilizan para devolver los datos asociados con la operación. El tipo de valor devuelto se define en
seccomp.h ,

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:记录事件到内核日志中。

Cada programa seccomp-BPF utiliza la estructura seccomp_data como parámetro de entrada, que se define en seccomp.h

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

Uso de BPF de Seccomp

Antes de introducir el proceso de uso, debe introducir Prctl()

Prctl

#include <sys/prctl.h>

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

Hay dos opciones relacionadas con seccomp: PR_SET_NO_NEW_PRIVS() y PR_SET_SECCOMP().

  • PR_SET_NO_NEW_PRIVS(): Es una característica introducida después de Linux 3.5, cuando un proceso o proceso hijo establece el atributo PR_SET_NO_NEW_PRIVS, no puede acceder a algunas operaciones que no se pueden compartir, como setuid, chroot, etc. El programa que configura seccomp-BPF debe tener CAP_SYS_ADMIN en Capacidades, o el programa ha definido el atributo no_new_privs. Si no hace esto, la protección de seccomp no será válida cuando los usuarios que no sean root usen el programa.Después de configurar el bit PR_SET_NO_NEW_PRIVS, puede garantizar que seccomp funcione para todos los usuarios.

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

    Si su segundo parámetro se establece en 1, esta operación puede garantizar que seccomp pueda funcionar para todos los usuarios, y el proceso secundario, es decir, el proceso después de execve, aún está sujeto a las restricciones de seccomp.

  • R_SET_SECCOMP(): Establecer seccomp para el proceso, la forma habitual es la siguiente

    prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
    

    El parámetro SECCOMP_MODE_FILTER indica el modo de filtro del set seccomp, si está configurado en SECCOMP_MODE_STRICT, significa modo estricto, si es modo de filtro, el límite de llamadas al sistema correspondiente está definido por la estructura &prog (struct sock_fprog mencionado anteriormente).

modo secundario

Generalmente, hay dos tipos de modo estricto y modo de filtrado.El modo estricto no es muy práctico, y el modo de filtrado se usa principalmente.

Modo de filtro secundario

Marco de código de proceso de ejecución
inserte la descripción de la imagen aquí
:

	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过滤器

Caso:
prohibir que la llamada al sistema de bifurcación genere procesos secundarios

#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;
}

referencia

Seccomp BPF y seguridad de contenedores

Supongo que te gusta

Origin blog.csdn.net/x646602196/article/details/130137830
Recomendado
Clasificación