Landlock LSM: Uprivilegeret Sandboxing i Linux — Komplet Guide med Kode og Værktøjer

Landlock LSM giver Linux-applikationer mulighed for at oprette sikre sandboxes uden root. Denne guide dækker ABI-versioner, C-implementering, Landrun, Island og systemd-integration med praktiske eksempler.

Introduktion

Linux-kernen er fundamentet for millioner af servere, cloud-instanser, IoT-enheder og desktop-systemer verden over. Men med den udbredelse følger også en angrebsflade, der bare bliver større og større. I 2025 blev der registreret hele 5.530 CVE'er (Common Vulnerabilities and Exposures) relateret til Linux-kernen — en stigning på 28 % i forhold til året før. Det svarer til 8-9 nye sårbarheder om dagen. Ærligt, det tal overraskede mig, da jeg først så det.

Den eksponentielle vækst skyldes dels, at Linux-kerneteamet blev en CVE Numbering Authority i starten af 2024, hvilket har forbedret gennemsigtigheden markant. Men det understreger også behovet for robuste sikkerhedsmekanismer på applikationsniveau.

Traditionelt har Linux-administratorer brugt sikkerhedsmoduler som SELinux og AppArmor til at håndhæve adgangskontrol. De er effektive, men kræver root-privilegier, komplekse politikker og central administration. Hvad nu, hvis enhver applikation — uanset privilegieniveau — bare kunne isolere sig selv i en sikker sandbox?

Det er præcis det, Landlock LSM (Linux Security Module) tilbyder. Landlock er en stackbar sikkerhedsmodul, der giver uprivilegerede processer mulighed for at begrænse deres egne adgangsrettigheder til filsystemet, netværket og interproces-kommunikation — uden root, uden containere og uden komplekse systemkonfigurationer. Så lad os dykke ned i alle aspekter af Landlock: fra arkitektur og ABI-versioner til praktisk implementering, værktøjer og bedste praksis.

Hvad er Landlock LSM?

Landlock er et stackbart Linux Security Module, der har været inkluderet i Linux-kernen siden version 5.13 (udgivet i juni 2021). I modsætning til SELinux og AppArmor, der kræver privilegeret adgang til konfiguration, er Landlock designet fra bunden til at være tilgængeligt for enhver proces — også dem, der kører uden root-privilegier.

Kerneegenskaber

  • Uprivilegeret sandboxing: Enhver proces kan oprette og håndhæve sin egen sikkerhedspolitik uden root-adgang. Det gør det muligt for udviklere at integrere sandboxing direkte i deres applikationer.
  • Stackbar arkitektur: Landlock fungerer ved siden af eksisterende sikkerhedsmoduler. Det erstatter ikke SELinux eller AppArmor, men tilføjer et ekstra sikkerhedslag oven på de eksisterende politikker.
  • Deny-by-default model: Når en proces aktiverer en Landlock-politik, nægtes al adgang til de ressourcer, der er omfattet af politikken, medmindre der eksplicit er tilføjet en regel, der tillader det.
  • Uigenkaldelighed: Når en Landlock-begrænsning er aktiveret, kan den aldrig fjernes. En proces kan kun tilføje yderligere restriktioner. Det er faktisk en ret elegant sikkerhedsgaranti.
  • Minimal kerne-kode: Implementeringen består af cirka 2.000 linjer kode (security/landlock/*), hvilket gør den overskuelig at auditere og vedligeholde.
  • Arvelighed: Underprocesser (fork()/clone()) arver automatisk forælderprocessens Landlock-begrænsninger, så sandboxen forbliver intakt.

Forskellen fra SELinux og AppArmor

SELinux og AppArmor er kraftfulde, systemdækkende sikkerhedsmoduler, men de opererer fundamentalt anderledes end Landlock:

  • SELinux kræver komplekse politikker skrevet i et særligt politiksprog, administreres af root og kræver labelbaseret adgangskontrol på alle systemobjekter.
  • AppArmor bruger sti-baserede profiler, der også skal konfigureres af en administrator med root-adgang.
  • Landlock kræver ingen systemkonfiguration. En applikation kan selv definere og håndhæve sine begrænsninger ved hjælp af tre simple systemkald. Ikke flere, ikke færre.

Tænk på Landlock som "defense-in-depth" på applikationsniveau. Det interfererer aldrig med andre adgangskontrolmekanismer, men tilføjer kun yderligere restriktioner oven på det, der allerede er på plads.

Landlock ABI-versioner og kernelunderstøttelse

Landlock har gennemgået en markant evolution siden sin introduktion i Linux 5.13. Hver ny ABI-version (Application Binary Interface) har udvidet funktionaliteten med nye adgangsrettigheder og kontrolmuligheder. Det er afgørende at forstå ABI-versionerne, da de bestemmer, hvilke funktioner der er tilgængelige på et givet system.

ABI-version Linux-kerne Nye funktioner
ABI v1 Linux 5.13 Grundlæggende filsystemadgangsrettigheder: EXECUTE, WRITE_FILE, READ_FILE, READ_DIR, REMOVE_DIR, REMOVE_FILE, MAKE_CHAR, MAKE_DIR, MAKE_REG, MAKE_SOCK, MAKE_FIFO, MAKE_BLOCK, MAKE_SYM
ABI v2 Linux 5.19 LANDLOCK_ACCESS_FS_REFER — tillader flytning og hard-linking af filer mellem forskellige mapper (file reparenting)
ABI v3 Linux 6.2 LANDLOCK_ACCESS_FS_TRUNCATE — kontrol over filafkortning via truncate(2), ftruncate(2), creat(2) og open(2) med O_TRUNC
ABI v4 Linux 6.7 Netværksrestriktioner: LANDLOCK_ACCESS_NET_BIND_TCP og LANDLOCK_ACCESS_NET_CONNECT_TCP til styring af TCP-socket-operationer
ABI v5 Linux 6.12 LANDLOCK_ACCESS_FS_IOCTL_DEV — kontrol over ioctl(2)-kald på karakter- og blokenheder
ABI v6 Linux 6.14 IPC-scoping: LANDLOCK_SCOPE_SIGNAL og LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET til begrænsning af interproces-kommunikation

For at tjekke den tilgængelige ABI-version på dit system kan du bruge følgende kommando:

# Tjek Landlock ABI-version via /sys
cat /sys/kernel/security/landlock/abi_version

# Alternativt via systemkald i C:
# landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)

Hvis filen /sys/kernel/security/landlock/abi_version ikke eksisterer, er Landlock enten ikke aktiveret i kernen eller slet ikke understøttet på dit system. Du kan aktivere det via kerneparameteren lsm=landlock,... eller sikre det via kerneconfig-optionen CONFIG_SECURITY_LANDLOCK=y.

Sådan fungerer Landlock

Landlock-sandboxing bygger på tre systemkald, der tilsammen danner en simpel men kraftfuld arbejdsgang. Lad os se på hvert systemkald og den overordnede flow.

De tre systemkald

1. landlock_create_ruleset()

Første trin er at oprette et regelsæt (ruleset), der definerer, hvilke adgangstyper der skal kontrolleres. Man angiver de "håndterede adgangsrettigheder" (handled_access_fs, handled_access_net, scoped), og Landlock returnerer en fil-deskriptor til regelsættet.

struct landlock_ruleset_attr {
    __u64 handled_access_fs;   // Filsystemrettigheder der kontrolleres
    __u64 handled_access_net;  // Netværksrettigheder der kontrolleres
    __u64 scoped;              // IPC-scoping flags
};

int ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);

Alle adgangstyper, der er angivet i handled_access_fs og handled_access_net, vil blive nægtet som standard, medmindre der eksplicit tilføjes en regel, der tillader dem. Det er deny-by-default i praksis.

2. landlock_add_rule()

Næste trin er at tilføje specifikke regler, der tillader adgang til bestemte ressourcer. Der findes to regeltyper:

  • LANDLOCK_RULE_PATH_BENEATH: Tillader specifikke filsystemoperationer under en given mappe eller fil.
  • LANDLOCK_RULE_NET_PORT: Tillader specifikke netværksoperationer på en given TCP-port.
// Tilføj filsystemregel
struct landlock_path_beneath_attr path_rule = {
    .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR,
    .parent_fd = open("/usr", O_PATH | O_CLOEXEC),
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_rule, 0);

// Tilføj netværksregel
struct landlock_net_port_attr net_rule = {
    .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
    .port = 443,
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &net_rule, 0);

3. landlock_restrict_self()

Sidste trin er at håndhæve regelsættet på den kaldende tråd (og alle fremtidige underprocesser). Inden dette kald skal man typisk indstille PR_SET_NO_NEW_PRIVS via prctl() for at forhindre, at sandboxede processer kan opnå nye privilegier via setuid-programmer.

// Forhindre privilegieeskalering
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

// Håndhæv regelsættet
landlock_restrict_self(ruleset_fd, 0);

// Luk regelsæt-deskriptoren (begrænsningerne forbliver aktive)
close(ruleset_fd);

Multi-lag politikstabling

En af Landlocks mest kraftfulde funktioner er politikstabling. Flere regelsæt kan stables oven på hinanden, og for at en handling skal være tilladt, skal alle lag tillade den (AND-logik). Det muliggør en lagdelt sikkerhedsmodel, som i praksis ser sådan ud:

  • Et første lag kan give bred adgang til /usr og /tmp.
  • Et andet lag (tilføjet senere, f.eks. efter init) kan indsnævre adgangen yderligere til kun /usr/share.
  • Resultatet er, at kun /usr/share er tilgængeligt, da begge lag skal tillade adgangen.

Maksimalt 16 lag kan stables per tråd. Hvert lag er uigenkaldeligt og kan kun tilføje yderligere restriktioner.

Praktisk implementering i C

Nu til den sjove del — rigtig kode. Følgende er et komplet C-eksempel, der demonstrerer oprettelsen af en Landlock-sandbox med filsystem- og netværksrestriktioner. Programmet giver læseadgang til /usr, læse- og skriveadgang til /tmp, og tillader kun udgående TCP-forbindelser til port 443 (HTTPS).

#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-funktioner til Landlock-systemkald */
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 ruleset_fd, enum landlock_rule_type type,
                  const void *attr, __u32 flags)
{
    return syscall(__NR_landlock_add_rule, ruleset_fd, type, attr, flags);
}

static inline int
landlock_restrict_self(int ruleset_fd, __u32 flags)
{
    return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
}

/* Hjælpefunktion til at tilføje filsystemregel */
static int add_path_rule(int ruleset_fd, __u64 allowed_access,
                         const char *path)
{
    int fd = open(path, O_PATH | O_CLOEXEC);
    if (fd < 0) {
        fprintf(stderr, "Kunne ikke åbne sti: %s: %s\n",
                path, strerror(errno));
        return -1;
    }

    struct landlock_path_beneath_attr attr = {
        .allowed_access = allowed_access,
        .parent_fd = fd,
    };

    int ret = landlock_add_rule(ruleset_fd,
                                 LANDLOCK_RULE_PATH_BENEATH, &attr, 0);
    close(fd);

    if (ret < 0) {
        fprintf(stderr, "Kunne ikke tilføje regel for %s: %s\n",
                path, strerror(errno));
        return -1;
    }
    return 0;
}

/* Hjælpefunktion til at tilføje netværksregel */
static int add_net_rule(int ruleset_fd, __u64 allowed_access,
                        __u64 port)
{
    struct landlock_net_port_attr attr = {
        .allowed_access = allowed_access,
        .port = port,
    };

    int ret = landlock_add_rule(ruleset_fd,
                                 LANDLOCK_RULE_NET_PORT, &attr, 0);
    if (ret < 0) {
        fprintf(stderr, "Kunne ikke tilføje netværksregel for port %llu: %s\n",
                (unsigned long long)port, strerror(errno));
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[])
{
    int ret;

    /* Trin 1: Detekter ABI-version */
    int abi_version = landlock_create_ruleset(NULL, 0,
                                LANDLOCK_CREATE_RULESET_VERSION);
    if (abi_version < 0) {
        if (errno == EOPNOTSUPP || errno == ENOSYS) {
            fprintf(stderr, "Landlock er ikke understøttet af kernen.\n");
            fprintf(stderr, "Forsøg at aktivere det med: "
                    "lsm=landlock,... kerneparameter\n");
        } else {
            perror("landlock_create_ruleset(VERSION)");
        }
        return 1;
    }
    printf("Landlock ABI-version: %d\n", abi_version);

    /* Trin 2: Definer håndterede adgangsrettigheder */
    /* Filsystemrettigheder (ABI v1-v3) */
    __u64 fs_access =
        LANDLOCK_ACCESS_FS_EXECUTE |
        LANDLOCK_ACCESS_FS_WRITE_FILE |
        LANDLOCK_ACCESS_FS_READ_FILE |
        LANDLOCK_ACCESS_FS_READ_DIR |
        LANDLOCK_ACCESS_FS_REMOVE_DIR |
        LANDLOCK_ACCESS_FS_REMOVE_FILE |
        LANDLOCK_ACCESS_FS_MAKE_CHAR |
        LANDLOCK_ACCESS_FS_MAKE_DIR |
        LANDLOCK_ACCESS_FS_MAKE_REG |
        LANDLOCK_ACCESS_FS_MAKE_SOCK |
        LANDLOCK_ACCESS_FS_MAKE_FIFO |
        LANDLOCK_ACCESS_FS_MAKE_BLOCK |
        LANDLOCK_ACCESS_FS_MAKE_SYM;

    /* Tilføj REFER hvis ABI >= 2 */
    if (abi_version >= 2)
        fs_access |= LANDLOCK_ACCESS_FS_REFER;

    /* Tilføj TRUNCATE hvis ABI >= 3 */
    if (abi_version >= 3)
        fs_access |= LANDLOCK_ACCESS_FS_TRUNCATE;

    /* Netværksrettigheder (kræver ABI >= 4) */
    __u64 net_access = 0;
    if (abi_version >= 4)
        net_access = LANDLOCK_ACCESS_NET_BIND_TCP |
                     LANDLOCK_ACCESS_NET_CONNECT_TCP;

    struct landlock_ruleset_attr ruleset_attr = {
        .handled_access_fs = fs_access,
        .handled_access_net = net_access,
    };

    /* Trin 3: Opret regelsæt */
    int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
                                              sizeof(ruleset_attr), 0);
    if (ruleset_fd < 0) {
        perror("landlock_create_ruleset");
        return 1;
    }

    /* Trin 4: Tilføj filsystemregler */
    /* Læseadgang til /usr (læs filer og mapper, eksekvér) */
    __u64 usr_access = LANDLOCK_ACCESS_FS_READ_FILE |
                       LANDLOCK_ACCESS_FS_READ_DIR |
                       LANDLOCK_ACCESS_FS_EXECUTE;
    if (add_path_rule(ruleset_fd, usr_access, "/usr") != 0)
        return 1;

    /* Læs- og skriveadgang til /tmp */
    __u64 tmp_access = LANDLOCK_ACCESS_FS_READ_FILE |
                       LANDLOCK_ACCESS_FS_READ_DIR |
                       LANDLOCK_ACCESS_FS_WRITE_FILE |
                       LANDLOCK_ACCESS_FS_MAKE_REG |
                       LANDLOCK_ACCESS_FS_MAKE_DIR |
                       LANDLOCK_ACCESS_FS_REMOVE_FILE |
                       LANDLOCK_ACCESS_FS_REMOVE_DIR;
    if (abi_version >= 3)
        tmp_access |= LANDLOCK_ACCESS_FS_TRUNCATE;
    if (add_path_rule(ruleset_fd, tmp_access, "/tmp") != 0)
        return 1;

    /* Trin 5: Tilføj netværksregler (kun HTTPS udgående) */
    if (abi_version >= 4) {
        if (add_net_rule(ruleset_fd,
                         LANDLOCK_ACCESS_NET_CONNECT_TCP, 443) != 0)
            return 1;
        printf("Netværksregel tilføjet: TCP connect port 443\n");
    } else {
        printf("Advarsel: Netværksrestriktioner kræver ABI >= 4 "
               "(fundet: %d)\n", abi_version);
    }

    /* Trin 6: Aktiver NO_NEW_PRIVS og håndhæv sandbox */
    ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    if (ret) {
        perror("prctl(PR_SET_NO_NEW_PRIVS)");
        close(ruleset_fd);
        return 1;
    }

    ret = landlock_restrict_self(ruleset_fd, 0);
    close(ruleset_fd);

    if (ret) {
        perror("landlock_restrict_self");
        return 1;
    }

    printf("Landlock-sandbox er nu aktiv!\n");
    printf("Tilladte operationer:\n");
    printf("  - Læs og eksekvér: /usr\n");
    printf("  - Læs og skriv:    /tmp\n");
    printf("  - TCP connect:      port 443 kun\n");

    /* Trin 7: Eksekvér den sandboxede applikation */
    if (argc > 1) {
        execvp(argv[1], &argv[1]);
        perror("execvp");
        return 1;
    }

    /* Demo: Test adgang */
    FILE *f = fopen("/tmp/landlock_test.txt", "w");
    if (f) {
        fprintf(f, "Denne fil blev oprettet i sandboxen.\n");
        fclose(f);
        printf("Succes: Skrev til /tmp/landlock_test.txt\n");
    }

    f = fopen("/etc/passwd", "r");
    if (!f) {
        printf("Forventet: Kan ikke læse /etc/passwd (%s)\n",
               strerror(errno));
    } else {
        printf("Uventet: Kunne læse /etc/passwd!\n");
        fclose(f);
    }

    return 0;
}

Kompilér og kør programmet:

gcc -Wall -o landlock_sandbox landlock_sandbox.c
./landlock_sandbox
# Eller med en kommando:
./landlock_sandbox /bin/ls /tmp

Bemærk, at programmet degraderer med ynde ved ældre kerner: det detekterer ABI-versionen og tilpasser de håndterede rettigheder derefter. Det er en vigtig best practice for produktionsklar kode — og noget jeg vil anbefale at gøre fra dag ét.

Filsystemadgangsrettigheder

Landlock tilbyder en finkornet kontrol over filsystemoperationer. Rettighederne er opdelt i to kategorier: filrettigheder (der gælder for individuelle filer) og mapperettigheder (der gælder for mapper og deres indhold).

Filrettigheder

Konstant ABI Beskrivelse
LANDLOCK_ACCESS_FS_EXECUTE v1 Eksekvér en fil (via execve(2) og lignende)
LANDLOCK_ACCESS_FS_WRITE_FILE v1 Åbn en fil til skrivning (kræver ofte også TRUNCATE)
LANDLOCK_ACCESS_FS_READ_FILE v1 Åbn en fil med læseadgang
LANDLOCK_ACCESS_FS_TRUNCATE v3 Afkort filer via truncate(2), ftruncate(2), creat(2) eller open(2) med O_TRUNC
LANDLOCK_ACCESS_FS_IOCTL_DEV v5 Udfør ioctl(2)-kald på karakter- og blokenheder

Mapperettigheder

Konstant ABI Beskrivelse
LANDLOCK_ACCESS_FS_READ_DIR v1 Åbn en mappe eller oplist dens indhold
LANDLOCK_ACCESS_FS_REMOVE_DIR v1 Fjern en tom mappe eller omdøb den
LANDLOCK_ACCESS_FS_REMOVE_FILE v1 Unlink (fjern) eller omdøb en fil
LANDLOCK_ACCESS_FS_MAKE_CHAR v1 Opret en karakter-enhedsfil
LANDLOCK_ACCESS_FS_MAKE_DIR v1 Opret en ny mappe
LANDLOCK_ACCESS_FS_MAKE_REG v1 Opret en regulær fil
LANDLOCK_ACCESS_FS_MAKE_SOCK v1 Opret en UNIX-domænesocket
LANDLOCK_ACCESS_FS_MAKE_FIFO v1 Opret en named pipe (FIFO)
LANDLOCK_ACCESS_FS_MAKE_BLOCK v1 Opret en blok-enhedsfil
LANDLOCK_ACCESS_FS_MAKE_SYM v1 Opret et symbolsk link
LANDLOCK_ACCESS_FS_REFER v2 Tillad hard-linking og flytning af filer mellem mapper (nægtet som standard)

Vigtige bemærkninger om fil- vs. mapperettigheder

Der er en vigtig skelnen her, som det er værd at dvæle ved:

  • Filrettigheder (EXECUTE, WRITE_FILE, READ_FILE, TRUNCATE, IOCTL_DEV) gælder kun for regulære filer og enheder. De kan angives i regler, der peger på både filer og mapper — i sidstnævnte tilfælde gælder de for alle filer under mappen.
  • Mapperettigheder (READ_DIR, REMOVE_DIR, MAKE_*, REFER) gælder kun for mapper og kan kun angives i regler, der peger på mapper.
  • Peger reglen på en specifik fil, gælder rettigheden kun for den pågældende fil. Peger den på en mappe, gælder rettigheden for alle filer i hele undertræet.

Netværksadgangskontrol

Med ABI version 4 (Linux 6.7) fik Landlock mulighed for at kontrollere TCP-netværksadgang. Det var en stor milepæl, fordi det gør det muligt at begrænse, hvilke porte en sandboxet proces kan binde til og oprette forbindelser til.

Tilgængelige netværksrettigheder

Konstant Beskrivelse
LANDLOCK_ACCESS_NET_BIND_TCP Tillad binding af en TCP-socket til en specifik lokal port
LANDLOCK_ACCESS_NET_CONNECT_TCP Tillad oprettelse af en aktiv TCP-forbindelse til en specifik fjernport

Eksempler på netværksrestriktioner

Her er et par eksempler på, hvordan netværksregler oprettes i praksis:

/* Tillad en webserver at binde til port 80 og 443 */
struct landlock_net_port_attr bind_http = {
    .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
    .port = 80,
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &bind_http, 0);

struct landlock_net_port_attr bind_https = {
    .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
    .port = 443,
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &bind_https, 0);

/* Tillad udgående forbindelser til en database på port 5432 */
struct landlock_net_port_attr connect_db = {
    .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
    .port = 5432,
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &connect_db, 0);

Netværksreglerne fungerer efter samme deny-by-default-princip som filsystemreglerne: Når LANDLOCK_ACCESS_NET_BIND_TCP eller LANDLOCK_ACCESS_NET_CONNECT_TCP er inkluderet i handled_access_net, nægtes alle bind- eller connect-operationer, medmindre der er en specifik regel, der tillader den pågældende port.

Bemærk: Netværksrestriktionerne er i øjeblikket begrænset til TCP. UDP, SCTP og andre protokoller er endnu ikke understøttede, men der arbejdes på at udvide understøttelsen i fremtidige kerneversioner. Det er en begrænsning, man bør have in mente.

IPC-scoping

ABI version 6 introducerede IPC-scoping (Inter-Process Communication), og det er ærligt talt en af de funktioner, jeg synes fortjener mere opmærksomhed. Den giver Landlock mulighed for at begrænse kommunikation mellem processer — noget der er særligt vigtigt for at forhindre, at en sandboxet proces kan påvirke processer uden for sit sikkerhedsdomæne.

Tilgængelige IPC-scoping flags

Konstant Beskrivelse
LANDLOCK_SCOPE_SIGNAL Begrænser signalafsendelse: En sandboxet proces kan kun sende signaler til processer inden for samme Landlock-domæne eller et underdomæne. Signaler til processer uden for sandboxen blokeres.
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET Begrænser forbindelser via abstrakte UNIX-sockets: En sandboxet proces kan kun forbinde til abstrakte UNIX-sockets, der tilhører processer i samme eller et underordnet Landlock-domæne.

Brug af IPC-scoping

IPC-scoping aktiveres via scoped-feltet i landlock_ruleset_attr:

struct landlock_ruleset_attr attr = {
    .handled_access_fs = /* ... filsystemrettigheder ... */,
    .handled_access_net = /* ... netværksrettigheder ... */,
    .scoped = LANDLOCK_SCOPE_SIGNAL |
              LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
};

int ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
/* ... tilføj regler ... */
landlock_restrict_self(ruleset_fd, 0);

Med IPC-scoping aktiveret kan en sandboxet proces ikke længere:

  • Sende SIGKILL, SIGTERM eller andre signaler til processer uden for sin sandbox.
  • Oprette forbindelse til abstrakte UNIX-domænesockets, der tilhører processer i et andet sikkerhedsdomæne.

Det er særligt værdifuldt i multi-tenant-miljøer, hvor flere applikationer kører på samme vært, og man ønsker at forhindre lateral bevægelse mellem sandboxede processer.

Praktiske værktøjer: Landrun og Island

Selvom Landlock API'et er relativt simpelt, kan det godt være lidt besværligt at implementere fra bunden for hver applikation. Heldigvis findes der et par rigtig gode værktøjer, der gør det nemt at bruge Landlock-sandboxing uden at skrive C-kode.

Landrun

Landrun er et letvægtigt CLI-værktøj skrevet i Go, der gør det praktisk at sandboxe enhver kommando med finkornet filsystem- og netværksadgangskontrol. Det kræver ingen root-privilegier, ingen containere og ingen SELinux/AppArmor-konfiguration. Kort sagt: det er Landlock gjort tilgængeligt for alle.

Grundlæggende brug

# Giv læseadgang med eksekveringsrettigheder til /usr,
# læs-/skriveadgang til /tmp, og opret en fil
landrun --rox /usr --rw /tmp touch /tmp/testfil

# Kun læseadgang - skrivning nægtes
landrun --rox /usr touch /tmp/testfil
# Resultat: touch: cannot touch '/tmp/testfil': Permission denied

# Oplist hjemmebiblioteket med minimumsrettigheder
landrun --rox /usr/bin/ls --rox /usr/lib --ro /home ls /home

# Start en interaktiv shell i en sandbox
landrun --rox /usr/ --ro /lib,/lib64 /usr/bin/bash

Netværksrestriktioner med Landrun

# Tillad kun HTTPS-udgående forbindelser
landrun --rox /usr/ --ro /etc --connect-tcp 443 \
    curl https://eksempel.dk

# Webserver bundet til specifikke porte
landrun --rox /usr/bin --ro /lib,/lib64,/var/www \
    --rwx /var/log --bind-tcp 80,443 \
    /usr/bin/nginx

# Debugging med strace
landrun --rox /usr strace -f -e trace=all ls

Landrun-kommandolinjeflags

Flag Beskrivelse
--ro <sti> Giv kun læseadgang til den angivne sti
--rox <sti> Læseadgang med eksekveringsrettigheder
--rw <sti> Læse- og skriveadgang
--rwx <sti> Fuld læse-, skrive- og eksekveringsadgang
--bind-tcp <port> Tillad TCP-binding til de angivne porte
--connect-tcp <port> Tillad udgående TCP-forbindelser til de angivne porte
--env <VAR> Videregiv miljøvariabel til den sandboxede proces
--best-effort Degradér med ynde på ældre kerner
--ldd Tilføjer automatisk nødvendige delte biblioteker
--add-exec Tilføj automatisk den eksekverbare fils sti

Island

Island er et andet sandboxing-værktøj, der er udviklet af Landlock-teamet selv. I modsætning til Landrun, der primært bruges interaktivt på kommandolinjen, fokuserer Island på TOML-baserede profiler og kontekstbevidst aktivering.

Islands kernekoncepter

  • Zero-code integration: Kører eksisterende binære filer uden modifikation.
  • TOML-baseret konfiguration: Sikkerhedspolitikker defineres i deklarative TOML-filer i stedet for kode.
  • Kontekstbevidst aktivering: Profiler aktiveres automatisk baseret på den aktuelle arbejdsmappe. Når du navigerer ind i et projektbibliotek, anvender Island automatisk den tilhørende sikkerhedsprofil. Ret smart, faktisk.

Profilstruktur

Island-profiler gemmes i ~/.config/island/profiles/<n>/ og består af:

  • profile.toml: Definerer aktiveringskonteksten, miljøvariable og isoleringsindstillinger.
  • landlock/: Indeholder sikkerhedspolitikfiler (f.eks. filsystemadgangsregler) i Landlock Config-formatet.
# Eksempel: ~/.config/island/profiles/webprojekt/profile.toml

[context]
when_beneath = ["/home/bruger/projekter/webapp"]

[environment]
NODE_ENV = "development"
PORT = "3000"

[workspace]
writable = true

Miljøintegration giver kontekstbevidsthed: Variablerne ISLAND_CONTEXT_BENEATH_[0-9]+ eksponerer alle when_beneath-stier relateret til den aktuelt anvendte profil, og profilspecifikke miljøvariable injiceres automatisk.

Island er stadig under aktiv udvikling, men det repræsenterer en spændende retning for brugervenlig Landlock-sandboxing med deklarativ konfiguration.

Integration med systemd-tjenester

En af de mest praktiske anvendelser af Landlock er at sandboxe systemd-tjenester i produktion. Ved at kombinere Landlock med systemd kan du give hver tjeneste præcis de rettigheder, den behøver — og ikke en eneste mere.

Landrun med systemd

Den simpleste metode er at bruge Landrun som en wrapper i en systemd-servicefil:

# /etc/systemd/system/nginx-landrun.service
[Unit]
Description=Nginx webserver med Landlock-sandbox via Landrun
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/landrun \
    --rox /usr/bin,/usr/lib \
    --ro /etc/nginx,/etc/ssl,/etc/passwd,/etc/group,/etc/nsswitch.conf \
    --rwx /var/log/nginx \
    --rwx /var/cache/nginx \
    --bind-tcp 80,443 \
    /usr/bin/nginx -g 'daemon off;'
Restart=always
User=nginx
Group=nginx

# Kombinér med systemds egne sikkerhedsfunktioner
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Aktivér og start tjenesten:

sudo systemctl daemon-reload
sudo systemctl enable nginx-landrun
sudo systemctl start nginx-landrun
sudo systemctl status nginx-landrun

Programmatisk integration

For tjenester, der er bygget med Landlock-understøttelse direkte i applikationen, kan systemd-servicefilen være enklere:

# /etc/systemd/system/min-applikation.service
[Unit]
Description=Min applikation med indbygget Landlock-sandbox
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/min-applikation --enable-landlock
Restart=on-failure
User=appbruger
Group=appgruppe

# Systemd-sikkerhedshærdning der komplementerer Landlock
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/min-applikation /var/log/min-applikation
ProtectHome=true
PrivateTmp=true
ProtectKernelModules=true
ProtectKernelTunables=true
RestrictNamespaces=true

[Install]
WantedBy=multi-user.target

Kommende systemd-integration via LandlockConfig

Der er også et igangværende arbejde med at integrere Landlock direkte i systemd via en ny LandlockConfig-nøgle i [Service]-sektionen. Denne funktion, der bygger på liblandlockconfig-biblioteket, vil gøre det muligt at pege på en mappe med TOML-filer, der definerer Landlock-politikken:

# Fremtidig systemd-integration (under udvikling)
[Service]
LandlockConfig=/etc/landlock/nginx/

Denne tilgang vil forenkle konfigurationen betydeligt og gøre Landlock-sandboxing til en førsteklasses borger i systemd-økosystemet. Det ser jeg personligt meget frem til.

Best practices og sikkerhedsanbefalinger

For at få mest muligt ud af Landlock-sandboxing, er her de anbefalinger, jeg mener er vigtigst:

1. Anvend "leaf-level" rettigheder

Undgå at give brede rettigheder til rodmapper. Jo mere specifik du er med dine stier, desto stærkere er sandboxen. Giv f.eks. adgang til /usr/share/min-app i stedet for hele /usr.

2. Defense-in-depth

Landlock bør aldrig være dit eneste sikkerhedslag. Kombinér det med:

  • seccomp-bpf: Begræns tilgængelige systemkald (Landlock kontrollerer ressourcer, seccomp kontrollerer operationer).
  • Linux namespaces: Isolér processens syn på filsystem, netværk, PID'er osv.
  • SELinux/AppArmor: Systemdækkende politikker som et overordnet sikkerhedslag.
  • systemd-hærdning: NoNewPrivileges, ProtectSystem, PrivateTmp m.fl.

3. ABI-versionsdetektion og graceful degradation

Antag aldrig en bestemt ABI-version. Detektér altid versionen ved kørselstid og tilpas dine håndterede rettigheder. Hvis en kerne ikke understøtter netværksrestriktioner (ABI < 4), bør din applikation stadig fungere — bare uden den pågældende beskyttelse.

int abi = landlock_create_ruleset(NULL, 0,
                    LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
    /* Landlock ikke tilgængeligt - kør uden sandbox
       eller afslut, afhængigt af sikkerhedskrav */
    if (errno == EOPNOTSUPP)
        fprintf(stderr, "Advarsel: Landlock ikke understøttet\n");
    /* ... */
}

/* Tilpas rettigheder baseret på ABI-version */
__u64 fs_rights = BASE_FS_RIGHTS;
if (abi >= 2) fs_rights |= LANDLOCK_ACCESS_FS_REFER;
if (abi >= 3) fs_rights |= LANDLOCK_ACCESS_FS_TRUNCATE;
/* ... osv. */

4. Respektér de 16 lags grænse

Landlock tillader maksimalt 16 stablede lag per tråd. Planlæg din lagdeling omhyggeligt, og undgå at oprette unødvendige lag. I de fleste tilfælde er 1-3 lag mere end tilstrækkeligt.

5. Håndtér fejl korrekt

Tjek altid returværdier fra alle tre systemkald. Håndtér særligt:

  • EOPNOTSUPP: Landlock er ikke aktiveret i kernen.
  • EINVAL: Ugyldige parametre (f.eks. ukendte flags på en ældre kerne).
  • E2BIG: For mange stablede lag (over 16).

6. Test grundigt

Test din sandbox med både positive og negative tilfælde. Verificér, at tilladte operationer virker, og at forbudte operationer nægtes med de forventede fejlmeddelelser (EACCES). Det lyder banalt, men det overses overraskende tit.

7. Dokumentér din sikkerhedspolitik

Beskriv tydeligt, hvilke rettigheder din applikation behøver og hvorfor. Det letter vedligeholdelse og sikkerhedsgennemgang — og din fremtidige dig selv vil takke dig for det.

Landlock vs. andre sikkerhedsmekanismer

For at forstå, hvornår Landlock er det rigtige valg, er det nyttigt at sammenligne det med andre Linux-sikkerhedsmekanismer.

Egenskab Landlock SELinux AppArmor seccomp-bpf Containere/Namespaces
Kræver root Nej Ja Ja Nej* Typisk ja
Kontroltype Ressource-adgang (filer, porte, IPC) Label-baseret MAC Sti-baseret MAC Systemkald-filtrering Fuld isolation
Granularitet Fil-/mappe-niveau, port-niveau Objekt-niveau Sti-niveau Systemkald-niveau Procesgruppe-niveau
Stackbar Ja (op til 16 lag) Nej (eksklusiv) Nej (eksklusiv) Ja Ja (nested)
Kompleksitet Lav Høj Middel Middel-høj Høj
Applikationsstyret Ja Nej Nej Ja Nej
Netværkskontrol TCP (fra ABI v4) Fuld Begrænset Systemkald-niveau Fuld (netns)
Overhead Minimalt Lavt Lavt Minimalt Varierende

* seccomp-bpf kræver PR_SET_NO_NEW_PRIVS eller CAP_SYS_ADMIN

Hvornår skal du bruge hvad?

  • Landlock: Ideel når en applikation selv vil begrænse sin adgang til filsystem, netværk og IPC. Perfekt til applikationer, der processerer utroværdigt input, plugins eller tredjepartskode. Intet behov for root eller systemkonfiguration.
  • SELinux/AppArmor: Brug til systemdækkende, obligatorisk adgangskontrol (MAC), der håndhæves af administratorer. Velegnet til servermiljøer med stringente compliance-krav.
  • seccomp-bpf: Brug til at filtrere tilgængelige systemkald. Komplementerer Landlock godt: Landlock kontrollerer hvilke ressourcer der kan tilgås, seccomp kontrollerer hvilke operationer der kan udføres.
  • Containere/Namespaces: Brug til fuld isolation af applikationer, særligt i multi-tenant-miljøer. Giver stærkest isolation, men med størst overhead og kompleksitet.

Den stærkeste sikkerhed opnås ved at kombinere flere mekanismer: Landlock + seccomp-bpf + namespaces + SELinux/AppArmor udgør en robust defense-in-depth-strategi.

Fremtiden for Landlock

Landlock er under aktiv udvikling, og der er flere spændende forbedringer på vej.

Rust-integration i kernen

Med Rusts stigende tilstedeværelse i Linux-kernen (fra Linux 6.1) er der mulighed for, at dele af Landlock-infrastrukturen fremover kan drage fordel af Rusts hukommelsessikkerhed. Rust-landlock-biblioteket (rust-landlock) giver allerede i dag en sikker Rust-API til Landlock fra brugerrum, og det arbejde informerer potentielle kerne-sideimplementeringer.

Udvidede adgangskontroller

Flere områder er under overvejelse for fremtidige ABI-versioner:

  • UDP-understøttelse: Udvidelse af netværksrestriktioner til at omfatte UDP-sockets, hvilket vil dække DNS-opslag og andre protokoller.
  • Udvidet ioctl-kontrol: Mere granuleret kontrol over enhedsspecifikke ioctl-kommandoer.
  • Audit-understøttelse: Forbedret logning og revision af Landlock-hændelser (arbejdet er allerede påbegyndt i Linux 6.14).
  • Filsystemtopologi: Potentiel kontrol over mount-operationer og filsystem-topologi-ændringer.

Signerede eBPF-programmer

Landlock var oprindeligt designet med eBPF-integration in mente. Selvom den nuværende implementering bruger en mere traditionel LSM-tilgang, er der forskning i signerede eBPF-programmer, der kan give endnu mere fleksibel og verificerbar sandboxing. I dette paradigme kan en betroet tredjepart signere eBPF-programmer, som kernen derefter kan indlæse med sikkerhed for programmets integritet.

Systemd-førsteklasses integration

Som nævnt er der et aktivt arbejde med at integrere Landlock direkte i systemd via LandlockConfig-direktivet. Når dette er færdiggjort, vil det være trivielt at tilføje Landlock-sandboxing til enhver systemd-tjeneste via deklarativ konfiguration.

Håndtering af "weird files"

I Linux 6.14 blev der tilføjet forbedret håndtering af såkaldte "weird files" — filer, der kan være synlige efter filsystemkorruption. Landlock håndterer nu disse filer korrekt, hvilket styrker robustheden i sikkerhedsmodellen.

Konklusion

Landlock LSM repræsenterer et paradigmeskifte i Linux-sikkerhed. For første gang kan enhver applikation — uanset privilegieniveau — oprette en robust, uigenkaldelig sikkerhedssandbox uden root-adgang, uden containere og uden kompleks systemkonfiguration. Med den stadigt stigende strøm af Linux-kernesårbarheder (5.530 CVE'er alene i 2025) er applikationsniveau-sandboxing ikke længere en luksus — det er en nødvendighed.

Landlocks styrker taler for sig selv:

  • Simpelt API: Kun tre systemkald at lære og anvende.
  • Uprivilegeret: Ingen root krævet, hvilket muliggør sandboxing af enhver brugerproces.
  • Stackbar: Fungerer ved siden af SELinux, AppArmor og andre LSM'er som et ekstra sikkerhedslag.
  • Uigenkaldelig: Når sandboxen er aktiv, kan den aldrig fjernes — kun styrkes.
  • Bred dækning: Filsystem, netværk og IPC-kontrol i en samlet ramme.
  • Lav overhead: Minimal påvirkning af systemets ydelse.

Uanset om du er applikationsudvikler, systemadministrator eller sikkerhedsarkitekt, bør Landlock være en del af din værktøjskasse. Start i dag: tjek din kerneversion (uname -r), verificér Landlock-understøttelse (cat /sys/kernel/security/landlock/abi_version), og begynd at sandboxe dine applikationer — enten programmatisk via C/Rust API'et eller med værktøjer som Landrun og Island. Hver ekstra sandboxet applikation er et skridt mod en sikrere Linux-infrastruktur.

Om Forfatteren Editorial Team

Our team of expert writers and editors.