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.

Actualizado: 21 de agosto de 2026

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 ABIKernelFechaCapacidades añadidas
v15.13Junio 2021Acceso a rutas del sistema de archivos (13 flags de acceso).
v25.19Julio 2022LANDLOCK_ACCESS_FS_REFER (permite renombrar entre subárboles).
v36.2Febrero 2023LANDLOCK_ACCESS_FS_TRUNCATE (control de truncate independiente de WRITE).
v46.7Enero 2024Control de red: TCP bind y connect por puerto.
v56.10Julio 2024Control de ioctl sobre archivos de dispositivo.
v66.12 LTSNoviembre 2024Scoping 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:

AspectoLandlockseccompAppArmorSELinux
GranularidadRutas, red, señalesSyscalls y argumentosRutas, capabilitiesEtiquetas MAC completas
Requiere rootNoNoSí (política global)Sí (política global)
PolíticaProgramática (código)BPF filterPerfil declarativoMódulo compilado .pp
Distribuciones activadasTodas kernel ≥5.13TodasUbuntu, SUSE, DebianRHEL, Fedora, Amazon Linux
Coste operativoBajo (self-service)BajoMedio (perfiles)Alto (labels, contextos)
Ideal paraContención por procesoReducir superficie de syscallsServicios del sistemaCompliance 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:

// gcc -o mini-sandbox mini-sandbox.c
#define _GNU_SOURCE
#include <linux/landlock.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int add_path(int ruleset_fd, const char *path, __u64 allowed) {
    struct landlock_path_beneath_attr pb = {
        .allowed_access = allowed,
        .parent_fd = open(path, O_PATH | O_CLOEXEC),
    };
    if (pb.parent_fd < 0) { perror(path); return -1; }
    int r = syscall(SYS_landlock_add_rule, ruleset_fd,
                    LANDLOCK_RULE_PATH_BENEATH, &pb, 0);
    close(pb.parent_fd);
    return r;
}

int main(int argc, char **argv) {
    struct landlock_ruleset_attr attr = {
        .handled_access_fs =
            LANDLOCK_ACCESS_FS_READ_FILE   | LANDLOCK_ACCESS_FS_READ_DIR |
            LANDLOCK_ACCESS_FS_WRITE_FILE  | LANDLOCK_ACCESS_FS_TRUNCATE |
            LANDLOCK_ACCESS_FS_MAKE_DIR    | LANDLOCK_ACCESS_FS_MAKE_REG |
            LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR
    };

    int rfd = syscall(SYS_landlock_create_ruleset, &attr, sizeof(attr), 0);
    if (rfd < 0) { perror("create_ruleset"); return 1; }

    if (add_path(rfd, "/etc",
                 LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)) return 1;
    if (add_path(rfd, "/var/lib/miapp",
                 LANDLOCK_ACCESS_FS_READ_FILE  | LANDLOCK_ACCESS_FS_READ_DIR |
                 LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_REG)) return 1;

    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("no_new_privs"); return 1; }
    if (syscall(SYS_landlock_restrict_self, rfd, 0)) { perror("restrict_self"); return 1; }
    close(rfd);

    execvp(argv[1] ? argv[1] : "/bin/sh", argv[1] ? argv + 1 : (char *[]){"/bin/sh", NULL});
    perror("execvp");
    return 1;
}

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.

Como referencia externa útil, el sitio landlock.io del propio mantenedor lista los CVE recientes contenidos por Landlock en fuzzers del kernel, y sirve como argumento para el equipo de compliance. Para vulnerabilidades específicas del sistema anfitrión, sigue siendo obligatorio el escaneo periódico documentado en la guía de escaneo de vulnerabilidades con Lynis, OpenSCAP y Trivy.

Auditoría y verificación de la sandbox

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:

$ grep '^Seccomp\|^NoNewPrivs\|^Landlock' /proc/$(pidof mi-parser)/status
NoNewPrivs:     1
Seccomp:        2
Seccomp_filters:        3

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:

bpftrace -e '
tracepoint:syscalls:sys_exit_openat /args->ret == -13/ {
    printf("EACCES pid=%d comm=%s\n", pid, comm);
}'

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.

Yuki Tanaka
Sobre el Autor Yuki Tanaka

Linux kernel security engineer with a background in eBPF and LSM. Likes hardening more than she likes sleeping.