Борьба с зомби-процессами
Ранее я обещал, что постараюсь раскрыть тему безопасного исполнения ненадежного кода (untrusted code). Сегодня будет первая статья из цикла “Как безопасно исполнять ненадежный код” #untrusted_code. Надеюсь, что будет интересно. :)

В Linux каждый процесс знает о своих потомках и должен самостоятельно о них заботиться. Например, если код приложения создает дочерний процесс вызовом системной функции fork(), то он должен дождаться его завершения через вызов функции waitpid(). Вполне логично, ведь “родитель” должен заботиться о своих детях. :)
int pid = fork();
if (pid > 0) {
// Parent logic
waitpid(pid);
} else {
// Children logic
}
Когда процесс завершается, он переходит в статус “зомби” (zombie, defunct), а ОС формирует сигнал SIGCHLD для всех процессов, которые ожидали его завершения с помощью функции waitpid(). Вместе с этим waitpid() заканчивает работу и возвращает управление, а зомби уничтожается - удаляется из системных таблиц ядра ОС. Если никто не ждет дочерний процесс, он продолжает бесконечно висеть в статусе “зомби”, находясь на учете у ОС.
В общем случае зомби-процессы сигнализируют об ошибках ПО, для этого их и придумали. В справках пишут, что зомби-процессы не потребляют ресурсы (RAM/CPU), но, находясь на учете у ОС, расходуют PIDs. Таким образом, рано или поздно может произойти нехватка PIDs. Это значит, что вы не сможете запустить новый процесс или поток!
Самый простой способ создать зомби - убить родительский процесс раньше дочернего. В этом случае ОС назначает сироте нового родителя - опекуна. По дизайну Linux им становится процесс с PID=1, который называют init-процессом. В обязанности init-процесса в том числе входит грязная работа - делать то, что не сделали безответственные родители - дожидаться завершения “зомби” и таким образом удалять их из системных таблиц ядра ОС.
Как правило, в большинстве Docker-образов init-процессом является sh. Так повелось, что sh специально “игнорирует” наличие зомби и не освобождает PIDs. Это сделано для того, чтобы системные администраторы могли увидеть проблему, например, выполнив команду ps, в выводе которой зомби-процессы отображаются как <defunct>. Таким образом, зомби будут висеть в системе бесконечно, до рестарта системы. Игнорирование этой проблемы ведет к утечке PIDs, которая при наличии в ОС лимита на их количество приводит к невозможности запуска новых процессов и потоков. Следовательно, к серьезным проблемам функционирования сервера.
Кстати, на базе этой особенности построена очень простая, но крайне агрессивная атака, называемая Fork bomb (по имени системной функции). Вот вариант ее реализации на C++ - процесс клонирует сам себя в бесконечном цикле:
#include <unistd.h>
int main() {
while (1) fork();
}
Если запустить такой процесс без подготовки, поможет только рестарт. Но даже если получится принудительно завершить все вредоносные процессы, в системных таблицах ядра ОС останется куча зомби-процессов и лимит PIDs будет на гране истощения.
Но что, если мы знаем, что запускаем ненадежный код? Например, реализация какого-нибудь GitLab делает это постоянно, выполняя CI на базе наших скриптов. Для GitLab пользовательские скрипты являются ненадежным кодом, и такие процессы могут быть завершены некорректно или принудительно. В таком приложении наличие зомби-процессов в системе - это норма. Тогда как бороться с зомби и утечкой PIDs?
Решение заключается в подмене init-процесс на такой, который будет удалять зомби сразу, как только они появляются. Делать ничего не нужно, т.к. всё уже сделано за нас. :) Проект называется tini. Использовать легко: в Dockerfile нужно указать tini в качестве ENTRYPOINT.
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini
RUN chmod 755 /usr/bin/tini
ENTRYPOINT [ "tini", "-g", "-s", "--" ]
В дополнение могу порекомендовать книгу “Linux System Programming” (Robert Love). В ней очень кратко и понятно описывается внутреннее устройство Linux и то, как правильно работать с функциями ОС.
Понравилась статья?
Посмею напомнить, что у меня есть Telegram-канал Архитектоника в ИТ, где я публикую материал на похожие темы примерно раз в неделю. Подписчики меня мотивируют, но ещё больше мотивируют живые дискуссии, ведь именно в них рождается истина. Поэтому подписывайтесь на канал и будем оставаться на связи! ;-)
Статьи из той же категории: