Ej-suid-container

From EjudgeWiki

Навигация: Главная страница/Система ejudge/Использование/Общая архитектура системы/ej-suid-container

Программа доступна с версии 3.9.0.

Данная программа реализует защищенный запуск программ на выполнение с использованием контейнеров Linux. Для программы создается изолированное окружение, в котором недоступна сеть, не видны другие процессы в системе, и скрыты некоторые каталоги файловой системы, а вместо некоторых других каталогов отображаются специально подготовленные образы. По умолчанию тестируемой программе запрещено исполнять некоторые системные вызовы.

Для правильной работы программа должна быть установлена как suid root программа. Она заменяет собой программы ej-suid-chown, ej-suid-exec, ej-suid-ipcrm, ej-suid-kill, то есть при использовании ej-suid-container данные программы могут быть удалены.

В текущей версии программа предназначена прежде всего для использования из других программ, поэтому не ставит целью вернуть информацию о завершившейся программе в человеко-читаемом виде или принимать аргументы командной строки в удобном для человека виде.

Изоляция запускаемой программы

Программа запускается в новом пространстве идентификаторов процессов Linux (pid namespaces). Идентификатор процесса 1 имеет процесс, который мониторит поведение выполняющейся программы. Он играет роль локального процесса init. Запускаемая программа получает идентификатор процесса 2. Если программа будет создавать новые процессы, им будут выдаваться последовательные идентификаторы. По умолчанию для пользователя, под которым выполняется запускаемая программа, устанавливается ограничение в 5 процессов.

Программа запускается в новом пространстве имен для объектов IPC. Все семафоры, сегменты разделяемой памяти, очереди сообщений, которые были созданы программой, будут уничтожены автоматически при ее завершении. Запускаемая программа не имеет доступ к объектам IPC на хост-системе.

Программа запускается в новом пространстве имен сетевой подсистемы. В этом пространстве имен не создаются никакие сетевые интерфейсы, то есть запускаемая программа работает с отключенной сетью. Она не имеет доступа к сетевым интерфейсам хост-системы.

Программа запускается в новом пространстве имен монтируемых каталогов файловой системы (mount namespace). Для запускаемой программы файловая система хост-системы модифицируется следующим образом:

  • Демонтируются все файловые системы типов fusectl, rpc_pipefs, securityfs и аналогичных, которые могут использоваться для доступа к системной информации.
  • Рабочий каталог запускаемой программы монтируется в /sandbox. Он будет установлен как текущий каталог при запуске.
  • Каталог /proc перекрывается пустым каталогом.
  • Каталог /sys перекрывается пустым каталогом.
  • Каталог /boot перекрывается пустым каталогом.
  • Каталог /srv перекрывается пустым каталогом.
  • Каталог /data перекрывается пустым каталогом.
  • Если подготовлен образ каталога /root, то этот образ перекрывает каталог /root.
  • Если подготовлен образ каталога /etc, этот образ перекрывает каталог /etc, однако /etc/alternatives и /etc/java монтируется с файловой системы хоста.
  • Если подготовлен образ каталога /var, этот образ перекрывает каталог /var файловой системы.
  • Если подготовлен образ каталога /dev, этот образ перекрывает каталог /dev файловой системы.
  • Каталоги /tmp, /run, /dev/mqueue, /dev/shm инициализируются как пустые каталоги для временных файлов (файловая система tmpfs).
  • Если подготовлен образ каталога /home, этот образ перекрывает каталог /home файловой системы, за исключеним каталога /home/judges/compile, который монтируется с файловой системы хоста. В противном случае каталоги /home/judges/data и /home/judges/var перекрываются пустыми каталогами.

Подготовленные образы каталогов должны располагаться в каталоге EJUDGE_PREFIX_DIR/share/ejudge/container. Eсли EJUDGE_PREFIX_DIR установлен в /opt/ejudge, то каталог должен выглядеть так:

[~]$ ls /opt/ejudge/share/ejudge/container
dev  empty  etc  home  root  var

У запускаемой программы блокируется возможность выполнения "небезопасных" системных вызовов. В текущей версии — это системные вызовы семейства fork (то есть fork, vfork, clone, clone3) и системные вызовы семейства exec (то есть execve, execveat). В дальнейшем список блокируемых системных вызовов может быть расширен. Если программа пытается выполнить запрещенный системный вызов, она получит сигнал SIGSYS (Bad system call).

У запускаемой программы блокируется возможность повышения привилегий с помощью исполнения suid-программ.

Использование

Использование:

ej-suid-container [OPTIONS] PROGRAM [ARGUMENTS]

Здесь OPTIONS — дополнительные опции для настройки контейнеров. PROGRAM — запускаемая программа, ARGUMENTS — аргументы для запускаемой программы.

Все опции для ej-suid-container задаются в одном аргументе командной строки. Между опциями отсутствуют разделители, но может использоваться символ ',' (запятая), который в опциях игнорируется. Опции начинаются со знака "минус".

Если опция требует целого параметра, параметр записывается сразу после опции без разделителя, например lo10.

Если опция требует параметра-размера, то после целого числа может следовать суффикс 'k', 'm' или 'g', задающий соответствующий множитель. Чтобы отделить значение с суффиксом от следующей опции можно использовать запятую.

Если опция требует параметр-строку, сразу же после опции записывается длина строки, затем символ "запятая", затем строка-параметр, например, ri10,/tmp/a.txt. Либо сразу после опции записывается символ-терминатор строки, а далее идет строка до этого символа-терминатора, например, ri|/tmp/a.txt|.

f<FD> файловый дескриптор - целое число задать ф. д. для получения результата выполнения программы Если опция отсутствует, то строка результата выводится на stderr
mg не создавать linux control group (не использовать!)
mi не создавать пространство имен IPC, запускаемая программа будет иметь доступ к объектам IPC хост-системы
mn не создавать пространство имен сетевых интерфейсов, запускаемая программа будет иметь неограниченный доступ к сети
ml в изолированном пространстве сетевых интерфейсов поднять интерфейс lo (loopback) и настроить его на адрес 127.0.0.1 (3.11.0)
mm не создавать пространство имен файловой системы, запускаемая программа будет иметь доступ ко всей файловой системе хост-системы
mp не создавать пространство имен процессов, запускаемая программа будет иметь идентификатор процесса среди процессов хост-системы (не использовать!)
mP не перекрывать файловую систему /proc, хотя в ней будут отображаться только процессы из пространства имен процессов, но общесистемные файлы, например, /proc/sys будут доступны
mS не перекрывать доступ к файловой системе /sys
mv не подменять файловую систему /var, запускаемая программа будет иметь доступ к каталогу /var хост-системы
me не подменять файловую систему /etc, запускаемая программа будет иметь доступ к каталогу /etc хост-системы
ms не привязывать рабочий каталог программы к каталогу /sandbox
mh не подменять файловую систему /home, запускаемая программа будет иметь доступ к каталогу /home хост-системы
md не подменять файловую систему /dev, запускаемая программа будет иметь доступ к каталогу /dev хост-системы
mr не подменять файловую систему /run, запускаемая программа будет иметь доступ к каталогу /run хост-системы (3.11.0)
mo не менять пользователя на ejexec/ejcompile (не использовать!)
mG не создавать новую группу процессов (не использовать!)
mc после завершения запущенной программы посчитать количество оставшихся незавершенных процессов. Может быть полезно, когда запускаемая программа создает другие процессы и необходимо проконтролировать, что все запущенные процессы были завершены перед завершением основной программы. Независимо от значения этой опции все такие процессы будут принудительно завершены, эта опция позволяет получить их количество.
mI посчитать количество объектов IPC, оставшихся неудаленными после завершения программы.
ma не ограничивать процессорное время для запускаемой программы
mb не ограничивать реальное время для запускаемой программы
mD переносить в контейнер не сам рабочий каталог, а его родительский каталог. Используется когда при тестировании в ejudge установлена опция use_tgz.
mC запускать программу в режиме контейнера для компиляции
mV отменить установку значения по умолчанию для лимита виртуальной памяти
mE включить определение ошибки "security violation"
mM включить эвристическое определение ошибки превышения лимита памяти
w<DIR> путь к каталогу - строка задать рабочий каталог для запускаемой программы
rn перенаправить stdin, stdout и stderr на /dev/null
rm слить вывод на stdout и stderr в один поток, задаваемый спецификацией перенаправления для stdout
ri<FILE> путь к файлу - строка перенаправить stdin из файла FILE
ro<FILE> путь к файлу - строка перенаправить stdout в файл FILE в режиме перезаписи
rO<FILE> путь к файлу - строка перенаправить stdout в файл FILE в режиме добавления
re<FILE> путь к файлу - строка перенаправить stderr в файл FILE в режиме перезаписи
rE<FILE> путь к файлу - строка перенаправить stderr в файл FILE в режиме добавления
rp<FILE> путь к файлу - строка запускать на выполнение файл FILE, а не файл, имя которого указано в командной строке. Опция используется, когда у запускаемой программы имя запускаемого файла отличается от argv[0].
ra<FD> ф.д. - целое число перенаправить stdin из заданного открытого файлового дескриптора
rb<FD> ф.д. - целое число перенаправить stdout в заданный открытый файловый дескриптор
lt<T> миллисекунды - целое число установить ограничение на процессорное время в миллисекундах
lr<T> миллисекунды - целое число установить ограничение на реальное время в миллисекундах
lm<M> восьмеричное число установить параметр umask для запускаемой программы
lo<N> целое число установить лимит на количество открытых файловых дескрипторов
lu<N> целое число установить лимит на количество процессов
ls<Z> размер установить лимит на размер стека в байтах
lv<Z> размер установить лимит на размер виртуального адресного пространства в байтах
lR<Z> размер установить лимит на размер потребленной оперативной памяти (RSS) в байтах
lf<Z> размер установить лимит на размер файлов, создаваемых запускаемой программой в байтах
ol<S> имя языка - строка задать язык программирования, на котором написана запускаемая программа
s0 отключить фильтрацию системных вызовов
se разрешить системные вызовы семейства exec
sf разрешить системные вызовы семейства fork
su разрешить системный вызов unshare
sm разрешить системный вызов memfd_create
cf<FD> ф.д. - целое число задать файловый дескриптор для управляющего сокета для управления выполняющейся программой извне
cu<N> целое число задать последовательный номер пользователя, под которым выполнять запущенную программу

Не гарантируется корректная работа программы при использовании опций mo, mG, mp. FIXME: удалить их?

Режим компиляции

Опция mC определяет, что программа должна запускаться в режиме "компилятора". Этот режим предназначен для использования при компиляции программ. Компилятор ограничивается в доступе к различным важным с точки зрения безопасности файлам файловой системы хоста. В режиме компиляции:

  • отключается фильтрация системных вызовов (s0)
  • отключается перекрытие файловой системы /proc (mP)
  • отключается перекрытие файловой системы /sys (mS)
  • отключается перекрытие файловой системы /dev (md)
  • лимит количества процессов устанавливается в 100
  • отключается лимит размера виртуальной памяти
  • отключается лимит реального времени
  • лимит процессорного времени устанавливается в 60000 мс
  • для запуска программы используется пользователь ejcompile

Настройка исходного языка программирования

Опция ol позволяет указать, на каком языке программирования была написана запускаемая программа. В зависимости от этого выполняется донастройка параметров запуска программы. Это сделано для того, чтобы запуск программ на разных языках программирования не требовал бы указания различных опций настройки, зависящих от языка, то есть чтобы любые поддерживаемые языки программирования работали "из коробки".

В таблице перечислены дополнительные настройки для поддерживаемых языков программирования.

языки настройки комментарий
javac7, javac, kotlin, scala s0mPls1M,lu40lv-1 отключается лимит размера виртуальной памяти, ограничение размера выполняется средствами jvm
mcs, vbnc, pasabc-linux s0mPls1M если задано ограничение размера виртуальной памяти, оно переустанавливается на ограничение RSS. Ограничение VM снимается.
pypy, pypy3 mP
gcc-vg, g++-vg s0mP если задано ограничение размера виртуальной памяти, оно переустанавливается на ограничение RSS. Ограничение VM снимается.
dotnet-cs, dotnet-vb s0mPls1M,lu40 если задано ограничение размера виртуальной памяти, оно переустанавливается на ограничение RSS. Ограничение VM снимается.
make s0mP
make-vg s0mP если задано ограничение размера виртуальной памяти, оно переустанавливается на ограничение RSS. Ограничение VM снимается.
gccgo s0mPlu20 если задано ограничение размера виртуальной памяти, оно переустанавливается на ограничение RSS. Ограничение VM снимается.
node s0mPls1M,lu20 если задано ограничение размера виртуальной памяти, оно переустанавливается на ограничение RSS. Ограничение VM снимается.

Настройка пользователя, под которым выполняется программа

Опция cu позволяет модифицировать пользователя, под которым запускается программа. Если опция не указана, то используется пользователь ejexec. Если указан номер, например, cu1, то используется пользователь ejexec1. Соответствующий пользователь должен быть создан.

Внешнее управление выполняющейся программой

Опция cf позволяет задать файловый дескриптор, с помощью которого внешняя программа может управлять выполняющейся программой. Этот файловый дескриптор должен быть двусторонним UNIX-сокетом. Его следует создавать с помощью системного вызова socketpair.

Команда занимает 4 байта и передается в бинарном виде. Поддерживается следующие команды.

  • 0xe00001SS - отправить в тестируемую программу сигнал SS.

Номер сигнала занимает младший байт сообщения.

Возвращаемая информация о работе программы

Программа ej-suid-container завершается с кодом 0, если процесс для запуска указанной программы был создан. В случае фатальной ошибки при создании процесса возвращается код 1.

Информация о выполнении запускаемой программы выводится в файловый дескриптор, переданный с опцией f, либо на стандартный поток ошибок, если опция не задана. Информация выводится в виде закодированной строки в следующем формате.

Сначала идет информация о том, как завершилась программа:

  • t (одна буква 't') — исчерпан лимит процессорного времени
  • r — исчерпан лимит реального времени
  • m — исчерпан лимит памяти
  • v — нарушение ограничений безопасности
  • e<N> (буква 'e', за которой следует число) — программа завершилась с кодом завершения N
  • s<N> (буква 's', за которой следует число) — программа завершилась из-за сигнала N

Далее в произвольном порядке идет детальная информация о завершении программы:

  • T<T> — затраченное процессорное время в микросекундах
  • R<T> — затраченное астрономическое время в микросекундах
  • u<T> — затраченное пользовательское время в микросекундах
  • s<T> — затраченное системное время в микросекундах
  • v<Z> — максимальный размер виртуальной памяти в байтах (неточно)
  • e<Z> — максимальный размер использованной оперативной памяти (RSS) в байтах
  • a<N> — поле ru_nvcsw
  • b<N> — поле ru_nivcsw
  • i<N> — количество объектов IPC, оставшихся после завершения
  • o<N> — количество процессов-сирот, оставшихся после завершения
  • ct<T> — затраченное процессорное время в микросекундах по всем процессам в совокупности
  • cu<T> — затраченное пользовательское время в микросекундах по всем процессам в совокупности
  • cs<T> — затраченное системное время в микросекундах по всем процессам в совокупности
  • L<S> — дополнительные сообщения - строка в формате, описанном выше (длина - запятая - содержимое)

FIXME: выводить на stderr в JSON-формате?