Guias

eBPF en Linux: guía práctica de observabilidad y seguridad con bpftrace

eBPF en Linux: guía práctica de observabilidad y seguridad con bpftrace

eBPF te permite ejecutar código seguro dentro del kernel sin recompilar ni cargar módulos. Es la razón por la que herramientas como Cilium, Falco, Tetragon o bpftrace existen.

Si trabajas con Linux en producción, eBPF ya está detrás de la red, la seguridad y la observabilidad de tu sistema, lo sepas o no. La documentación oficial de kernel lo describe como “the extended BPF facility”, una evolución de la antigua máquina virtual de Berkeley Packet Filter, pensada ahora para ejecutar programas genéricos dentro del kernel, en sandboxes verificadas, con JIT a nativo.[^1]

Esta guía explica los fundamentos y, sobre todo, cómo se usa con bpftrace, la herramienta high-level que escribió Brendan Gregg sobre libbpf y LLVM.

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 2-3 horas para el switch a enforcing.
  • Requisitos: Root, kernel moderno, 2-4 semanas en permissive antes de enforcing.
  • Resultado: Sistema con MAC activo, logs en /var/log/audit/audit.log.
  • Troubleshooting: audit2allow, audit2why, setenforce 0 para emergencias.

TL;DR

  • eBPF = código que se mete dentro del kernel, lo verifica el verificador, y se compila JIT a instrucciones nativas.[^1][^2]
  • bpftrace es un lenguaje tipo AWK que traduce a eBPF: ideal para one-liners y scripts rápidos.[^3]
  • Casos reales: latencia de read(), syscalls por proceso, fugas de malloc(), eventos de red, detección de execve sospechosos.
  • La seguridad la garantiza el verificador, que rechaza cualquier programa que pueda colgar el kernel o leer memoria arbitraria.[^2]
  • Necesitas CAP_BPF y, en producción moderna, CAP_PERFMON o CAP_SYS_ADMIN para la mayoría de probes.[^1]

1. Qué es eBPF y por qué importa

BPF (Berkeley Packet Filter) nació como una máquina virtual para filtrar paquetes. eBPF (“extended”) la generaliza: ya no filtra solo paquetes, ejecuta cualquier programa C-like en puntos de enganche del kernel: tracepoints, kprobes, uprobes, sockets, XDP, LSM, cgroups, etc.[^1]

El flujo es el siguiente:

1. Escribes un programa (en C con libbpf, o en bpftrace).

2. clang lo compila a un objeto ELF con secciones que el loader entiende.

3. El loader llama a la syscall bpf() para cargar el programa.

4. El verificador lo simula instrucción por instrucción: comprueba que termina, que no se sale de los rangos de memoria permitidos, que las llamadas a helpers son válidas y que el acceso a mapas es seguro.[^2]

5. Si pasa, se compila con JIT a código nativo y se enlaza al hook elegido.

El verificador es la pieza clave. Las reglas son duras: el programa debe ser DAG (sin bucles), no puede tocar punteros sin haberlos comprobado, las escrituras a mapas y stack están limitadas en tamaño, y en modo “secure” ni siquiera se permite aritmética de punteros para que no se filtre una dirección de kernel a espacio de usuario.[^2]

2. libbpf, BTF y CO-RE

libbpf es la librería C de referencia. Carga objetos BPF, los verifica, gestiona mapas y se ocupa del CO-RE (Compile Once – Run Everywhere).[^4] Con CO-RE puedes compilar un programa BPF en una máquina y ejecutarlo en otra con un kernel distinto, gracias al formato BTF (BPF Type Format), que describe las estructuras del kernel y permite al loader resolver relocaciones en tiempo de carga.[^5]

Para una BPF app típica, libbpf expone cuatro fases:[^4]

1. __open() — parsea el ELF, descubre mapas, programas y globals.

2. __load() — crea mapas, resuelve relocaciones, verifica y carga.

3. __attach() — engancha los programas auto-attacheables.

4. __destroy() — libera todo.

bpftool gen skeleton genera un header .skel.h con funciones __open/__load/__attach/__destroy ya cableadas, lo que reduce el código de usuario a unas pocas líneas.

3. bpftrace: trazando el kernel con una línea

bpftrace es un lenguaje inspirado en AWK y C, mantenido por Netflix, Google, Red Hat y la comunidad, que compila a eBPF usando LLVM y libbpf por debajo.[^3] Se distribuye en los repos oficiales de Ubuntu, Debian, Fedora, Arch y openSUSE.

3.1. Instalación

El primer bloque instala bpftrace usando el gestor de paquetes de la distribución que usas; necesitas tenerlo disponible antes de consultar probes o cargar programas eBPF. Elegí solo la línea que corresponde a tu sistema para no mezclar repositorios ni versiones.

Cubre tres familias principales (Debian/Ubuntu con apt, Fedora con dnf, Arch con pacman) porque las dependencias de LLVM y libbpf cambian entre ellas y mezclar repos produce fallos raros. La instalación desde los repos oficiales garantiza la versión correcta para tu kernel.


# Debian/Ubuntu
sudo apt install bpftrace
# Fedora
sudo dnf install bpftrace
# Arch
sudo pacman -S bpftrace

Si el paquete no aparece, habilita los repositorios oficiales de tu distribución y confirmá la versión del kernel antes de seguir. Una instalación incompleta suele terminar en errores de LLVM o libbpf cuando bpftrace intenta compilar el probe.

Comprueba la versión y que tienes permisos:

Este chequeo confirma que el binario está instalado y que el kernel puede ejecutar un probe mínimo antes de empezar a observar eventos reales. También separa un problema de instalación de uno de permisos para cargar BPF.

bpftrace --version imprime versión y rutas; el probe BEGIN { printf("okn") } no engancha nada del kernel, solo ejercita el pipeline de carga para confirmar que el verificador acepta el programa y el JIT puede ejecutarlo.


bpftrace --version
sudo bpftrace -e 'BEGIN { printf("okn"); }'

Si la versión responde pero el probe devuelve Operation not permitted, revisa CAP_BPF, CAP_PERFMON, kernel.unprivileged_bpf_disabled y las políticas de AppArmor o SELinux. La salida ok confirma que la carga y la ejecución básica funcionaron.

Necesitas CAP_BPF + CAP_PERFMON, o ser root.[^1]

3.2. Listar probes disponibles

Consultar los tracepoints disponibles te permite elegir un hook estable y conocer sus argumentos antes de escribir el script. Hacerlo primero evita depender de nombres de funciones internas que pueden cambiar entre versiones del kernel.

bpftrace -l con el wildcard * enumera todos los tracepoints del subsistema syscalls; -l con un nombre concreto y -v te muestra la signatura de cada hook (args->arg0, etc.) para saber qué tipos puedes leer.


# Todos los tracepoints
bpftrace -l 'tracepoint:syscalls:sys_enter_*'
# Argumentos de un tracepoint concreto
bpftrace -vl tracepoint:syscalls:sys_enter_openat

Si no aparece el tracepoint esperado, puede faltar soporte de tracing, BTF o una categoría de eventos en ese kernel; no fuerces un nombre inventado. verifica que la lista tenga resultados y que la consulta detallada muestre los argumentos que vas a usar.

3.3. Los cinco one-liners que más uso

Estos one-liners cubren conteo de syscalls, latencias, archivos abiertos, tamaños de lectura y errores de red en una sola sesión. Ejecutalos como diagnósticos puntuales y cortalos cuando ya tengas la muestra: los mapas agregados crecen mientras el probe sigue enganchado.

Cada uno resuelve una pregunta típica: cuántos calls hace cada proceso, cuánto tarda read(), qué archivos abre un PID, cuál es la distribución de bytes leídos, y dónde falla connect(). El primero y el tercero son buenos para empezar; el cuarto y el quinto sirven para detectar problemas de red sin tcpdump.


# 1. Syscalls por proceso (resumen en Ctrl-C)
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
# 2. Latencia de read() por proceso
sudo bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; }
                  kretprobe:vfs_read /@start[tid]/ {
                    @ns[comm] = hist(nsecs - @start[tid]);
                    delete(@start, tid);
                  }'
# 3. Archivos abiertos por PID
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat
                  /pid == 1234/ {
                    printf("%s [%d] %sn", comm, pid, str(args.filename));
                  }'
# 4. Distribución de tamaños de read()
sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read {
                    @bytes = hist(args.ret);
                  }'
# 5. Tasa de errores en connect()
sudo bpftrace -e 'tracepoint:syscalls:sys_exit_connect /args.ret<0/ {
                    @err[comm] = count();
                  }'

Un tracepoint not found, argumentos inválidos o un rechazo del verificador indican incompatibilidad con el kernel o con la versión de bpftrace; corregí el hook antes de interpretar datos. Si hay demasiados eventos, filtrá por PID o proceso para no generar sobrecarga ni una salida imposible de leer.

@ introduce un mapa, hist() genera un histograma power-of-2, count() cuenta, lhist() es lineal, printf y str() formatean como en C.[^3]

4. Casos reales

4.1. Encontrar el proceso que satura el disco

Este probe agrupa las escrituras por proceso y sirve para identificar quién está generando presión de I/O sin instrumentar la aplicación. Es un primer corte barato antes de pasar a medir bytes, latencia o el dispositivo concreto.

El tracepoint sys_enter_write se dispara cada vez que un proceso llama a write() (no writev, pwrite64 ni sendfile, ojo). El mapa @writes[comm] agrega por nombre de proceso; los datos se imprimen al cancelar con Ctrl-C.


sudo bpftrace -e 'tracepoint:syscalls:sys_enter_write {
                    @writes[comm] = count();
                  }'

Si el resultado queda vacío, el sistema no recibió escrituras durante la captura o el tracepoint no está disponible; provocá una operación de escritura controlada y repetí la observación. No confundas cantidad de llamadas con bytes: un proceso puede llamar muchas veces y escribir poco.

La salida te dirá quién escribe más, por proceso.

4.2. Detectar binarios ejecutándose desde /tmp (señal típica de malware)

Observar execve expone cada lanzamiento de proceso y permite detectar rutas inusuales como /tmp o cachés escribibles. El hook sirve para generar evidencia con PID, proceso y ruta antes de decidir si hay actividad maliciosa.

sys_enter_execve se ejecuta antes de que el kernel cambie la imagen del proceso; args.filename es la ruta que se intenta cargar (puede ser relativa, en cuyo caso conviene resolver con el cwd). Cada línea se imprime en vivo, así que sirve para hacer triage manual durante un incidente.


sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
                    printf("%s [%d] %sn",
                           comm, pid, str(args.filename));
                  }'

Mucho ruido puede venir de servicios legítimos, workers o scripts temporales, así que una ruta sospechosa no prueba por sí sola una intrusión. Confirmá el usuario, el árbol de procesos y la frecuencia de cada evento antes de bloquear o matar un proceso.

Si aparecen ejecuciones frecuentes desde /tmp o ~/.cache, investiga.

4.3. Medir el impacto de una migración a K8s

Agrupar los bytes transmitidos por interfaz muestra cómo se reparte el tráfico durante una migración o una prueba de carga. Esta medición te da una línea base por NIC sin capturar el contenido de los paquetes.

El tracepoint net:net_dev_xmit se dispara en cada dev_queue_xmit() del stack de red con la longitud del frame en args.len. Agregando por ifname se ve cuánto tráfico genera cada interfaz del host (útil para distinguir tráfico de control plane vs data plane en un nodo K8s).


sudo bpftrace -e 'tracepoint:net:net_dev_xmit {
                    @bytes[ifname] = sum(args.len);
                  }'

Si ifname no está disponible en ese tracepoint o la interfaz no genera tráfico, el mapa puede quedar vacío; consultá los argumentos del hook y generá tráfico conocido. compara el total con los contadores de la interfaz para detectar pérdidas por el punto de enganche elegido.

Para interfaces de red concretas, suma bytes transmitidos por interfaz.

4.4. Trazar fugas de memoria en una app (libc malloc)

Engancharte al malloc de libc y agrupar por stack ayuda a localizar qué rutas de código reservan memoria de forma sostenida. La información de los frames orienta la investigación sin recompilar la aplicación.

Un uprobe se attacha a la función malloc exportada por la libc del proceso target; arg0 es el tamaño pedido. ustack(5) captura los cinco frames de userspace para que veas qué call site es responsable; agrupando por stack se ve qué función acumula más reservas.


sudo bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc {
                    @size[ustack(5)] = sum(arg0);
                  }'

La ruta de libc cambia entre distribuciones y arquitecturas, y una aplicación estáticamente enlazada no tendrá ese símbolo; si el uprobe no carga, localizá la biblioteca real y el símbolo exportado. prueba con una carga controlada y vigilá el overhead, porque capturar cada reserva puede ser costoso en procesos muy activos.

ustack(5) muestra los cinco frames superiores. Lo que más suma, es lo que más filtra.

5. Producción: Falco, Tetragon, Cilium

Para observabilidad y seguridad 24/7, bpftrace no es la herramienta adecuada (no persiste). Ahí entran:

  • Falco: usa eBPF en el kernel para detectar actividad sospechosa y la procesa en un userspace daemon. Es el estándar CNCF para runtime security.
  • Tetragon (Cilium): eventos con kernel-side filtering, sin pasar por userspace, latencia sub-milisegundo.
  • Cilium: red de Kubernetes con eBPF para kube-proxy replacement, load balancing, observabilidad L3/L4/L7.

Todos comparten libbpf y BTF por debajo. Si aprendes bpftrace, entiendes lo que ellos hacen.

6. Seguridad y permisos

Cargar BPF no es trivial. El kernel aplica varios controles:

  • CAP_BPF o CAP_SYS_ADMIN (o ser root).
  • En kernels modernos, hace falta también CAP_PERFMON para perf-related events.
  • kernel.unprivileged_bpf_disabled = 1 (sysctl) impide a usuarios sin capabilities cargar BPF; en distros recientes suele estar activo por defecto.
  • Modo "secure" del verificador prohíbe aritmética de punteros en programas que se cargan sin privilegios, evitando fugas de direcciones.[^2]

Si bpftrace se queja con "Operation not permitted", revisa capabilities y sysctl. En sistemas con AppArmor o SELinux estrictos, también hay que asegurarse de que el binario tiene permiso para perf_event_open y bpf.

7. Conclusiones

eBPF es la infraestructura que hace posible la observabilidad y seguridad modernas en Linux sin módulos de kernel. bpftrace es la mejor puerta de entrada: en una línea puedes medir latencias, contar eventos, perfilar y detectar anomalías. Para producción, lo que aprendas aquí se traduce directamente a Falco, Tetragon, Cilium o tus propios programas con libbpf.

Si quieres profundizar, los siguientes recursos son el siguiente paso natural: el libro de Learning eBPF de Liz Rice, los bpftrace internals en docs/internals_development.md del propio repositorio, y los Cilium labs sobre XDP, tc y tracepoints.

8. FAQ

¿Necesito recompilar el kernel para usar eBPF?

No. Cualquier kernel 4.4+ soporta eBPF, y 5.x+ ya trae el verificador y BPF type format que se usan hoy.

¿bpftrace se queda colgado si me equivoco?

No. El verificador rechaza los programas peligrosos en la carga, así que un script mal escrito no compromete el kernel.

¿Qué diferencia hay entre tracepoints y kprobes?

Tracepoints son hooks estáticos, con argumentos estables entre versiones. Kprobes enganchan cualquier función del kernel, pero su signatura cambia entre versiones y debes consultar el código del kernel para entender los argumentos.[^3]

¿Cómo depuro un programa BPF?

bpftool prog dump xlated muestra el bytecode; bpftool prog dump jited muestra el nativo; bpftrace -dd muestra el programa expandido antes de cargarlo.

Preguntas frecuentes

¿Por qué eBPF en Linux: guía práctica de observabilidad y seguridad con bpftrace es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con eBPF en Linux: guía práctica?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con eBPF en Linux: guía práctica?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar eBPF en Linux: guía práctica y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a eBPF en Linux: guía práctica?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *