Landlock LSM en Linux 2026: Sandboxing Sin Root con ABI v6 y systemd 258
Guía práctica de Landlock LSM en Linux: cómo sandboxear procesos sin privilegios de root con la ABI v6 del kernel 6.12 y la nueva directiva LandlockPaths= de systemd 258. Ejemplos en C, Rust y Go.
Landlock LSM es un módulo de seguridad del kernel de Linux que permite a cualquier proceso, incluso sin privilegios de root, restringirse a sí mismo mediante una sandbox declarativa de acceso a archivos, red y señales. Desde el kernel 5.13 hasta la ABI v6 en el kernel 6.12 (noviembre de 2024), Landlock ha pasado de ser una prueba de concepto a la primera capa práctica de hardening aplicativo sin necesidad de política administrativa. En este artículo repaso su modelo, cuándo elegirlo frente a seccomp o AppArmor, y cómo desplegarlo hoy en producción con systemd 258.
Landlock es un LSM apilable, disponible en el kernel principal desde 5.13 (junio de 2021) y estable en la mayoría de distribuciones desde 2023.
La ABI v6 (kernel 6.12, noviembre de 2024) añade control granular de ioctl sobre dispositivos y refinamientos de red iniciados en la ABI v4.
A diferencia de SELinux o AppArmor, Landlock no requiere permisos de administrador: cualquier binario puede sandboxearse a sí mismo.
systemd 258 (julio de 2025) expone Landlock a los unit files mediante LandlockPaths=, integrándolo con el modelo de servicios existente.
Combinar Landlock (FS, red y señales) con seccomp (syscalls) y namespaces produce una defensa en profundidad que ningún LSM alcanza en solitario.
La detección de la versión de ABI en tiempo de ejecución es obligatoria: fallar de forma degradada evita romper el binario en kernels antiguos.
¿Qué es Landlock LSM y por qué importa en 2026?
Landlock es un Linux Security Module apilable diseñado por Mickaël Salaün e integrado en el kernel Linux 5.13 (junio de 2021). Su objetivo no es sustituir a SELinux o AppArmor, sino cubrir un caso que estos no resuelven bien: permitir que un proceso no privilegiado restrinja voluntariamente sus propias capacidades de acceso, sin depender del administrador del sistema. Sigue el mismo espíritu que seccomp(2), pero operando a un nivel más alto: rutas, redes y señales, en lugar de llamadas al sistema individuales.
La razón por la que Landlock es relevante en 2026 es doble. Primero, la ABI ha madurado hasta la versión 6 (kernel 6.12 LTS, noviembre de 2024) e incorpora control de ioctl sobre dispositivos, scoping de señales entre dominios y filtrado de bind/connect TCP. Segundo, el ecosistema de userspace ya no se limita a la libc: existen bindings estables para Rust (landlock-rs 0.4), Go (landlock-lsm/go-landlock) y, desde julio de 2025, la propia systemd expone la característica en unit files con LandlockPaths=. Esto significa que hoy puedes sandboxear servicios de producción sin escribir una sola línea de C.
Honestamente, he desplegado Landlock en dos escenarios donde SELinux era una carga excesiva: parsers de contenido subido por usuarios (imágenes, PDF) y hooks de webhooks Git. La ganancia operativa es que un desarrollador puede añadir la sandbox en el mismo commit que introduce la funcionalidad, sin coordinar cambios de política con el equipo de seguridad. La ganancia técnica es que un CVE de path traversal en el parser queda contenido dentro de /var/lib/svc/input, aunque el proceso se ejecute como su usuario habitual.
Arquitectura y versiones ABI de Landlock
Landlock funciona sobre tres primitivas: un ruleset, una o más reglas y un enforcement irrevocable. Las tres se gestionan con syscalls introducidas específicamente para el módulo: landlock_create_ruleset(2), landlock_add_rule(2) y landlock_restrict_self(2). Una vez restringido, el proceso (y todos sus descendientes) no puede ampliar los permisos concedidos. La restricción se hereda a través de execve(2), y las reglas nuevas solo pueden ser más estrictas, nunca más laxas. Este modelo monotónico es idéntico al de seccomp filter mode 2 y evita la escalada de privilegios vía reconfiguración.
La progresión de versiones ABI resume su historia:
Versión ABI
Kernel
Fecha
Capacidades añadidas
v1
5.13
Junio 2021
Acceso a rutas del sistema de archivos (13 flags de acceso).
v2
5.19
Julio 2022
LANDLOCK_ACCESS_FS_REFER (permite renombrar entre subárboles).
v3
6.2
Febrero 2023
LANDLOCK_ACCESS_FS_TRUNCATE (control de truncate independiente de WRITE).
v4
6.7
Enero 2024
Control de red: TCP bind y connect por puerto.
v5
6.10
Julio 2024
Control de ioctl sobre archivos de dispositivo.
v6
6.12 LTS
Noviembre 2024
Scoping de señales y sockets abstractos Unix entre dominios.
Detectar la ABI en tiempo de ejecución es obligatorio si tu binario debe funcionar en un rango amplio de distribuciones. La llamada landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION) devuelve la versión soportada por el kernel actual. Debian 12 (bookworm) trae 6.1, Ubuntu 24.04 LTS trae 6.8, y RHEL 10 (mayo de 2025) trae 6.12; no puedes asumir v6 en todas partes. La estrategia habitual es enmascarar las flags no soportadas con landlock_add_rule devolviendo EINVAL silenciosamente, algo que la biblioteca landlock-rs automatiza con su enum ABI.
Landlock vs seccomp vs AppArmor vs SELinux
La confusión más frecuente en revisiones de código es proponer Landlock donde seccomp sería más apropiado, o viceversa. Cada mecanismo ocupa un nicho distinto y son complementarios, no alternativos. He resumido las diferencias operativas en la siguiente tabla:
Aspecto
Landlock
seccomp
AppArmor
SELinux
Granularidad
Rutas, red, señales
Syscalls y argumentos
Rutas, capabilities
Etiquetas MAC completas
Requiere root
No
No
Sí (política global)
Sí (política global)
Política
Programática (código)
BPF filter
Perfil declarativo
Módulo compilado .pp
Distribuciones activadas
Todas kernel ≥5.13
Todas
Ubuntu, SUSE, Debian
RHEL, Fedora, Amazon Linux
Coste operativo
Bajo (self-service)
Bajo
Medio (perfiles)
Alto (labels, contextos)
Ideal para
Contención por proceso
Reducir superficie de syscalls
Servicios del sistema
Compliance multitenant
La regla que suelo aplicar: seccomp filtra lo que puede llamar el proceso; Landlock filtra a qué recursos apuntan esas llamadas; AppArmor y SELinux imponen políticas administrativas sobre el sistema entero. Un servicio bien blindado en 2026 combina los tres primeros: seccomp para bloquear ptrace, keyctl y familia; Landlock para restringir escrituras a un directorio de trabajo; y, si estás en una distro con AppArmor o SELinux activado, un perfil complementario que impone la política mínima a nivel de sistema. Cubro esta arquitectura en más detalle en la guía práctica de MAC con SELinux y AppArmor.
Primer sandbox en C con la ABI del kernel
Antes de recurrir a una biblioteca, conviene entender la ABI directa. El siguiente ejemplo mínimo restringe el proceso a lectura de /etc y lectura/escritura en /var/lib/miapp, y luego ejecuta un shell. Es el equivalente al ejemplo sandboxer que se distribuye con la documentación oficial del kernel, adaptado para claridad:
Dos detalles importantes. Primero, PR_SET_NO_NEW_PRIVS es obligatorio para procesos sin CAP_SYS_ADMIN, ya que Landlock rehúsa aplicar restricciones si un binario pudiera luego ganar privilegios vía SUID. Es el mismo requisito de seccomp, y explica por qué combinarlos es natural. Segundo, el descriptor devuelto por landlock_create_ruleset puede cerrarse tras restrict_self (el kernel mantiene una referencia interna); olvidar cerrarlo es una fuga de descriptores en servicios de larga duración.
Bindings prácticos: landlock-rs y go-landlock
Escribir syscalls a mano es útil para entender la ABI, pero en producción se usa un binding. En Rust, landlock 0.4 ofrece una API de builder que negocia la ABI automáticamente. Este es el equivalente al ejemplo anterior en 15 líneas:
// Cargo.toml: landlock = "0.4"
use landlock::{Access, AccessFs, ABI, Ruleset, RulesetAttr, RulesetCreatedAttr,
path_beneath_rules, RulesetStatus};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let abi = ABI::V6;
let ro: Vec<AccessFs> = AccessFs::from_read(abi).into_iter().collect();
let rw = AccessFs::from_all(abi);
let status = Ruleset::default()
.handle_access(AccessFs::from_all(abi))?
.create()?
.add_rules(path_beneath_rules(&["/etc"], ro))?
.add_rules(path_beneath_rules(&["/var/lib/miapp"], rw))?
.restrict_self()?;
if status.ruleset == RulesetStatus::NotEnforced {
eprintln!("Landlock no soportado; abortando");
std::process::exit(1);
}
Ok(())
}
La biblioteca informa mediante RulesetStatus si el kernel soportaba todas las capacidades solicitadas, algunas, o ninguna. El patrón recomendado es tratar PartiallyEnforced como un warning, pero seguir ejecutando: es preferible una sandbox parcial en un kernel 6.1 a fallar por completo. En Go, la biblioteca github.com/landlock-lsm/go-landlock sigue un patrón similar con landlock.V6.BestEffort().RestrictPaths(...), lo que permite el mismo código en Debian 12 y en Ubuntu 24.04 sin condicionales.
systemd 258 y la directiva LandlockPaths=
La novedad más importante de 2025 para operadores es que systemd 258 (julio de 2025) introdujo la directiva LandlockPaths= en unit files. Esto elimina la necesidad de instrumentar cada servicio: puedes envolverlo desde la configuración. La sintaxis es similar a la de BindPaths=:
[Service]
ExecStart=/usr/bin/mi-parser --config /etc/mi-parser.yaml
DynamicUser=yes
# systemd 258+: Landlock declarativo en la unit
LandlockPaths=~/var/lib/mi-parser:rw /etc/mi-parser.yaml:ro /usr/share/mi-parser:ro
# Complemento con seccomp y otras defensas systemd
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
SystemCallFilter=@system-service
SystemCallArchitectures=native
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
El prefijo ~ hace que la línea sea opcional si el kernel no soporta Landlock, siguiendo la convención de otras directivas systemd. La combinación con SystemCallFilter=, ProtectSystem= y DynamicUser= produce una sandbox que, en la práctica, cubre lo que hace cinco años requería un contenedor completo. He documentado la lista completa de directivas de hardening systemd (ProtectKernelTunables, ProtectKernelModules, MemoryDenyWriteExecute, y demás) en la guía de hardening de servicios systemd con ProtectSystem y NoNewPrivileges, que sigue siendo la base recomendada antes de añadir Landlock encima.
Casos de uso en producción y errores comunes
Según lo que he visto en el terreno, cuatro casos justifican Landlock con más claridad. Primero, parsers de contenido no confiable: convert (ImageMagick), pdftotext, ffmpeg. Segundo, runners CI de tareas de usuarios: cada job restringido al directorio de trabajo antes de execve. Tercero, build tools con extensiones cargadas dinámicamente: bundlers de JavaScript, hooks de git server. Cuarto, procesos que renuncian a permisos progresivamente tras completar su fase de setup, un patrón conocido como privilege dropping. Este último es especialmente elegante en Landlock porque cada llamada a landlock_restrict_self apila una nueva capa de restricción sin poder deshacer las anteriores.
Los errores que más veo:
Olvidar la resolución de enlaces simbólicos: Landlock evalúa el path final tras resolver symlinks. Si tu binario abre /tmp/session.log y ese enlace apunta fuera del árbol permitido, obtendrás EACCES. (Me pasó exactamente esto en mi último despliegue con un logger que rotaba a un directorio distinto.)
Confundir WRITE_FILE con crear archivos nuevos: para crear un archivo se necesita también MAKE_REG. Cada operación de creación (directorio, socket, FIFO, dispositivo) tiene su propia flag.
Ignorar el network scoping de ABI v4: aunque restrinjas rutas, un proceso puede seguir hablando al bus systemd por socket abstracto Unix hasta que uses el scoping introducido en v6.
Aplicar Landlock antes de descartar file descriptors: los descriptores abiertos previamente sobreviven a la sandbox. Cierra todo lo innecesario primero.
No probar en kernels antiguos: si tu público incluye Debian 11 o RHEL 8, tu binario debe seguir funcionando cuando Landlock no esté disponible.
La verificación es donde muchos despliegues fallan. A diferencia de AppArmor o SELinux, Landlock no emite eventos audit por defecto para cada denegación (esto llegará gradualmente en el kernel 6.13+ vía LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF), así que las herramientas tradicionales de ausearch no bastan. Tres técnicas que sí funcionan hoy:
Primero, comprobar que un proceso está efectivamente sandboxeado leyendo /proc/PID/status:
Nota: la línea Landlock: se añadió en el kernel 6.10; en kernels anteriores usa capsh --print --pid=$PID o instrumenta el propio proceso para exponer un endpoint de estado.
Segundo, usar strace -e trace=%file sobre pruebas de regresión que intenten violaciones intencionales: cada intento debe devolver EACCES. Automatiza este test en CI para detectar regresiones cuando alguien añade un open() a un path no listado.
Tercero, para observabilidad en producción, engancha un programa eBPF con bpftrace al tracepoint lsm/file_open y correlaciona los EACCES con el PID del servicio. Este patrón, junto con seguridad runtime basada en eBPF, lo detallo en la comparativa de Falco, Tetragon y Tracee. Para Landlock en concreto, un one-liner de bpftrace es suficiente:
Combinando esta observabilidad con los principios generales de hardening del kernel Linux con sysctl y Lockdown LSM, obtienes un ciclo completo: prevención (Landlock, seccomp), detección (eBPF) y contención (Lockdown, Secure Boot). Es la arquitectura que recomiendo para servicios expuestos a Internet en 2026.
Preguntas frecuentes
¿Landlock reemplaza a AppArmor o SELinux?
No. Landlock es un LSM apilable que complementa a AppArmor y SELinux; los tres pueden estar activos simultáneamente y el kernel aplica la intersección de sus decisiones. Landlock añade sandbox a nivel de proceso sin root, mientras que AppArmor y SELinux imponen política administrativa a nivel de sistema.
¿Qué versión del kernel de Linux necesito para usar Landlock?
Como mínimo 5.13 (junio de 2021) para la funcionalidad básica de FS. Para control de red necesitas 6.7 (ABI v4), y para ioctl y scoping de señales/sockets abstractos necesitas 6.10 (v5) y 6.12 (v6) respectivamente. Debian 12 trae v3, Ubuntu 24.04 trae v4 y RHEL 10 trae v6.
¿Puedo usar Landlock dentro de un contenedor Docker o Podman?
Sí, siempre que el runtime no lo bloquee. Docker y Podman heredan la política LSM del kernel del host y Landlock funciona dentro del contenedor sin configuración adicional. La excepción son perfiles seccomp que bloqueen las syscalls landlock_*; el perfil por defecto de Docker las permite desde 20.10.
¿Landlock protege contra escaladas de privilegios con SUID?
Indirectamente, sí. Landlock exige PR_SET_NO_NEW_PRIVS antes de aplicarse en procesos no privilegiados, lo que bloquea automáticamente cualquier transición SUID durante execve. Un proceso sandboxeado no puede ganar privilegios ejecutando /usr/bin/sudo ni ningún binario SUID.
¿Cómo se depuran las denegaciones de Landlock?
En kernels <6.13 no hay logging automático: usa strace -e trace=%file,network y busca retornos EACCES, o engancha un programa bpftrace al tracepoint sys_exit_openat filtrando ret == -13. El kernel 6.13+ añadirá auditd nativo mediante flags específicas de landlock_restrict_self.
Aprende a aplicar sandboxing declarativo a tus unidades systemd con ProtectSystem, NoNewPrivileges, SystemCallFilter y DynamicUser. Mide el impacto con systemd-analyze security y endurece un servicio real de 9.6 (UNSAFE) a 1.4 (SAFE).
Guía práctica de hardening del kernel Linux en 2026: parámetros sysctl, KASLR, KPTI, Lockdown LSM en modo confidentiality, Secure Boot con claves propias y restricción de io_uring y user namespaces, con auditoría CIS.
Guía práctica de cifrado de disco con LUKS2 en Linux 2026: Argon2id como KDF, desbloqueo automático con TPM 2.0 y systemd-cryptenroll, NBDE con Clevis/Tang, rotación de claves y backups del header LUKS.