В этой статье поговорим о том, как использовать API Landlock для защиты Linux-приложений, ограничивая доступ к файловой системе и сети.
Два часа ночи. Вас будит уведомление: хакер нашёл уязвимость в вашем приложении и теперь может украсть учётные данные для доступа к системам ваших клиентов. Как предотвратить такой сценарий?
До сих пор у нас был только вариант защищать приложения со стороны системы, используя решения вроде SELinux или AppArmor. Мы также можем настраивать права пользователей и групп или фильтровать системные вызовы через seccomp. А что, если разработчики могли бы управлять правами приложения самостоятельно?
Здесь и появляется Landlock — механизм безопасности ядра Linux, который позволяет приложениям добровольно ограничивать собственные привилегии. Без root и без сложной настройки. И очень просто: всего три новых системных вызова.
Что такое Landlock?
Landlock — это модуль безопасности Linux (LSM), который позволяет непривилегированным процессам добровольно ограничивать свои права доступа.
Landlock — модуль безопасности Linux, появившийся в ядре 5.13, который позволяет обычным процессам добровольно ограничивать свой доступ к системным ресурсам. Он использует контроль доступа на основе путей, то есть вы точно задаёте, какие пути и какие операции разрешены.
Зачем нужен Landlock?
Основные преимущества:
Работает без root
Нужно освоить всего 3 системных вызова:
landlock_create_ruleset()— создаёт набор правил (ruleset)landlock_add_rule()— добавляет конкретные правила доступаlandlock_restrict_self()— применяет ограничения к процессуПриложения работают и на старых ядрах (с ослабленной защитой или вовсе без неё)
Используется в systemd, Chromium, Pacman
Landlock работает по принципу «запрещено по умолчанию»: по умолчанию блокируется всё. Вы должны явно указать, что разрешено. Это противоположно обычному поведению Unix, где всё разрешено, пока вы это не запретите.
Чтобы защитить приложение, нужно сделать три шага:
Создать набор правил → Определить, какие типы доступа вы хотите контролировать.
- Handled_access — какие права вы вообще хотите контролировать (например, чтение, запись).
2. Добавить правила → Указать конкретные пути и разрешённые операции.
- Allowed_access — что именно разрешено для заданного пути
3. Применить правила → Включить ограничения для процесса.
- Правила начинают применяться после landlock_restrict_self(), а ограничения наследуются дочерними процессами (fork, exec).
Псевдокод:
// Хочу контролировать чтение и запись ruleset = create_ruleset(READ | WRITE); // /usr можно читать add_rule(ruleset, "/usr", READ); // /tmp можно читать и писать add_rule(ruleset, "/tmp", READ | WRITE); // Всё остальное: заблокировано! restrict_self(ruleset);
После restrict_self() процесс (и его дочерние процессы) может только читать из /usr и читать/писать в /tmp. Любая попытка обратиться к /home или /etc завершится ошибкой EACCES.
Давайте соберём песочницу
Приложение позволяет запускать программы из командной строки и задавать разрешения через конфигурационный файл. Полный код доступен здесь: Landlock — Sandbox. В main мы загружаем конфигурацию и параметры, переданные через командную строку. Затем создаём набор правил, добавляем разрешения для путей и сети и активируем Landlock. Наконец, запускаем программу, для которой хотим ограничить доступ.
// sandbox.cpp int main(int argc, char* argv[], char *const *const envp) { ... landlock.create_ruleset(fs_restrictions, net_restrictions); for (auto& it : path_perms) { landlock.add_rule(it.first, it.second); } ... if (net_port >= 0) { landlock.add_net_rule(static_cast<__u64>(net_port), net_permissions); ... landlock.restrict_self(no_new_priv); ... execvpe(cmd_args_c[0], cmd_args_c.data(), envp); }
Теперь можно перейти к файлу landlock.cpp и разобрать реализацию. Сначала нам нужен заголовок landlock.h.
#include <linux/landlock.h>
Ниже приведены все доступные права вплоть до ABI v5. Рекомендую прочитать комментарии в заголовке landlock.h — там отлично описаны все символы.
... static const std::map<std::string, __u64> LANDLOCK_FS_MAP = { {"execute", LANDLOCK_ACCESS_FS_EXECUTE}, {"read_file", LANDLOCK_ACCESS_FS_READ_FILE}, {"write_file", LANDLOCK_ACCESS_FS_WRITE_FILE}, {"read_dir", LANDLOCK_ACCESS_FS_READ_DIR}, {"remove_dir", LANDLOCK_ACCESS_FS_REMOVE_DIR}, {"remove_file", LANDLOCK_ACCESS_FS_REMOVE_FILE}, {"make_char", LANDLOCK_ACCESS_FS_MAKE_CHAR}, {"make_dir", LANDLOCK_ACCESS_FS_MAKE_DIR}, {"make_reg", LANDLOCK_ACCESS_FS_MAKE_REG}, {"make_sock", LANDLOCK_ACCESS_FS_MAKE_SOCK}, {"make_fifo", LANDLOCK_ACCESS_FS_MAKE_FIFO}, {"make_block", LANDLOCK_ACCESS_FS_MAKE_BLOCK}, {"make_sym", LANDLOCK_ACCESS_FS_MAKE_SYM}, /* ABI v2 */ {"refer", LANDLOCK_ACCESS_FS_REFER}, /* ABI v3 */ {"truncate", LANDLOCK_ACCESS_FS_TRUNCATE}, /* ABI v5 */ {"ioctl_dev", LANDLOCK_ACCESS_FS_IOCTL_DEV}, }; static const std::map<std::string, __u64> LANDLOCK_NET_MAP = { /* ABI v4 */ {"bind_tcp", LANDLOCK_ACCESS_NET_BIND_TCP}, {"connect_tcp", LANDLOCK_ACCESS_NET_CONNECT_TCP}, };
Далее у нас есть три новых системных вызова: landlock_create_ruleset, landlock_add_rule, landlock_restrict_self.
... static inline int sys_create_ruleset( const struct landlock_ruleset_attr *attr, size_t attr_size, __u32 flags ) { return syscall(__NR_landlock_create_ruleset, attr, attr_size, flags); } static inline int sys_add_rule( int ruleset_fd, enum landlock_rule_type rule_type, const void *rule_attr, __u32 flags ) { return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags); } static inline int sys_restrict_self( int ruleset_fd, __u32 flags ) { return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); }
Чтобы создать новый набор правил, необходимо передать структуру landlock_ruleset_attr, содержащую битовые маски прав доступа, которые должны контролироваться по умолчанию. В зависимости от версии ABI доступны поля для прав доступа к файловой системе и сети. Важный шаг — адаптация прав под конкретную версию ABI. Системный вызов без параметров возвращает доступную версию ABI.
... int Landlock::create_ruleset( const std::vector<std::string>& fs_restr, const std::vector<std::string>& net_restr) { ... struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = fs_access, .handled_access_net = net_access, }; int abi = sys_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); if (abi < 0) { /* Корректная деградация, если Landlock не поддерживается. */ std::cerr << "Текущее ядро не поддерживает API Landlock\n"; return 0; } switch (abi) { case 1: /* Удаляем LANDLOCK_ACCESS_FS_REFER для ABI < 2 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; __attribute__((fallthrough)); case 2: /* Удаляем LANDLOCK_ACCESS_FS_TRUNCATE для ABI < 3 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; __attribute__((fallthrough)); case 3: /* Удаляем поддержку сети для ABI < 4 */ ruleset_attr.handled_access_net &= ~(LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP); __attribute__((fallthrough)); case 4: /* Удаляем LANDLOCK_ACCESS_FS_IOCTL_DEV для ABI < 5 */ #ifdef LANDLOCK_ACCESS_FS_IOCTL_DEV ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; #endif break; } ruleset_fd = sys_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ... }
Чтобы добавить разрешения для путей, нужно передать структуру landlock_path_beneath_attr, содержащую файловый дескриптор пути и битовую маску прав доступа.
int Landlock::add_rule( const std::string path, std::vector<std::string>& fs_perms) { int path_fd; struct landlock_path_beneath_attr path_beneath = {0}; if (open_path(path, path_fd) < 0) { return -1; } __u64 fs_access = make_allowed_mask(fs_perms); path_beneath.parent_fd = path_fd; path_beneath.allowed_access = fs_access; if (sys_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0) < 0) ... }
Сетевые разрешения добавляются в набор правил.
sys_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &net_port, 0)
Наконец, мы вызываем landlock_restrict_self. Этот системный вызов вернёт ошибку, если для процесса не установлен флаг PR_SET_NO_NEW_PRIVS. Без этого флага процесс может повысить свои привилегии уже после применения ограничений, например запустив бинарник с флагом setuid.
... int Landlock::restrict_self(bool no_new_privs) { /* Устанавливаем no_new_privs (обязательно перед landlock_restrict_self) */ ... if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) ... /* Применяем набор правил */ if (sys_restrict_self(ruleset_fd, 0) < 0) ... }
Как встроить Landlock в приложение
Стратегия выбора разрешений:
Начните с минимума: только
READ_FILE+READ_DIRПостепенно добавляйте права по мере необходимости приложению
Проверьте, что всё работает с включёнными ограничениями
Зафиксируйте в документации, зачем нужно каждое разрешение
Версии ABI
Landlock развивался в нескольких версиях ядра. Каждая версия ABI добавляет новые возможности:
ABI | Ядро | Ключевая возможность |
1 | 5.13 | Базовые ограничения доступа к файловой системе |
2 | 5.19 | REFER — контроль операций, создающих/перемещающих ссылки на объекты (link/rename и связанные операции) |
3 | 6.2 | TRUNCATE — контроль усечения файлов |
4 | 6.7 | Сеть — контроль bind/connect для TCP |
5 | 6.10 | IOCTL_DEV — контроль ioctl на устройствах |
6 | 6.12 | IPC — сигналы и абстрактные сокеты |
Ловушка файловых дескрипторов
Файловый дескриптор, открытый до применения ограничений, остаётся доступным.
int fd = open("/etc/passwd", O_RDONLY); // Открываем ДО включения песочницы apply_landlock_sandbox(); // Это по-прежнему работает! Дескриптор уже открыт char buf[1024]; read(fd, buf, sizeof(buf));
Решение: закрыть все дескрипторы перед вызовом restrict_self или использовать флаг O_CLOEXEC при вызовах open*.
Как протестировать приложение?
Всё просто. Скомпилируйте приложение, доступное здесь: Landlock — Sandbox. Проверьте, как настройки влияют на поведение программы.
Приложение vulnerable_server.py позволяет выполнять команды над системными файлами, что упрощает изучение поведения API.
./sandbox -- python3 vulnerable_server.py
Поддерживает ли моё ядро Landlock?
Проверьте на своей системе:
dmesg | grep landlock uname -r
Подведем итоги
Landlock меняет подход к безопасности Linux-приложений, позволяя разработчикам напрямую задавать ограничения доступа к путям и сокетам. Развитие Landlock продолжается, и в будущем ожидаются новые улучшения.
Надеюсь, материал был полезен. Спасибо за прочтение!
Документация и ресурсы:

Если вы только заходите в Linux и хотите разобраться с нуля, важно построить базу: файловая модель, права, процессы, сеть, инструменты диагностики. На специализации Administrator Linux эти основы раскладывают по полочкам и доводят до практики — чтобы потом уверенно применять вещи вроде Landlock, а не копировать команды вслепую. Для знакомства с форматом обучения и экспертами приходите на бесплатные демо-уроки:
26 февраля 20:00. «С Windows на Linux: первый шаг системного администратора». Записаться
4 марта 20:00. «GREP и другие регулярные выражения Linux». Записаться
12 марта 20:00. «Перенаправление потоков в Linux». Записаться
Для тех, кто хочет быстро подтянуть основы, рекомендуем мини-видеокурс «Linux для начинающих», сейчас всего за 10 рублей.
