Перейти к содержанию

Seccomp

Эта статья находится на начальном уровне проработки, в одной из её версий выборочно используется текст из источника, распространяемого под свободной лицензией
Материал из энциклопедии Руниверсалис

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

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

История

Механизм 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 (после того, как включение убрали из /proc, узнать, работает ли уже включённый seccomp было невозможно без завершения процесса). В 2014 году в версии 3.17 добавлен специальный системный вызов — seccomp(), который частично повторяет функциональность prctl()[4].

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

Описание

Чтобы включить в программе seccomp, можно воспользоваться системным вызовом seccomp() или prctl(). На данный момент существуют два режима работы seccomp: SECCOMP_MODE_STRICT и SECCOMP_MODE_FILTER. Узнать, включён ли seccomp и в каком режиме, можно из поля «Seccomp» в файла /proc/[pid]/status. Поле принимает значения 0, 1 или 2: 0 — seccomp для процесса не включён, 1 — seccomp в режиме 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, файл status из procfs необходимо открыть с правами на чтение до включения 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].

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

Если к процессу присоединено несколько фильтров, из них будет сформирован односвязный список и они будут запущены в порядке, обратном порядку их добавления, даже если один из фильтров должен убить процесс[7]. В таком случае, возвращаемое значение системного вызова будет определяться первым (по порядку запуска фильтров) возвращаемым значением с наивысшим приоритетом, в соответствии с таблицей (в порядке уменьшения приоритета)[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,0 1,1 1,2 Imamjafar Borate, Chavan R. K., 2016.
  2. 2,0 2,1 Kroah-Hartman, 2007.
  3. 3,0 3,1 3,2 Kurt Dietrich, Johannes Winter, 2011.
  4. 4,0 4,1 4,2 4,3 4,4 4,5 4,6 4,7 Jake Edge, Michael Kerrisk, 2015.
  5. Jonathan Corbet. Seccomp and sandboxing // LWN.net. — 2009. — 13 мая. Архивировано 12 ноября 2017 года.
  6. Jonathan Corbet. The inherent fragility of seccomp() // LWN.net. — 2017. — 1 ноября. Архивировано 9 декабря 2017 года.
  7. 7,0 7,1 7,2 Lingguang Lei, Jianhua Sun, Kun Sun, Chris Shenefiel, Rui Ma, Yuewu Wang, Qi Li, 2017.
  8. 8,0 8,1 Taesoo Kim, Nickolai Zeldovich, 2013.
  9. Steven McCanne, Van Jacobson, 1993.
  10. SECure COMPuting with filters. The Linux Kernel Archives. Linux Kernel Organization. Дата обращения: 3 марта 2018. Архивировано 13 октября 2017 года.
  11. Anto.Y, 2012.
  12. Adrian Mouat, 2015.
  13. Senthil Kumaran S., 2017.

Литература

  • Steven McCanne, Van Jacobson. The BSD Packet Filter: A New Architecture for User-level Packet Capture // 1993 Winter USENIX. — San Diego, CA, 1993. — 2 января.
  • Greg Kroah-Hartman. Linux Kernel in a Nutshell. — 1005 Gravenstein Highway North, Sebastopol, CA 95472: O'Reilly Media, 2007. — 208 с. — ISBN 978-0596100797.
  • Michael Kerrisk. The Linux Programming Interface. — San Francisco: No Starch Press, 2010. — 1556 с. — ISBN 978-1-59327-220-3.
  • Jake Edge, Michael Kerrisk. A seccomp overview // LWN.net : Linux Plumbers Conference. — Seattle, Washington, USA, 2015. — 1 сентября.
  • Taesoo Kim, Nickolai Zeldovich. Practical and effective sandboxing for non-root users // 2013 USENIX Annual Technical Conference. — 2013.
  • Ma Bo, Mu Dejun, Fan Wei, Hu Wei. Improvements the Seccomp sandbox based on PBE theory (англ.) // IEEE. — 2013. — 01 Июль. — doi:10.1109/WAINA.2013.81.
  • Imamjafar Borate, Chavan R. K. Sandboxing in Linux: From Smartphone to Cloud (англ.) // International Journal of Computer Applications. — 2016. — Август (т. 148, № 8).
  • Senthil Kumaran S. Practical LXC and LXD: Linux Containers for Virtualization and Orchestration. — Apress, 2017. — С. 147. — 159 с. — ISBN 978-1-4842-3024-4.
  • Adrian Mouat. Using Docker: Developing and Deploying Software with Containers. — 1005 Gravenstein Highway North, Sebastopol, CA 95472: O'Reilly Media, 2015. — С. 320. — 354 с.
  • Kurt Dietrich, Johannes Winter. Towards a Trustworthy, Lightweight Cloud Computing Framework for Embedded Systems // Trust and Trustworthy Computing: 4th International Conference. — Pittsburgh, PA, USA: Springer, 2011. — Июнь.
  • Anto.Y. Chrome OS and Secret of Google. — Lambert Academic Publishing, 2012. — С. 41. — 228 с. — ISBN 978-3-659-17127-7.
  • Lingguang Lei, Jianhua Sun, Kun Sun, Chris Shenefiel, Rui Ma, Yuewu Wang, Qi Li. SPEAKER: Split-Phase Execution of Application Containers // Detection of Intrusions and Malware, and Vulnerability Assessment: 14th International Conference, DIMVA 2017. — Bonn, Germany: Springer, 2017. — 1 июля.

Ссылки