Seccomp

seccomp (сокращение от англ. secure computing mode) — средство обеспечения безопасности ядра Linux, предназначенное для ограничения доступных процессу системных вызовов. В сочетании с механизмом BPF (Berkeley Packet Filter) позволяет реализовывать гибкую фильтрацию вызовов и их аргументов на основе заданных правил.

Впервые появился в ядре версии 2.6.12 в 2005 году[1][2].

Общие сведения
seccomp
Тип механизм безопасности ядра
Разработчик Андреа Арканджели
Написана на Си
Операционная система Linux
Первый выпуск март 2005
Лицензия GPL
Сайт code.google.com/p/seccom…

История

Механизм seccomp разработал Андреа Арканджели (итал. Andrea Arcangeli) в рамках коммерческого проекта CPUShare. В основе его идеи было предоставление вычислительных ресурсов компьютеров, в частности с ОС Linux, для исполнения стороннего кода. Впервые появился в ядре Linux в марте 2005 года (версия 2.6.12). В первой версии, чтобы включить seccomp, процесс должен был записать «1» в файл /proc/PID/seccomp. После этого гостевому коду были доступны только четыре системных вызова: read(), write(), exit() и sigreturn()[3]. В 2007 году в версии 2.6.23 была добавлена возможность включения seccomp через системный вызов prctl() с помощью операции PR_SET_SECCOMP, а интерфейс через /proc был исключён[4].

Несмотря на то, что проект CPUShare не получил развития, seccomp остался в ядре Linux[3]. В феврале 2009 года в реализации seccomp была обнаружена уязвимость, обусловленная несовпадением номеров системных вызовов в 32-битной и 64-битной архитектурах ядра Linux. Например, разрешённому вызову read() из 64-битной версии соответствовал запрещённый вызов restart_syscall() из 32-битной версии. После этого возникло обсуждение об исключении seccomp из Linux, а Линус Торвальдс предположил, что seccomp никем не используется[5].

Поскольку seccomp был достаточно прост для использования, у разработчиков Google Chrome возникла идея реализовать на его основе средство для запуска сторонних плагинов. Однако для реализации этой задачи простого блокирования всех системных вызовов за исключением четырёх разрешённых было недостаточно. В итоге в 2012 году с seccomp был интегрирован механизм BPF (Berkeley Packet Filter) (в версии 3.5 добавлен второй режим работы — SECCOMP_MODE_FILTER), что значительно расширило возможности механизма: после нововведения гостевой процесс мог более гибко выбирать набор разрешённых и запрещённых системных вызовов, присоединяя соответствующую BPF-программу. В 2012 году появилась также библиотека libseccomp, предоставляющая простой API для фильтрации системных вызовов[4].

Для упрощения использования seccomp, в 2013 году в версии 3.8 было добавлено поле «Seccomp» в /proc/PID/status, позволяющее выяснить состояние seccomp. В 2014 году в версии 3.17 был добавлен специальный системный вызов — seccomp(), который частично повторяет функциональность prctl()[4].

В ноябре 2017 года, с выходом glibc версии 2.26, появилась новая проблема: поскольку в программах на языке C системные вызовы делаются не напрямую, а через обёртки стандартной библиотеки, названия которых могут не совпадать с названиями системных вызовов, запрет какого-либо вызова может неожиданно повлиять на программу. Например, функция open() из glibc версии 2.26 реализована через системный вызов openat(), который не совпадает с вызовом open() и может быть ошибочно заблокирован автором фильтра[6].

Описание

Для работы с недоверенными или потенциально опасными программами желательно использовать специально выделенные среды — песочницы или контейнеры, — из которых нельзя нанести вред работоспособности системы в целом. В таких средах для запускаемых программ лимитированы многие системные возможности, такие как доступ к сети, устройствам ввода-вывода, взаимодействие с операционной системой. Механизм seccomp определяет для процесса набор разрешённых системных вызовов и блокирует те, которые не были заранее объявлены. В настоящее время используется в ряде браузеров, Linux-подобных ОС и некоторых системах виртуализации.

Для включения seccomp в программе используется системный вызов seccomp() или prctl(). Существует два режима работы: SECCOMP_MODE_STRICT и SECCOMP_MODE_FILTER. Узнать, включён ли seccomp и в каком режиме, можно по полю «Seccomp» в файле /proc/[pid]/status. Поле принимает значения: 0 — seccomp не включён, 1 — режим SECCOMP_MODE_STRICT, 2 — режим SECCOMP_MODE_FILTER[4] Включить seccomp для других процессов невозможно[7].

Режим SECCOMP_MODE_STRICT

Это единственный режим работы до Linux 3.5. Для его использования ядро должно быть сконфигурировано с ключом CONFIG_SECCOMP=y[2].

Чтобы войти в этот режим, выполняется вызов prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) или seccomp(SECCOMP_SET_MODE_STRICT, 0, NULL). После этого процесс может использовать лишь четыре системных вызова: read(), write(), _exit() и sigreturn()[3]. Попытка использования других системных вызовов приведёт к прерыванию процесса сигналом SIGKILL. Так как open() запрещён, проверку включения seccomp происходит желательно до входа в ограниченный режим[4].

Режим SECCOMP_MODE_FILTER

Режим появился в ядре Linux версии 3.5.[8]. Он доступен, если ядро собрано с флагом CONFIG_SECCOMP_FILTER=y.

Перед переходом в этот режим необходимо выполнить вызов prctl(PR_SET_NO_NEW_PRIVS, 1), чтобы установить бит no_new_privs. Это обязательное требование связано с тем, чтобы предотвратить возможность повышения привилегий через execve(). Без этого перейти в режим SECCOMP_MODE_FILTER не удастся[4].

Далее, чтобы присоединить к процессу фильтр, выполняется вызов prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args) или seccomp(SECCOMP_SET_MODE_FILTER, 0, args), где args — указатель на структуру sock_fprog, содержащую массив BPF-инструкций и его длину. Фильтр запускается каждый раз при системном вызове, получая на вход структуру с данными: номер вызова, архитектуру, состояние счётчика команд и аргументы[8]. Фильтр должен возвращать 32-битное значение: верхние 16 бит — действие, нижние 16 — данные. Фильтры, в которых не предусмотрён возврат значения, не будут установлены[9].

Если установлено несколько фильтров, они объединяются в односвязный список и запускаются в порядке, обратном их добавлению, даже если один фильтр должен завершить процесс.

Приоритет действий определяется следующим образом[10]:

Действие Результат
SECCOMP_RET_KILL Системный вызов не выполняется, процесс завершён сигналом SIGSYS[4]
SECCOMP_RET_TRAP Системный вызов не выполняется, процесс получает сигнал SIGSYS, обработчику сигнала доступна информация о вызове
SECCOMP_RET_ERRNO Системный вызов не выполняется, в переменной errno содержится возвращённое фильтром значение
SECCOMP_RET_TRACE Если для процесса указан трассировщик через ptrace(), он будет уведомлён о вызове. Если не указан — вызов блокируется
SECCOMP_RET_ALLOW Системный вызов выполняется

Примеры использования

Включение режима SECCOMP_MODE_STRICT[4]

# include <stdio.h>
# include <unistd.h>
# include <linux/seccomp.h>
# include <sys/prctl.h>
# include <fcntl.h>

int main () {
    int fd; 

    prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);

    fprintf(stderr, "try open\n");
    fd = open("test_file", O_CREAT);
    fprintf(stderr, "fd = %d", fd);

    return 0;
}

Результат работы:

$ gcc test_seccomp.c -o test_seccomp
$ ./test_seccomp
try open
Killed

В приведённом примере после вызова prctl() процесс работает ровно до момента, когда попытается сделать вызов open().

Примеры использования в ПО

  • Операционная система Chrome OS[11];
  • Операционная система Android Oreo;
  • Браузер Firefox;
  • FTP-сервер vsftpd[1];
  • Flatpak — инструмент для распространения и установки приложений внутри контейнеров;
  • Система виртуализации Docker[12];
  • Браузер Chromium[1];
  • Система контейнерной виртуализации LXC[13].

Примечания

  1. 1 2 3 Imamjafar Borate, Chavan R. K. (2016). “Sandboxing in Linux: From Smartphone to Cloud” (PDF). International Journal of Computer Applications [англ.]. 148 (8). Дата обращения 2024-06-15.
  2. 1 2 Greg Kroah-Hartman. Linux Kernel in a Nutshell. — O'Reilly Media, 2007. — P. 208. — ISBN 978-0596100797.
  3. 1 2 3 Kurt Dietrich, Johannes Winter (2011). “Towards a Trustworthy, Lightweight Cloud Computing Framework for Embedded Systems”. Trust and Trustworthy Computing: 4th International Conference [англ.]. Pittsburgh, PA, USA: Springer. Дата обращения 2024-06-15.
  4. 1 2 3 4 5 6 7 8 Jake Edge, Michael Kerrisk (2015). “A seccomp overview”. LWN.net [англ.]. Дата обращения 2024-06-15.
  5. Seccomp and sandboxing. LWN.net. Eklektix, Inc. (13 мая 2009). Дата обращения: 15 июня 2024. Архивировано 12 ноября 2017 года.
  6. The inherent fragility of seccomp(). LWN.net (10 ноября 2017). Дата обращения: 15 июня 2024. Архивировано 9 декабря 2017 года.
  7. Lingguang Lei, Jianhua Sun, Kun Sun, Chris Shenefiel, Rui Ma, Yuewu Wang, Qi Li (2017). “SPEAKER: Split-Phase Execution of Application Containers”. Detection of Intrusions and Malware, and Vulnerability Assessment: 14th International Conference, DIMVA 2017 [англ.]. Bonn, Germany: Springer. Дата обращения 2024-06-15.
  8. 1 2 Taesoo Kim, Nickolai Zeldovich (2013). “Practical and effective sandboxing for non-root users” (PDF). 2013 USENIX Annual Technical Conference [англ.]. Дата обращения 2024-06-15.
  9. Steven McCanne, Van Jacobson (1993). “The BSD Packet Filter: A New Architecture for User-level Packet Capture” (PDF). 1993 Winter USENIX. Дата обращения 2024-06-15.
  10. SECure COMPuting with filters. The Linux Kernel Archives. Linux Kernel Organization. Дата обращения: 15 июня 2024. Архивировано 13 октября 2017 года.
  11. Anto.Y. Chrome OS and Secret of Google. — Lambert Academic Publishing, 2012. — P. 41. — ISBN 978-3-659-17127-7.
  12. Adrian Mouat. Using Docker: Developing and Deploying Software with Containers. — O'Reilly Media, 2015. — P. 320. — ISBN 978-1-4842-3024-4.
  13. Senthil Kumaran S. Practical LXC and LXD: Linux Containers for Virtualization and Orchestration. — Apress, 2017. — P. 147. — ISBN 978-1-4842-3024-4.

Литература

Ссылки

Дополнительно по теме