Landlock su Linux 2026: Sandboxing Unprivileged con LSM Nativo del Kernel

Landlock è l'LSM unprivileged del kernel Linux 5.13+ che permette a un processo di sandboxarsi senza CAP_SYS_ADMIN. Guida 2026: API delle tre syscall, esempi in C, integrazione con systemd, Docker, Chromium, confronto con seccomp, AppArmor e SELinux.

Landlock Linux 2026: Guida Sandbox LSM

Aggiornato: 28 Luglio 2026

Landlock è un Linux Security Module (LSM) unprivileged mainlined nel kernel 5.13 (giugno 2021) che permette a qualsiasi processo, anche senza CAP_SYS_ADMIN, di applicare a se stesso e ai propri discendenti restrizioni granulari sul filesystem, sulla rete TCP e sulle IPC. A differenza di AppArmor o SELinux, non richiede policy centralizzate né privilegi di root: il programma dichiara ciò di cui ha bisogno tramite tre syscall (landlock_create_ruleset, landlock_add_rule, landlock_restrict_self) e il kernel nega tutto il resto. Nel 2026, con l'ABI v6 disponibile da kernel 6.12 LTS, Landlock è supportato da systemd, Chromium, Firefox e dai principali runtime container.

  • Landlock è un LSM basato su stack, disponibile dal kernel 5.13; l'ABI corrente è la v6 nel kernel 6.12 LTS (novembre 2024).
  • Le regole sono cumulative e immutabili: un processo può solo aggiungere restrizioni, mai rimuoverle, e le regole si ereditano attraverso execve().
  • Le tre syscall che compongono l'API (444, 445, 446) permettono sandboxing senza CAP_SYS_ADMIN, cosa impossibile con seccomp filter puramente path-based.
  • Dal kernel 6.7 Landlock copre bind(2) e connect(2) TCP; dal 6.12 include scoping di segnali UNIX e abstract socket.
  • Il costo prestazionale medio misurato dal team upstream è <3% sui carichi I/O-bound, grazie all'uso di trie rbtree per il lookup dei percorsi.

Cos'è Landlock in Linux?

Landlock è un Linux Security Module basato su stackable LSM, ideato da Mickaël Salaün di ANSSI e integrato ufficialmente nel kernel Linux 5.13 nel giugno 2021 (commit 17ae69aba89d). Il suo scopo è colmare un vuoto storico dell'ecosistema Linux: permettere a un processo non privilegiato di limitare le proprie capacità di accesso al kernel senza toccare policy globali gestite dall'amministratore.

Da un punto di vista architetturale, Landlock si aggancia agli hook del framework LSM (security_file_open, security_path_mkdir, security_socket_bind, ecc.) e valuta ogni accesso rispetto a un ruleset costruito in userspace. La caratteristica che lo distingue da AppArmor e SELinux è la direzionalità: le restrizioni si propagano solo verso il basso, dal processo che le impone verso i suoi discendenti creati con fork(2)/clone(2). Non c'è modo di rimuoverle, nemmeno con un exec verso un binario setuid, cosa esplicitamente prevista dal design tramite il flag PR_SET_NO_NEW_PRIVS, che va impostato prima della restrizione.

In pratica: un processo che si auto-limita a leggere solo /etc/ssl/certs e scrivere solo /var/log/miaapp continuerà a rispettare quelle regole anche se un attaccante riesce a farlo tornare a UID 0 tramite un exploit locale. Questo modello (chiamato unprivileged sandboxing) è ciò che rende Landlock complementare, non alternativo, alle MAC tradizionali. Ho scoperto la potenza di questa proprietà proprio dopo un red team interno, dove un privilege escalation locale è stato neutralizzato dalle regole imposte al processo web.

Versioni del kernel e ABI Landlock nel 2026

Landlock usa un contratto ABI versionato in modo lineare: ogni nuovo tipo di regola o flag incrementa il numero di ABI, che il programma può interrogare con landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION). Questo permette il cosiddetto best-effort sandboxing: un binario compilato per ABI v6 può funzionare correttamente su un kernel v3 semplicemente ignorando le regole non supportate, invece di fallire.

Ecco la timeline delle release rilevanti nel 2026:

  • ABI v1 (kernel 5.13, giugno 2021): filesystem read/write/execute, LANDLOCK_ACCESS_FS_* di base.
  • ABI v2 (kernel 5.19, agosto 2022): LANDLOCK_ACCESS_FS_REFER, gestione dei rename tra directory.
  • ABI v3 (kernel 6.2, febbraio 2023): LANDLOCK_ACCESS_FS_TRUNCATE, controllo di truncate(2) e O_TRUNC.
  • ABI v4 (kernel 6.7, gennaio 2024): rete TCP con LANDLOCK_ACCESS_NET_BIND_TCP e LANDLOCK_ACCESS_NET_CONNECT_TCP.
  • ABI v5 (kernel 6.10, luglio 2024): LANDLOCK_ACCESS_FS_IOCTL_DEV, restrizione di ioctl sui device.
  • ABI v6 (kernel 6.12 LTS, novembre 2024): scoping IPC con LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET e LANDLOCK_SCOPE_SIGNAL.

Nel 2026, il kernel 6.12 è la nuova serie LTS mantenuta fino a dicembre 2026 secondo la tabella ufficiale delle release del kernel, quindi rappresenta il target realistico per hardening di produzione. Debian 13 (Trixie) fornisce kernel 6.12; Ubuntu 26.04 LTS è previsto con kernel 6.14, che aggiunge minor extension all'ABI v6 ma non introduce ancora la v7.

Landlock vs seccomp, AppArmor e SELinux

La domanda che ricevo più spesso durante audit di hardening è "perché non basta seccomp?" oppure "meglio Landlock o AppArmor?". Onestamente, la risposta corretta è che risolvono problemi diversi. Ho preparato la tabella qui sotto sulla base della documentazione ufficiale landlock.io e dell'esperienza operativa su cluster di produzione:

CaratteristicaLandlockseccomp-bpfAppArmorSELinux
Richiede rootNoNoSì (per il caricamento profilo)
Granularità path filesystemAlta (path-based)Nulla (solo syscall)Alta (path-based)Alta (label-based)
Controllo argomenti syscallSemanticoSolo scalariPath e capabilityLabel + tipo
Sandbox rete TCPSì (dal kernel 6.7)Sì (indiretto)Parziale
Policy centralizzataNo (embedded nel programma)NoSì (/etc/apparmor.d)Sì (booleans, moduli)
Immutabile dopo il setSolo tramite change_hatSì per il dominio
Overhead misurato< 3%< 1%~ 5%~ 7%

La conclusione operativa: seccomp e Landlock lavorano bene insieme, non in alternativa. Uso seccomp per bloccare intere famiglie di syscall potenzialmente pericolose (ptrace, keyctl, bpf, userfaultfd) e Landlock per restringere il perimetro filesystem/rete al minimo necessario. AppArmor e SELinux rimangono la scelta corretta per policy imposte dall'esterno del programma, tipico in ambienti multi-tenant. Per un confronto sui trade-off dei runtime security eBPF-based, consiglio la lettura del nostro confronto tra Falco e Tetragon per la runtime security eBPF.

L'API: le tre syscall di Landlock spiegate

Landlock espone esattamente tre syscall, con numeri stabilizzati nel kernel 5.13:

  • landlock_create_ruleset(2) (syscall 444): crea un file descriptor che rappresenta un ruleset ancora vuoto, dichiarando quali tipi di accesso saranno gestiti.
  • landlock_add_rule(2) (syscall 445): aggiunge una singola regola al ruleset (es. "concedi accesso lettura al percorso /etc").
  • landlock_restrict_self(2) (syscall 446): applica il ruleset al processo chiamante, momento dopo il quale ogni accesso non esplicitamente autorizzato viene negato dal kernel.

La sequenza è unidirezionale: una volta chiamato landlock_restrict_self, non c'è modo di allentare le regole. Anche execve preserva le restrizioni, e le regole applicate sono l'intersezione di tutti i ruleset accumulati dai padri. È proprio la proprietà che rende Landlock componibile in scenari con processi che si auto-restringono in profondità (Chromium fa esattamente questo per ogni tab).

Un requisito critico, che ho visto sfuggire più di una volta: prima di chiamare landlock_restrict_self, il processo deve aver invocato prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0). Senza NO_NEW_PRIVS, un binario setuid potrebbe teoricamente aggirare le regole, e il kernel rifiuta la restrizione con EPERM. Questo comportamento è documentato nella documentazione ufficiale userspace-api/landlock ed è simmetrico a seccomp-bpf.

Esempio pratico in C: sandbox del filesystem

Il codice qui sotto mostra un template di sandboxing che uso come punto di partenza per applicazioni self-hardening. Compila con gcc -o sandbox sandbox.c su qualsiasi distribuzione con kernel ≥ 6.7 e glibc ≥ 2.38 (che aggiunge il wrapper syscall). L'ho usato in un progetto interno per contenere un binario terzo di parsing PDF che non riuscivo a mettere sotto container: due giorni di lavoro, un exploit potenziale in meno.

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/landlock.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <unistd.h>

/* Wrapper syscall (glibc < 2.38 non li fornisce). */
static inline int landlock_create_ruleset(
    const struct landlock_ruleset_attr *attr, size_t size, __u32 flags) {
    return syscall(__NR_landlock_create_ruleset, attr, size, flags);
}
static inline int landlock_add_rule(int fd, enum landlock_rule_type type,
                                    const void *attr, __u32 flags) {
    return syscall(__NR_landlock_add_rule, fd, type, attr, flags);
}
static inline int landlock_restrict_self(int fd, __u32 flags) {
    return syscall(__NR_landlock_restrict_self, fd, flags);
}

/* Autorizza un percorso in sola lettura. */
static int allow_read(int ruleset_fd, const char *path) {
    struct landlock_path_beneath_attr pb = {
        .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
                          LANDLOCK_ACCESS_FS_READ_DIR,
        .parent_fd = open(path, O_PATH | O_CLOEXEC),
    };
    if (pb.parent_fd < 0) {
        fprintf(stderr, "open %s: %s\n", path, strerror(errno));
        return -1;
    }
    int ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
                                &pb, 0);
    close(pb.parent_fd);
    return ret;
}

int main(int argc, char **argv) {
    /* 1. Verifichiamo l'ABI disponibile (best-effort). */
    int abi = landlock_create_ruleset(NULL, 0,
                                       LANDLOCK_CREATE_RULESET_VERSION);
    if (abi < 4) {
        fprintf(stderr, "Landlock ABI %d insufficiente (serve >= 4)\n", abi);
        return 1;
    }

    /* 2. Dichiariamo l'insieme di accessi che gestiremo. */
    struct landlock_ruleset_attr attr = {
        .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
                             LANDLOCK_ACCESS_FS_READ_DIR  |
                             LANDLOCK_ACCESS_FS_WRITE_FILE,
    };
    int ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
    if (ruleset_fd < 0) {
        perror("landlock_create_ruleset");
        return 1;
    }

    /* 3. Aggiungiamo esplicitamente i percorsi consentiti. */
    if (allow_read(ruleset_fd, "/etc/ssl/certs") ||
        allow_read(ruleset_fd, "/usr/lib") ||
        allow_read(ruleset_fd, "/usr/share")) {
        return 1;
    }

    /* 4. NO_NEW_PRIVS è prerequisito. */
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl NO_NEW_PRIVS");
        return 1;
    }

    /* 5. Applichiamo il ruleset al processo corrente. */
    if (landlock_restrict_self(ruleset_fd, 0)) {
        perror("landlock_restrict_self");
        return 1;
    }
    close(ruleset_fd);

    /* Da qui in poi, ogni open() al di fuori dei tre path fallisce con EACCES. */
    execvp(argv[1], &argv[1]);
    perror("execvp");
    return 1;
}

Compilato ed eseguito come ./sandbox cat /etc/passwd, produce Permission denied: /etc/passwd non è coperto dalla regola /etc/ssl/certs e il kernel nega l'openat(2). È esattamente il comportamento voluto, senza eccezioni per UID 0 e senza interazione con AppArmor o SELinux.

Sandboxing di rete TCP dal kernel 6.7

Fino al kernel 6.6, Landlock era limitato al filesystem. Con la v4 dell'ABI (6.7, gennaio 2024) sono arrivati due nuovi tipi di accesso: LANDLOCK_ACCESS_NET_BIND_TCP e LANDLOCK_ACCESS_NET_CONNECT_TCP. Non coprono ancora UDP, ICMP, né socket raw. È una scelta intenzionale del maintainer per evitare interferenze con eBPF cgroup socket filter, secondo la mailing list linux-security-module dell'ottobre 2023.

Il pattern d'uso è simmetrico a path_beneath: si dichiara la porta e il tipo di operazione. Ecco un esempio minimale che permette al processo di ascoltare solo sulla porta 8080 e di connettersi solo alla 443:

struct landlock_ruleset_attr attr = {
    .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
                          LANDLOCK_ACCESS_NET_CONNECT_TCP,
};
int fd = landlock_create_ruleset(&attr, sizeof(attr), 0);

struct landlock_net_port_attr bind_rule = {
    .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
    .port           = 8080,
};
landlock_add_rule(fd, LANDLOCK_RULE_NET_PORT, &bind_rule, 0);

struct landlock_net_port_attr conn_rule = {
    .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
    .port           = 443,
};
landlock_add_rule(fd, LANDLOCK_RULE_NET_PORT, &conn_rule, 0);

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

Dopo la restrizione, qualsiasi bind() verso una porta diversa da 8080 e qualsiasi connect() verso una destinazione diversa dalla 443 (indipendentemente dall'indirizzo IP) ritorna EACCES. È il primitivo di sandboxing di rete più leggero disponibile su Linux senza uso di namespace, ideale per web application che devono parlare con un endpoint noto e nulla più. Combinato con la restrizione filesystem, riduce drasticamente la superficie utilizzabile da un attaccante che ottiene RCE nel processo.

Landlock e systemd: come sfruttarlo senza scrivere codice

Non tutti gli operatori vogliono ricompilare un binario. La buona notizia è che systemd 253+ (marzo 2023) integra Landlock nativamente, e systemd 257 (dicembre 2024, presente in Debian 13 e Ubuntu 26.04) espone anche le direttive di sandboxing di rete. La direttiva chiave nel file unit è ProtectSystem=strict combinata con ReadOnlyPaths=, ReadWritePaths= e InaccessiblePaths=. Sotto il cofano, systemd traduce queste direttive in regole Landlock ove il kernel le supporta, altrimenti ricade su namespace mount.

Esempio pratico per un servizio web:

[Service]
ExecStart=/usr/bin/miaapp
DynamicUser=yes
NoNewPrivileges=yes

# Restrizioni filesystem (tradotte in Landlock ove disponibile)
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/miaapp /var/log/miaapp
PrivateTmp=yes

# Restrizioni di rete (systemd 257+ con kernel 6.7+)
RestrictAddressFamilies=AF_INET AF_INET6
SocketBindDeny=any
SocketBindAllow=8080

# seccomp e capability aggiuntivi
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
CapabilityBoundingSet=

Per verificare che l'unit sia effettivamente hardened, uso systemd-analyze security miaapp.service: dalla 257 il tool assegna esplicitamente punteggio positivo alla presenza di SocketBindDeny e mostra "Landlock" nella colonna Enforced by quando applicabile. Per un approccio complementare basato su MAC classico su servizi legacy, il nostro articolo su difesa in profondità con seccomp e AppArmor copre le sinergie con container runtime.

Come Docker, Podman e Chromium usano Landlock

Nel 2026 l'adozione di Landlock nei runtime container è ancora parziale, ma in crescita:

  • runc 1.2+ (rilasciato in novembre 2024) accetta profili Landlock via annotazioni OCI org.opencontainers.landlock.*, ma solo se il kernel host supporta ABI ≥ 4. Docker Engine 27 e Podman 5 espongono queste annotazioni tramite l'opzione --security-opt landlock=....
  • Kubernetes non ha ancora un supporto di prima classe: la RuntimeClass rimane il meccanismo primario per gvisor o kata. Un KEP in stato alpha propone un campo landlockProfile nel SecurityContext, ma non è ancora nella milestone v1.32.
  • Chromium (dalla release 116, agosto 2023) usa Landlock nel processo renderer di Linux come layer aggiuntivo sopra il namespace sandbox. Ogni tab ottiene un ruleset che vieta l'accesso alla home directory.
  • Firefox ha introdotto un supporto sperimentale con Landlock nella versione 116 (2023) e nel 2026 è attivo per default sul processo content.
  • systemd-nspawn e Bubblewrap (usato da Flatpak) applicano Landlock ove disponibile, migliorando le garanzie del sandbox oltre i namespace.

Un caveat pratico che ho toccato con mano: dentro un container OCI, il ruleset Landlock viene applicato al processo del container, non al runtime. Ciò significa che il kernel host deve essere ≥ 6.7 e che l'applicazione nel container deve chiamare le syscall Landlock direttamente. Non c'è modo, al 2026, di iniettare regole Landlock dall'esterno del processo target, cosa che invece SELinux e AppArmor permettono di fare a livello di label.

Limitazioni, trade-off e quando non usarlo

Landlock non è una panacea. Le limitazioni che devo elencare durante ogni review di sicurezza:

  • No wildcard/glob: le regole path_beneath sono ancorate a un directory FD reale; non è possibile esprimere pattern come /tmp/*.log. Se serve, si combina con inotify o si struttura l'applicazione in modo che il pattern non sia necessario.
  • Nessun controllo dei permessi POSIX classici: Landlock è ortogonale a chmod. Se un file è mode 0600 di un altro utente, il kernel rifiuta l'accesso anche se il ruleset lo permette. Le regole aggiungono solo restrizioni, non sono grant.
  • No UDP, no ICMP, no netlink fino all'ABI v6. Se l'applicazione usa DNS, il resolver libc apre socket UDP che restano non filtrati da Landlock. Per uno sandboxing di rete completo serve ancora eBPF cgroup socket filter o netfilter.
  • Interazione con FUSE: i filesystem FUSE sono trattati come opachi dal punto di vista dei path Landlock. Su cluster che usano rclone-mount o mergerfs, le regole di lettura si applicano al mountpoint, non al backing store.
  • Coesistenza con io_uring: Landlock intercetta gli hook LSM, e io_uring rispetta gli stessi hook dal kernel 5.13. Tuttavia le regole di fixed file pre-registrate all'apertura di io_uring possono restare valide anche dopo la restrizione, un dettaglio che ho approfondito nel nostro audit del punto cieco di io_uring nella sicurezza Linux.

Il mio consiglio operativo per il 2026: usate Landlock in ogni applicazione che scrivete voi (server web, CLI, worker), combinatelo con seccomp-bpf per il livello syscall, mantenete AppArmor o SELinux come baseline di sistema per i binari di terze parti che non potete modificare. È una difesa in profondità classica; nessuno di questi strumenti da solo copre l'intera superficie di attacco.

Domande Frequenti

Landlock è meglio di AppArmor?

Non è una domanda ben posta: risolvono problemi diversi. Landlock è pensato per applicazioni che si auto-limitano senza privilegi, AppArmor per policy imposte a livello di sistema da un amministratore. In produzione uso entrambi: AppArmor come baseline per binari di terze parti, Landlock nei processi che controllo.

Quale versione del kernel supporta Landlock?

Landlock è disponibile dal kernel 5.13 (giugno 2021) con ABI v1. Le funzionalità di rete richiedono almeno il 6.7 (gennaio 2024), l'IOCTL device il 6.10 e lo scoping dei segnali il 6.12. Il target consigliato nel 2026 è il kernel 6.12 LTS.

Landlock funziona in Docker o Podman?

Sì, ma con caveat. Il kernel host deve avere Landlock abilitato (i kernel Debian/Ubuntu/Fedora recenti lo includono di default) e il runtime deve permettere le syscall 444, 445, 446 nel seccomp profile. Docker Engine 27 e Podman 5 espongono l'opzione --security-opt landlock=... per applicare ruleset dichiarativi.

Landlock richiede il permesso di root?

No. Questa è la caratteristica principale che lo distingue da AppArmor e SELinux. Qualsiasi processo utente può creare un ruleset e applicarlo a se stesso senza CAP_SYS_ADMIN, purché abbia prima invocato prctl(PR_SET_NO_NEW_PRIVS, 1, ...).

Come verifico che Landlock sia abilitato nel mio kernel?

Controllate /sys/kernel/security/lsm: se contiene la stringa landlock, l'LSM è caricato. Se manca ma il kernel è ≥ 5.13, potrebbe essere disabilitato via parametro boot lsm=; aggiungete landlock alla lista in /etc/default/grub e ricompilate l'initramfs con update-grub.

Yuki Tanaka
Sull'Autore Yuki Tanaka

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