Новости
24.08.2020
Книга «BPF для мониторинга Linux»
Дэвид Калавера и Лоренцо Фонтана помогут вам раскрыть возможности BPF. Расширьте свои знания об оптимизации производительности, сетях, безопасности. — Используйте BPF для отслеживания и модификации поведения ядра Linux. — Внедряйте код для безопасного мониторинга событий в ядре — без необходимости перекомпилировать ядро или перезагружать систему. — Пользуйтесь удобными примерами кода на C, Go или Python. — Управляйте ситуацией, владея жизненным циклом программы BPF.
Безопасность ядра Linux, его возможности и Seccomp
BPF предоставляет мощный способ расширения ядра без ущерба для стабильности, безопасности и скорости. По этой причине разработчики ядра подумали, что было бы неплохо использовать его универсальность для улучшения изоляции процессов в Seccomp путем реализации фильтров Seccomp, поддерживаемых программами BPF, известными также как Seccomp BPF. В этой главе мы расскажем, что такое Seccomp и как он применяется. Затем вы узнаете, как писать фильтры Seccomp с помощью программ BPF. После этого рассмотрим встроенные ловушки BPF, которые есть в ядре для модулей безопасности Linux.
Модули безопасности Linux (LSM) — это платформа, предоставляющая набор функций, которые можно применять для стандартизированной реализации различных моделей безопасности. LSM может использоваться непосредственно в дереве исходного кода ядра, например Apparmor, SELinux и Tomoyo.
Начнем с обсуждения возможностей Linux.
Возможности
Суть возможностей Linux заключается в том, что вам нужно предоставить непривилегированному процессу разрешение на выполнение определенной задачи, но без использования suid для этой цели, или иным образом сделать процесс привилегированным, уменьшая возможность атак и обеспечивая процессу возможность выполнения определенных задач. Например, если вашему приложению нужно открыть привилегированный порт, допустим, 80, вместо запуска процесса от имени root можете просто предоставить ему возможность CAP_NET_BIND_SERVICE.
Рассмотрим программу Go с именем main.go:
Эта программа обслуживает HTTP-сервер на порте 80 (это привилегированный порт). Обычно мы запускаем ее сразу после компиляции:
Однако, поскольку мы не предоставляем привилегии root, этот код выдаст ошибку при привязке порта:
capsh (средство управления оболочкой) — это инструмент, который запускает оболочку с определенным набором возможностей.
В этом случае, как уже говорилось, вместо предоставления полных прав root можно разрешить привязку привилегированных портов, предоставив возможность cap_net_bind_service наряду со всеми остальными, которые уже есть в программе. Для этого можем заключить нашу программу в capsh:
Немного разберемся в этой команде.
- capsh — используем capsh в качестве оболочки.
- --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — поскольку нам нужно сменить пользователя (мы не хотим запускаться с правами root), укажем cap_net_bind_service и возможность фактически изменить идентификатор пользователя с root на nobody, а именно cap_setuid и cap_setgid.
- --keep=1 — хотим сохранить установленные возможности, когда выполнено переключение с аккаунта root.
- --user=«nobody» — конечным пользователем, запускающим программу, будет nobody.
- --addamb=cap_net_bind_service — задаем очистку связанных возможностей после переключения из режима root.
- — -c "./capabilities" — просто запускаем программу.
Связанные возможности — это особый вид возможностей, которые наследуются дочерними программами, когда текущая программа выполняет их с помощью execve(). Наследоваться могут только возможности, разрешенные как связанные, или, другими словами, как возможности окружения.
Вероятно, вам интересно, что значит +eip после указания возможности в опции --caps. Эти флаги используются для определения того, что возможность:
-должна быть активирована (p);
-доступна для применения (е);
-может быть унаследована дочерними процессами (i).
Поскольку мы хотим использовать cap_net_bind_service, нужно сделать это с флагом e. Затем запустим оболочку в команде. В результате запустится двоичный файл capabilities, и нам нужно пометить его флагом i. Наконец, мы хотим, чтобы возможность была активирована (мы это сделали, не меняя UID) с помощью p. Это выглядит как cap_net_bind_service+eip.
Можете проверить результат с помощью ss. Немного сократим вывод, чтобы он поместился на странице, но он покажет связанный порт и идентификатор пользователя, отличные от 0, в данном случае 65 534:
В этом примере мы использовали capsh, но вы можете написать оболочку с помощью libcap. За дополнительной информацией обращайтесь к man 3 libcap.
При написании программ разработчик довольно часто не знает заранее всех возможностей, необходимых программе во время выполнения; более того, в новых версиях эти возможности могут измениться.
Чтобы лучше понять возможности нашей программы, можем взять инструмент BCC capable, который устанавливает kprobe для функции ядра cap_capable:
Добиться того же самого мы можем, используя bpftrace с однострочным kprobe в функции ядра cap_capable:
Это выведет что-то вроде следующего, если возможности нашей программы будут активированы после kprobe:
Пятый столбец — это возможности, в которых нуждается процесс, и, поскольку эти выходные данные включают в том числе неаудитные события, мы видим все неаудитные проверки и, наконец, требуемую возможность с флагом аудита (последний в выводе), установленным в 1. Возможность, которая нас интересует, — CAP_NET_BIND_SERVICE, она определяется как константа в исходном коде ядра в файле include/uapi/linux/ability.h с идентификатором 10:
Возможности часто задействуются во время выполнения контейнеров, таких как runC или Docker, чтобы они работали в непривилегированном режиме, но им были разрешены только те возможности, которые необходимы для запуска большинства приложений. Когда приложению требуются определенные возможности, в Docker можно обеспечить их с помощью --cap-add:
Эта команда предоставит контейнеру возможность CAP_NET_ADMIN, что позволит ему настроить сетевую ссылку для добавления интерфейса dummy0.
В следующем разделе показано использование таких возможностей, как фильтрация, но с помощью другого метода, который позволит нам программно реализовать собственные фильтры.
Seccomp
Seccomp означает Secure Computing, это уровень безопасности, реализованный в ядре Linux, который позволяет разработчикам фильтровать определенные системные вызовы. Хотя Seccomp сопоставим с возможностями Linux, его способность управлять определенными системными вызовами делает его намного более гибким по сравнению с ними.
Seccomp и возможности Linux не исключают друг друга, их часто используют вместе, чтобы получить пользу от обоих подходов. Например, вы можете захотеть предоставить процессу возможность CAP_NET_ADMIN, но не разрешить ему принимать соединения через сокет, блокируя системные вызовы accept и accept4.
Способ фильтрации Seccomp основан на фильтрах BPF, работающих в режиме SECCOMP_MODE_FILTER, и фильтрация системных вызовов выполняется так же, как и для пакетов.
Фильтры Seccomp загружаются с использованием prctl через операцию PR_SET_SECCOMP. Эти фильтры имеют форму программы BPF, которая выполняется для каждого пакета Seccomp, представленного с помощью структуры seccomp_data. Эта структура содержит эталонную архитектуру, указатель инструкций процессора во время системного вызова и максимум шесть аргументов системного вызова, выраженных как uint64.
Вот как выглядит структура seccomp_data из исходного кода ядра в файле linux/seccomp.h:
Как видно из этой структуры, мы можем фильтровать по системному вызову, его аргументам или их комбинации.
После получения каждого пакета Seccomp фильтр обязан выполнить обработку, чтобы принять окончательное решение, и сообщить ядру, что делать дальше. Окончательное решение выражается одним из возвращаемых значений (кодов состояния).
— SECCOMP_RET_KILL_PROCESS — завершение всего процесса сразу же после фильтрации системного вызова, который из-за этого не выполняется.
— SECCOMP_RET_KILL_THREAD — завершение текущего потока сразу же после фильтрации системного вызова, который из-за этого не выполняется.
— SECCOMP_RET_KILL — алиас для SECCOMP_RET_KILL_THREAD, оставлен для обратной совместимости.
— SECCOMP_RET_TRAP — системный вызов запрещен, и сигнал SIGSYS (Bad System Call) отправляется вызывающей его задаче.
— SECCOMP_RET_ERRNO — системный вызов не выполняется, и часть возвращаемого значения фильтра SECCOMP_RET_DATA передается в пространство пользователя как значение errno. В зависимости от причины ошибки возвращаются разные значения errno. Список номеров ошибок приведен в следующем разделе.
— SECCOMP_RET_TRACE — используется для уведомления трассировщика ptrace с помощью — PTRACE_O_TRACESECCOMP для перехвата, когда выполняется системный вызов, чтобы видеть и контролировать этот процесс. Если трассировщик не подключен, возвращается ошибка, errno устанавливается в -ENOSYS, а системный вызов не выполняется.
— SECCOMP_RET_LOG — системный вызов разрешен и зарегистрирован в журнале.
— SECCOMP_RET_ALLOW — системный вызов просто разрешен.
ptrace — это системный вызов для реализации механизмов трассировки в процессе, называемом tracee, с возможностью наблюдения и контроля за выполнением процесса. Программа трассировки может эффективно влиять на выполнение и изменять регистры памяти tracee. В контексте Seccomp ptrace используется, когда запускается кодом состояния SECCOMP_RET_TRACE, следовательно, трассировщик может предотвратить выполнение системного вызова и реализовать собственную логику.
С полным содержанием статьи можно ознакомиться на сайте "Хабрахабр":
Комментарии: 0
Пока нет комментариев