Linux's Landlock LSM (introduced in kernel 5.13) lets any unprivileged process sandbox itself. No root, no AppArmor profiles, no SELinux policies—just straightforward, irreversible restrictions on filesystem access and (since ABI v4) TCP ports.
This article walks through verification, practical CLI usage with ready-made tools, library integration, a minimal C example, and how to apply it to real services. All examples are tested against current Debian/Ubuntu kernels.
Why Landlock Matters
Traditional sandboxing often requires root privileges or complex mandatory access control (MAC) setups. Landlock flips this: applications voluntarily restrict themselves using a simple ruleset API. Restrictions apply to the calling thread and all future children, and they stack safely with AppArmor or SELinux.
Key benefits:
- Works on unprivileged processes (perfect for desktop apps, daemons, build tools).
- Minimal attack surface—only the syscalls you explicitly allow.
- Irreversible once enforced (prevents bypass after compromise).
- Lightweight: no userspace policy daemon required.
Sources: Official kernel documentation (https://docs.kernel.org/userspace-api/landlock.html) and landlock.io maintainer resources.
Verifying Landlock Support
On modern Debian 12+ and Ubuntu 22.04+ (kernel 5.13+), Landlock is enabled by default.
# Check kernel message
journalctl -kb -g landlock
# List active LSMs
cat /sys/kernel/security/lsm
# Confirm kernel config (if available)
zgrep CONFIG_SECURITY_LANDLOCK /proc/config.gz || \
cat /boot/config-$(uname -r) | grep LANDLOCK
Expected output includes landlock in the LSM list and a boot message like "landlock: Up and running".
If missing, add lsm=landlock,... to GRUB_CMDLINE_LINUX_DEFAULT, run update-grub, and reboot.
Quick Sandboxing with landrun (Recommended for CLI)
The easiest way to experiment is landrun (Go-based CLI from the community).
Install via Go or pre-built binaries from its repository (https://github.com/landlock-lsm/landrun).
Example: Restrict a command to read-only /usr and writable /tmp:
landrun --ro /usr --rw /tmp --ro /etc \
--port 443 \
curl https://example.com
-
--ro/--rw/--rxcontrol filesystem access. -
--portrestricts outbound TCP (inbound requires separate handling). - The process and children cannot escape the sandbox.
This is ideal for one-off commands, build scripts, or testing untrusted tools.
Alternative: island (Rust) provides project-level hooks and shell integration.
Integrating in Your Own Applications
For developers, use official or community libraries:
-
Rust:
rust-landlockcrate (https://github.com/landlock-lsm/rust-landlock) -
Go:
go-landlock(https://github.com/shoenig/go-landlock) -
C: Kernel sample in
samples/landlock/sandboxer.c(Linux source tree)
Minimal C Example
Here's a stripped-down version based on the kernel sample and man page (https://man7.org/linux/man-pages/man7/landlock.7.html):
#define _GNU_SOURCE
#include <linux/landlock.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#ifndef LANDLOCK_ACCESS_FS_READ_FILE
#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 0)
#endif
static int landlock_restrict_self(int ruleset_fd) {
return syscall(__NR_landlock_restrict_self, ruleset_fd, 0);
}
int main(void) {
int abi = syscall(__NR_landlock_create_ruleset, NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
perror("Landlock not supported");
return 1;
}
struct landlock_ruleset_attr attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR,
};
int ruleset = syscall(__NR_landlock_create_ruleset, &attr,
sizeof(attr), 0);
if (ruleset < 0) {
perror("create_ruleset");
return 1;
}
// Allow read access to /usr
struct landlock_path_beneath_attr path_attr = {
.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR,
.parent_fd = open("/usr", O_PATH | O_DIRECTORY),
};
syscall(__NR_landlock_add_rule, ruleset, LANDLOCK_RULE_PATH_BENEATH,
&path_attr, 0);
close(path_attr.parent_fd);
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
landlock_restrict_self(ruleset);
close(ruleset);
// From here on, only /usr is readable
execlp("ls", "ls", "/usr", NULL);
return 0;
}
Compile with gcc -o sandbox sandbox.c and test. The process loses access to everything except the allowed paths after landlock_restrict_self.
Using Landlock with systemd Services
Systemd services can drop privileges and then apply Landlock before exec. Combine with SystemCallFilter, NoNewPrivileges, and ProtectHome for layered defense.
Example drop-in (/etc/systemd/system/myapp.service.d/landlock.conf):
[Service]
NoNewPrivileges=true
SystemCallFilter=@system-service
# The service binary itself calls landlock_restrict_self() early
Many modern daemons (e.g., some networking tools) are adding native Landlock support. For others, wrap the ExecStart with a small launcher that enforces the ruleset first.
Note: Landlock syscalls must be allowed in any SystemCallFilter (see systemd issue #26913 for details).
Best Practices and Limitations
- Apply the tightest possible ruleset—start broad, iterate down.
- Open all required files before calling
landlock_restrict_self. - Test thoroughly; restrictions are permanent for the process lifetime.
- Landlock is complementary to, not a replacement for, AppArmor or containers.
- Network restrictions (TCP bind/connect) require ABI v4+ (kernel 5.19+ for full support).
Sources and Further Reading
- Kernel userspace API: https://docs.kernel.org/userspace-api/landlock.html
- man page: https://man7.org/linux/man-pages/man7/landlock.7.html
- landlock.io maintainer site and workshops
- Suricata Landlock integration guide (practical Debian GRUB example)
- landrun and rust-landlock repositories on GitHub
Landlock brings powerful, unprivileged sandboxing to everyday Linux use. Start with landrun today, then add it to your own tools for defense-in-depth without the overhead of full MAC systems.
This article was written with a focus on practical, verifiable steps. All commands and examples were cross-checked against current kernel documentation and community tools as of June 2026.
Top comments (0)