¡Hola, habitantes de Habr! La máquina virtual BPF es uno de los componentes más importantes del núcleo de Linux. Su uso adecuado permitirá a los ingenieros de sistemas detectar fallas y resolver incluso los problemas más complejos. Aprenderás a crear programas que rastreen y modifiquen el comportamiento del núcleo, podrás implementar código de forma segura para monitorear eventos en el núcleo y mucho más. David Calavera y Lorenzo Fontana te ayudarán a explorar las capacidades de BPF. Amplía tus conocimientos sobre optimización del rendimiento, redes y seguridad. — Usa BPF para rastrear y modificar el comportamiento del núcleo de Linux. — Implementa código para el monitoreo seguro de eventos en el núcleo, sin necesidad de recompilar el núcleo o reiniciar el sistema. — Aprovecha ejemplos de código en C, Go o Python. — Controla la situación, dominando el ciclo de vida del programa BPF.
Seguridad del núcleo de Linux, sus capacidades y Seccomp
BPF proporciona una forma poderosa de extender el núcleo sin comprometer la estabilidad, la seguridad y la velocidad. Por esta razón, los desarrolladores del núcleo pensaron que sería útil utilizar su versatilidad para mejorar la aislación de procesos en Seccomp mediante la implementación de filtros de Seccomp, soportados por programas BPF, conocidos también como Seccomp BPF. En este capítulo, discutiremos qué es Seccomp y cómo se aplica. Luego aprenderás a escribir filtros de Seccomp utilizando programas BPF. Después, revisaremos los ganchos BPF integrados que hay en el núcleo para los módulos de seguridad de Linux.
Los módulos de seguridad de Linux (LSM) son una plataforma que proporciona un conjunto de funcionalidades que se pueden aplicar para una implementación estandarizada de varios modelos de seguridad. LSM puede usarse directamente en el árbol de código fuente del núcleo, por ejemplo, Apparmor, SELinux y Tomoyo.
Comencemos discutiendo las capacidades de Linux.
Funcionalidades
La esencia de las capacidades de Linux radica en que necesitas otorgar a un proceso no privilegiado el permiso para ejecutar una tarea específica, pero sin utilizar suid para este propósito, o de alguna manera hacer que el proceso sea privilegiado, minimizando así la posibilidad de ataques y permitiendo que el proceso ejecute ciertas tareas. Por ejemplo, si tu aplicación necesita abrir un puerto privilegiado, digamos, el 80, en lugar de ejecutar el proceso como root, puedes simplemente otorgarle la capacidad CAP_NET_BIND_SERVICE.
Consideremos un programa Go llamado main.go:
package main
import (
"net/http"
"log"
)
func main() {
log.Fatalf("%v", http.ListenAndServe(":80", nil))
}Este programa ejecuta un servidor HTTP en el puerto 80 (que es un puerto privilegiado). Normalmente lo ejecutamos justo después de la compilación:
$ go build -o capabilities main.go
$ ./capabilitiesSin embargo, dado que no estamos otorgando privilegios de root, este código generará un error al intentar enlazar el puerto:
2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1capsh (herramienta de gestión de shell) es una herramienta que lanza un shell con un conjunto específico de capacidades.
En este caso, como se mencionó anteriormente, en lugar de otorgar plenos derechos de root, se puede permitir que se enlacen puertos privilegiados, otorgando la capacidad cap_net_bind_service junto con todas las demás que ya posee el programa. Para ello, podemos encerrar nuestro programa en capsh:
# capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep'
--keep=1 --user="nobody"
--addamb=cap_net_bind_service -- -c "./capabilities"Desglosamos un poco este comando.
- capsh — usamos capsh como shell.
- —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — dado que necesitamos cambiar de usuario (no queremos ejecutarnos con privilegios de root), especificamos cap_net_bind_service y la capacidad de cambiar de hecho el identificador de usuario de root a nobody, es decir, cap_setuid y cap_setgid.
- —keep=1 — queremos mantener las capacidades establecidas al cambiar de cuenta de root.
- —user="nobody" — el usuario final que ejecutará el programa será nobody.
- —addamb=cap_net_bind_service — especificamos la limpieza de las capacidades relacionadas después de cambiar del modo root.
- — -c "./capabilities" — simplemente ejecutamos el programa.
Las capacidades relacionadas son un tipo especial de capacidades que son heredadas por programas secundarios cuando el programa actual las ejecuta a través de execve(). Solo pueden heredarse las capacidades que están permitidas como relacionadas, o, en otras palabras, como capacidades de entorno.
Probablemente te interese qué significa +eip después de indicar una capacidad en la opción —caps. Estas banderas se utilizan para determinar que la capacidad:
-debe ser activada (p);
-está disponible para su uso (e);
-puede ser heredada por procesos hijos (i).
Dado que queremos usar cap_net_bind_service, debemos hacerlo con la bandera e. Luego ejecutaremos un shell en el comando. Como resultado, se ejecutará el archivo binario de capacidades, y necesitamos marcarlo con la bandera i. Finalmente, queremos que la capacidad esté activada (lo hemos hecho sin cambiar el UID) con p. Esto se ve como cap_net_bind_service+eip.
Puedes verificar el resultado usando ss. Acortaremos un poco la salida para que quepa en la página, pero mostrará el puerto asociado y el identificador de usuario, distintos de 0, en este caso 65 534:
# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0En este ejemplo usamos capsh, pero puedes escribir un shell usando libcap. Para más información, consulta man 3 libcap.
Al escribir programas, el desarrollador a menudo no conoce de antemano todas las capacidades que la aplicación necesitará durante la ejecución; además, en nuevas versiones, estas capacidades pueden cambiar.
Para entender mejor las capacidades de nuestra aplicación, podemos tomar la herramienta BCC capable, que establece un kprobe para la función del núcleo cap_capable:
/usr/share/bcc/tools/capable
TIME UID PID TID COMM CAP NAME AUDIT
10:12:53 0 424 424 systemd-udevd 12 CAP_NET_ADMIN 1
10:12:57 0 1103 1101 timesync 25 CAP_SYS_TIME 1
10:12:57 0 19545 19545 capabilities 10 CAP_NET_BIND_SERVICE 1Lograremos lo mismo utilizando bpftrace con un kprobe de una línea en la función del núcleo cap_capable:
bpftrace -e
'kprobe:cap_capable {
time("%H:%M:%S ");
printf("%-6d %-6d %-16s %-4d %dn", uid, pid, comm, arg2, arg3);
}'
| grep -i capabilitiesEsto generará algo como lo siguiente, si las capacidades de nuestra aplicación están habilitadas después del kprobe:
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 10 1La quinta columna son las capacidades requeridas por el proceso, y dado que estos datos incluyen eventos no auditados, vemos todas las verificaciones no auditadas y, finalmente, la capacidad requerida con la bandera de auditoría (la última en la salida) establecida en 1. La capacidad que nos interesa es CAP_NET_BIND_SERVICE, que se define como una constante en el código fuente del núcleo en el archivo include/uapi/linux/ability.h con el identificador 10:
/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10<source lang="go">Las capacidades suelen ser utilizadas durante la ejecución de contenedores, como runC o Docker, para que funcionen en modo no privilegiado, pero se les permiten solo las capacidades necesarias para ejecutar la mayoría de las aplicaciones. Cuando una aplicación requiere ciertas capacidades, se pueden proporcionar en Docker mediante —cap-add:
docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummyEste comando otorgará al contenedor la capacidad CAP_NET_ADMIN, lo que le permitirá configurar el enlace de red para agregar la interfaz dummy0.
La próxima sección mostrará el uso de capacidades como el filtrado, pero utilizando otro método que nos permitirá implementar nuestros propios filtros programáticamente.
Seccomp
Seccomp significa Secure Computing, es un nivel de seguridad implementado en el núcleo de Linux que permite a los desarrolladores filtrar ciertas llamadas al sistema. Aunque Seccomp es comparable a las capacidades de Linux, su capacidad para manejar ciertas llamadas al sistema lo hace mucho más flexible en comparación con ellas.
Seccomp y las capacidades de Linux no se excluyen mutuamente; a menudo se utilizan juntos para beneficiarse de ambos enfoques. Por ejemplo, podrías querer dar a un proceso la capacidad CAP_NET_ADMIN, pero no permitirle aceptar conexiones a través de un socket, bloqueando las llamadas al sistema accept y accept4.
El método de filtrado de Seccomp se basa en filtros BPF que operan en modo SECCOMP_MODE_FILTER, y el filtrado de llamadas al sistema se lleva a cabo de la misma manera que para los paquetes.
Los filtros Seccomp se cargan utilizando prctl a través de la operación PR_SET_SECCOMP. Estos filtros tienen la forma de un programa BPF, que se ejecuta para cada paquete Seccomp presentado utilizando la estructura seccomp_data. Esta estructura contiene la arquitectura de referencia, un puntero a las instrucciones del procesador durante la llamada al sistema y un máximo de seis argumentos de la llamada al sistema, expresados como uint64.
Así es como se ve la estructura seccomp_data del código fuente del núcleo en el archivo linux/seccomp.h:
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};Como se puede ver en esta estructura, podemos filtrar por llamada al sistema, sus argumentos o una combinación de ellos.
Después de recibir cada paquete, el filtro Seccomp debe realizar el procesamiento para tomar una decisión final y comunicar al núcleo qué hacer a continuación. La decisión final se expresa mediante uno de los valores devueltos (códigos de estado).
— SECCOMP_RET_KILL_PROCESS — finalización de todo el proceso inmediatamente después de filtrar la llamada al sistema que no se ejecuta debido a esto.
— SECCOMP_RET_KILL_THREAD — finalización del hilo actual inmediatamente después de filtrar la llamada al sistema que no se ejecuta debido a esto.
— SECCOMP_RET_KILL — alias para SECCOMP_RET_KILL_THREAD, mantenido por compatibilidad hacia atrás.
— SECCOMP_RET_TRAP — la llamada al sistema está prohibida, y se envía la señal SIGSYS (Llamada al Sistema Incorrecta) a la tarea que la invocó.
— SECCOMP_RET_ERRNO — la llamada al sistema no se ejecuta, y parte del valor devuelto del filtro SECCOMP_RET_DATA se pasa al espacio de usuario como valor errno. Dependiendo de la causa del error, se devuelven diferentes valores de errno. La lista de números de error se encuentra en la siguiente sección.
— SECCOMP_RET_TRACE — se utiliza para notificar al depurador ptrace mediante — PTRACE_O_TRACESECCOMP para interceptar cuando se ejecuta una llamada al sistema, para ver y controlar este proceso. Si el depurador no está conectado, se devuelve un error, errno se establece en -ENOSYS, y la llamada al sistema no se ejecuta.
— SECCOMP_RET_LOG — la llamada al sistema está permitida y registrada en el registro.
— SECCOMP_RET_ALLOW — la llamada al sistema simplemente está permitida.
ptrace es una llamada al sistema para implementar mecanismos de rastreo en un proceso denominado tracee, con la capacidad de observar y controlar la ejecución del proceso. El programa de rastreo puede afectar eficazmente la ejecución y cambiar los registros de memoria del tracee. En el contexto de Seccomp, ptrace se utiliza cuando se invoca el código de estado SECCOMP_RET_TRACE, por lo que el depurador puede prevenir la ejecución de la llamada al sistema e implementar su propia lógica.
Errores de Seccomp
De vez en cuando, al trabajar con Seccomp, te encontrarás con varios errores que se identifican con el valor devuelto del tipo SECCOMP_RET_ERRNO. Para reportar un error, la llamada al sistema seccomp devolverá -1 en lugar de 0.
Se pueden presentar los siguientes errores:
— EACCESS — la parte solicitante no tiene permiso para realizar la llamada al sistema. Esto suele ocurrir porque no tiene privilegios CAP_SYS_ADMIN o no está configurado no_new_privs mediante prctl (hablaremos de esto más adelante);
— EFAULT — los argumentos pasados (args en la estructura seccomp_data) no tienen una dirección válida;
— EINVAL — aquí pueden haber cuatro razones:
- la operación solicitada es desconocida o no es soportada por el núcleo en la configuración actual;
- los flags especificados son inválidos para la operación solicitada;
- la operación incluye BPF_ABS, pero hay problemas con el desplazamiento especificado, que puede exceder el tamaño de la estructura seccomp_data;
- la cantidad de instrucciones pasadas al filtro excede el máximo;
— ENOMEM — no hay suficiente memoria para ejecutar el programa;
— EOPNOTSUPP — la operación indicó que con SECCOMP_GET_ACTION_AVAIL la acción estaba disponible, sin embargo, el núcleo no soporta la devolución en los argumentos;
— ESRCH — hubo un problema al sincronizar otro hilo;
— ENOSYS — no hay un rastreador adjunto a la acción SECCOMP_RET_TRACE.
prctl es una llamada al sistema que permite al programa de espacio de usuario gestionar (establecer y obtener) aspectos específicos del proceso, como el número de bytes, los nombres de los hilos, el modo de computación protegida (Seccomp), privilegios, eventos de Perf, etc.
Seccomp puede parecerle una tecnología de sandbox, pero no es así. Seccomp es una utilidad que permite a los usuarios desarrollar un mecanismo de sandbox. Ahora veamos cómo se crean programas de interacción de usuario utilizando un filtro llamado directamente por la llamada al sistema Seccomp.
Ejemplo de filtro BPF Seccomp
Aquí mostraremos cómo combinar dos acciones discutidas anteriormente, a saber:
— escribiremos un programa Seccomp BPF que se aplicará como filtro con diferentes códigos de retorno dependiendo de las decisiones tomadas;
— cargaremos el filtro utilizando prctl.
Para comenzar, se necesitan los encabezados de la biblioteca estándar y del núcleo de Linux:
#include <errno.h>
#include <linux/audit.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <unistd.h>Antes de intentar ejecutar este ejemplo, debemos asegurarnos de que el núcleo se compile con CONFIG_SECCOMP y CONFIG_SECCOMP_FILTER establecidos en y. En una máquina de trabajo, esto se puede verificar así:
cat /proc/config.gz | zcat | grep -i CONFIG_SECCOMP
El resto del código es una función install_filter, compuesta por dos partes. La primera parte contiene nuestra lista de instrucciones de filtrado BPF:
static int install_filter(int nr, int arch, int error) {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
}; Las instrucciones se establecen utilizando los macros BPF_STMT y BPF_JUMP, definidos en el archivo linux/filter.h.
Analicemos las instrucciones.
— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — el sistema carga y acumula con BPF_LD en forma de palabra BPF_W, los datos de paquete se localizan con un desplazamiento fijo BPF_ABS.
— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — verifica mediante BPF_JEQ si el valor de la arquitectura en la constante del acumulador BPF_K es igual a arch. Si es así, pasa con desplazamiento 0 a la siguiente instrucción, de lo contrario, para emitir un error, salta con desplazamiento 3 (en este caso) porque arch no coincide.
— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — carga y acumula con BPF_LD en forma de palabra BPF_W, que es el número de llamada al sistema, contenido en un desplazamiento fijo BPF_ABS.
— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — compara el número de llamada al sistema con el valor de la variable nr. Si son iguales, pasa a la siguiente instrucción y bloquea la llamada al sistema; de lo contrario, permite la llamada al sistema con SECCOMP_RET_ALLOW.
— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — finaliza el programa con BPF_RET y, como resultado, emite un error SECCOMP_RET_ERRNO con el número de la variable err.
— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — finaliza el programa con BPF_RET y permite la ejecución de la llamada al sistema mediante SECCOMP_RET_ALLOW.
SECCOMP ES CBPF
Tal vez te estés preguntando por qué se utiliza una lista de instrucciones en lugar de un objeto ELF compilado o un programa en C compilado con JIT.Hay dos razones para esto.
• En primer lugar, Seccomp aplica cBPF (BPF clásico), no eBPF, lo que significa que no tiene registros, sino solo un acumulador para almacenar el último resultado de los cálculos, como se puede ver en el ejemplo.
• En segundo lugar, Seccomp recibe un puntero a un arreglo de instrucciones BPF directamente y nada más. Los macros que utilizamos simplemente ayudan a especificar estas instrucciones en una forma conveniente para los programadores.
Si necesitas ayuda adicional para entender esta construcción, considera el pseudocódigo que hace lo mismo:
if (arch != AUDIT_ARCH_X86_64) {
return SECCOMP_RET_ALLOW;
}
if (nr == __NR_write) {
return SECCOMP_RET_ERRNO;
}
return SECCOMP_RET_ALLOW;Después de definir el código del filtro en la estructura socket_filter, es necesario definir sock_fprog, que contiene el código y la longitud del filtro calculada. Esta estructura de datos es necesaria como argumento para la declaración del trabajo del proceso en adelante:
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};Solo queda una cosa por hacer en la función install_filter: ¡cargar el propio programa! Para esto, usamos prctl, tomando PR_SET_SECCOMP como opción, para entrar en modo de cálculo protegido. Luego, indicamos al modo que cargue el filtro usando SECCOMP_MODE_FILTER, que se encuentra en la variable prog del tipo sock_fprog:
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
perror("prctl(PR_SET_SECCOMP)");
return 1;
}
return 0;
}Finalmente, podemos utilizar nuestra función install_filter, pero antes debemos usar prctl para establecer PR_SET_NO_NEW_PRIVS para la ejecución actual, evitando así la situación en la que los procesos hijos obtienen privilegios más amplios que los padres. De esta manera, podemos hacer las siguientes llamadas prctl en la función install_filter sin tener permisos de root.
Ahora podemos llamar a la función install_filter. Bloquearemos todas las llamadas al sistema write relacionadas con la arquitectura X86-64 y simplemente daremos permiso, bloqueando todos los intentos. Después de establecer el filtro, continuamos la ejecución usando el primer argumento:
int main(int argc, char const *argv[]) {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl(NO_NEW_PRIVS)");
return 1;
}
install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
return system(argv[1]);
}Vamos a empezar. Para compilar nuestro programa, podemos usar ya sea clang o gcc, de todos modos, es solo la compilación del archivo main.c sin opciones especiales:
clang main.c -o filter-writeComo se mencionó, hemos bloqueado todas las escrituras en el programa. Para verificar esto, necesitamos un programa que produzca alguna salida, y ls parece un buen candidato. Aquí está cómo se comporta normalmente:
ls -la
total 36
drwxr-xr-x 2 fntlnz users 4096 Abr 28 21:09 .
drwxr-xr-x 4 fntlnz users 4096 Abr 26 13:01 ..
-rwxr-xr-x 1 fntlnz users 16800 Abr 28 21:09 filter-write
-rw-r--r-- 1 fntlnz users 19 Abr 28 21:09 .gitignore
-rw-r--r-- 1 fntlnz users 1282 Abr 28 21:08 main.c
¡Excelente! Así es como se utiliza nuestro programa de shell: simplemente pasamos el programa que queremos probar como primer argumento:
./filter-write "ls -la"Después de ejecutarse, este programa produce una salida completamente vacía. Sin embargo, podemos aplicar strace para ver lo que sucede:
strace -f ./filter-write "ls -la"El resultado está significativamente abreviado, pero la parte correspondiente muestra que las escrituras están bloqueadas con el error EPERM — exactamente el que configuramos. Esto significa que el programa no produce salida porque no puede acceder a la llamada del sistema write:
[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operación no permitida)
[pid 25099] write(2, "error de escritura", 17) = -1 EPERM (Operación no permitida)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operación no permitida)Ahora comprendes cómo funciona Seccomp BPF y tienes una buena idea de lo que se puede lograr con él. Pero, ¿no te gustaría lograr lo mismo usando eBPF en lugar de cBPF, para aprovechar todo su potencial?
Pensando en los programas eBPF, la mayoría de la gente piensa que simplemente los escriben y los cargan con privilegios de administrador. Aunque esta afirmación es en general cierta, el núcleo implementa un conjunto de mecanismos para proteger los objetos eBPF en varios niveles. Estos mecanismos se denominan trampas BPF LSM.
Trampas BPF LSM
Para proporcionar un control de eventos del sistema independiente de la arquitectura, LSM implementa el concepto de trampas. Técnicamente, una llamada a una trampa es similar a una llamada al sistema, pero es independiente del sistema e integrada con la infraestructura. LSM proporciona un nuevo concepto en el que el nivel de abstracción puede ayudar a evitar problemas que surgen al trabajar con llamadas al sistema en diferentes arquitecturas.
En el momento de escribir este libro, el núcleo tenía siete trampas relacionadas con los programas BPF, y SELinux era el único LSM integrado que las implementaba.
El código fuente de las trampas se encuentra en el árbol del núcleo en el archivo include/linux/security.h:
extern int security_bpf(int cmd, union bpf_attr *attr, unsigned int size);
extern int security_bpf_map(struct bpf_map *map, fmode_t fmode);
extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_alloc(struct bpf_map *map);
extern void security_bpf_map_free(struct bpf_map *map);
extern int security_bpf_prog_alloc(struct bpf_prog_aux *aux);
extern void security_bpf_prog_free(struct bpf_prog_aux *aux);Cada uno de ellos se invocará en diferentes etapas de la ejecución:
— security_bpf — realiza una verificación inicial de las llamadas al sistema ejecutadas por BPF;
— security_bpf_map — verifica cuando el núcleo devuelve un descriptor de archivo para el mapa;
— security_bpf_prog — verifica cuando el núcleo devuelve un descriptor de archivo para el programa eBPF;
— security_bpf_map_alloc — verifica si el campo de seguridad dentro de los mapas BPF está inicializado;
— security_bpf_map_free — verifica si se está limpiando el campo de seguridad dentro de los mapas BPF;
— security_bpf_prog_alloc — verifica si se inicializa el campo de seguridad dentro de los programas BPF;
— security_bpf_prog_free — verifica si se está limpiando el campo de seguridad dentro de los programas BPF.
Ahora, al ver todo esto, entendemos: la idea de los interceptores LSM BPF es que pueden proporcionar protección a cada objeto eBPF, asegurando que solo aquellos que tienen los privilegios apropiados pueden realizar operaciones sobre los mapas y programas.
Currículum
La seguridad no es algo que se pueda implementar de manera universal para todo lo que se desea proteger. Es importante poder proteger los sistemas en diferentes niveles y de diversas maneras. Crean lo que crean, la mejor manera de asegurar un sistema es organizar diferentes niveles de protección desde diversas posiciones, de modo que la reducción de la seguridad de un nivel no permita acceder a todo el sistema. Los desarrolladores del núcleo han realizado un gran trabajo al proporcionarnos un conjunto de diferentes capas y puntos de interacción. Esperamos haberle dado una buena idea de qué son las capas y cómo utilizar programas BPF para trabajar con ellas.
Sobre los autores
David Calavera es Director Técnico en Netlify. Ha trabajado en el soporte de Docker y ha participado en el desarrollo de herramientas como Runc, Go y BCC, así como otros proyectos de código abierto. Es conocido por su trabajo en proyectos de Docker y en el desarrollo del ecosistema de plugins de Docker. A David le encantan los gráficos de llamas y siempre busca optimizar el rendimiento.
Lorenzo Fontana trabaja en un equipo de desarrolladores de software de código abierto en Sysdig, donde se ocupa principalmente de Falco, un proyecto de la Cloud Native Computing Foundation que proporciona seguridad para entornos de contenedores y detección de anomalías a través de un módulo del kernel y eBPF. Está apasionado por los sistemas distribuidos, las redes definidas por software, el kernel de Linux y el análisis del rendimiento.
» Para más detalles sobre el libro, puedes consultar
»
»
Para los usuarios de Habr, descuento del 25% con el cupón — Linux
Tras el pago de la versión en papel del libro, se envía el libro electrónico por correo electrónico.
Fuente: habr.com
