Как ограничить размер подпроцесса stdout и stderr в python

Мне нужно запустить приложения, представленные пользователями. Мой код выглядит так:

def run_app(app_path):
    inp = open("app.in", "r")
    otp = open("app.out", "w")

    return subprocess.call(app_path, stdout=otp, stdin=inp)

Теперь, поскольку у меня нет контроля над тем, что пользователи будут отправлять, я хочу ограничить размер вывода приложения. Другие вещи, такие как попытки доступа к неавторизованным системным ресурсам и злоупотребление циклами ЦП, ограничены применением правил apparmor. Максимально допустимое время выполнения обрабатывается родительским процессом (в python). Теперь мошенническое приложение может по-прежнему пытаться заполнить серверную систему, записав много данных в свой стандартный вывод, зная, что стандартный вывод сохраняется в файл.

Я не хочу использовать AppArmors RLIMIT или что-либо в режиме ядра для файлов stdout/stderr. Было бы здорово сделать это из python, используя стандартную библиотеку.

В настоящее время я думаю о создании подкласса файла и при каждой записи проверяю, сколько данных уже записано в поток. Или создайте файл сопоставления памяти с максимальной установленной длиной.

Но я чувствую, что может быть более простой способ ограничения размера файла, которого я пока не вижу.

1 ответ

Наследование file или создание другого псевдофайлового объекта Python вообще не будет работать, так как файл будет использован в подпроцессе - и, следовательно, это должен быть файл ОС, а не объект класса Python. Подпроцесс не отправит ваш объект Python для использования другим процессом.

И хотя Python имеет встроенную и простую поддержку картографических файлов, через mmap модуль, отображение памяти не предназначено для этого: вы можете указать размер файла, который зеркально отражается в памяти, но вы не ограничиваете запись в файл вообще: избыточные данные будут просто записываться на диск и не будут отображаться. (И, опять же, вы передаете подпроцессу файл-диск, а не объект mmap). В какой-то момент можно было бы создать файл со значением Sentinel и сохранить поток, проверяющий, переписан ли Sentinel, и в этот момент он может уничтожить подпроцесс, но я сомневаюсь, что это будет надежно.

Кроме того, существуют инструменты мониторинга активности диска, такие как inotify: вы можете использовать pyinotify для обработчика вашего основного процесса, который будет вызываться при каждом обращении к файлу. Недостаток: нет никакого события для "записи в файл" - просто "файл обработан" - я не уверен, будет ли какое-либо из возможных событий вызвано инкрементной записью файла. И, тем не менее, если дочерний процесс выполнит всю свою запись в одном системном вызове, вы все равно будете уведомлены слишком поздно.

Итак, я думаю, что это сработает: создайте файл в искусственно ограниченной файловой системе. Таким образом, ОС будет блокировать запись при превышении максимального размера.

В Linux вы можете предварительно создать файл с нужным размером + некоторые накладные расходы, создать на нем FS и смонтировать его с помощью интерфейса "loop" - затем просто создайте файлы stdout и sterr внутри этой файловой системы и вызовите своего ребенка процесс.

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

В общем, возможно, вам лучше просто использовать собственные настройки rlimit в Apparmor.

Другие вопросы по тегам