mprotect вся программа, для запуска опасного кода

#c #linux #posix #sandbox #mprotect

#c #linux #posix #песочница #mprotect

Вопрос:

У меня есть небольшая программа, которая отображает потенциально опасный исполняемый код (с помощью PROT_EXEC), вызывает prctl(PR_SET_SECCOMP, 1) и затем выполняет этот mmap’d код. Это все хорошо и позволяет мне «сохранить» состояние оценки путем синхронизации области mmap’d с диском и перезагрузить ее позже (скорее всего, на другой машине для балансировки нагрузки). Однако этот метод работает не всегда — потому что этот код мог внести изменения в программу, которых нет в области mmap’d, и эта информация будет потеряна.

Итак, что я хотел бы сделать, это сделать абсолютно все (кроме этой области mmap’d) доступным только для чтения перед вызовом кода. Таким образом, у меня есть гарантия, что исполняемый код не сможет изменить состояние чего-либо, кроме области mmap’d, которую я могу сериализовать / десериализовать по желанию.

Кстати, это Linux на x86_64

Спасибо

Комментарии:

1. Если вам удастся сделать память доступной только для чтения, не произойдет ли сбой вашей программы при попытке записи?

2. Если он пытается выполнить запись за пределы своей области mmap’d, то сбой — это нормально

3. хммм … если у вас есть разрешение на mprotect вашего кода, и вы запускаете исполняемый код, не отказываясь от этих разрешений, исполняемый код может просто вызвать mprotect, чтобы восстановить эти права.

4. @gby Нет, потому что вызов prctl отключает возможность выполнения системных вызовов

Ответ №1:

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

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

Ваш следующий вариант — fork() . Вы могли бы exec поместить дочерний файл в специальную исполняемую оболочку, которая допускает минимальный ущерб и самоанализ вредоносным исполняемым кодом (обеспечивает простую загрузку / дамп), или вы могли бы сделать то же самое, заставив дочерний файл модифицировать себя с тем же эффектом. Это все еще не на 100% безопасно.

Предложение0

  • Создайте автономный двоичный файл, который связан с минимальными библиотеками ( -nodefaultlibs ).
  • После fork , ptrace(PTRACE_TRACEME) в дочернем элементе (чтобы вы могли надежно считывать содержимое памяти и выполнять другие вмешательства) и закройте все дескрипторы, кроме дескриптора канала (просто stdin для простоты). exec() в вышеупомянутый двоичный файл-оболочку.

В двоичном файле-оболочке:

  • mmap частный регион в известном месте с разрешениями на запись и выполнение. В качестве альтернативы вы можете статически выделить эту область, если размер фиксирован.
  • Прочитайте содержимое канала в область.
  • Закройте канал. Теперь у процесса нет открытых дескрипторов.
  • prctl(PR_SET_SECCOMP, 1) . Теперь единственными допустимыми системными вызовами являются _exit и sigreturn . Поскольку процесс не может raise , sigreturn не должно иметь никакого полезного эффекта.
  • Удалите разрешения на запись из основного стека (должно быть единственным стеком). Поскольку у вас нет намерения возвращаться и сразу после этого произойдет переход, вам не нужно будет снова прикасаться к стеку.
  • Перейдите в исходное местоположение внутри региона. Сделайте это с помощью assembly или создайте указатель на функцию и вызовите ее (если вы можете заставить ее работать, не обращаясь к стеку). Теперь вы должны выполнить область памяти, которая является единственной доступной для записи областью. Основной стек был защищен, а куча не должна использоваться из-за отсутствия поддержки библиотеки.

В родительском:

  • Используя ptrace или wait , перехватите ошибочное или успешное завершение.
  • Считайте отображенную область в известном местоположении через /proc/<pid>/mem или эквивалент file.

Комментарии:

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

2. Кроме того, кстати, причина, по которой я использую область mmap’d и загружаю код туда, заключается в том, что Linux не позволяет мне устанавливать разрешения на исполняемый файл для обычных страниц памяти, что является большой проблемой для загрузки отражающего / самоизменяющегося кода

3. @Heptic: Нет, это невозможно. Смысл в том, чтобы создать минимальный процесс без дескрипторов, и только сбой или _exit в качестве жизнеспособных способов завершения. Как только это заканчивается, родительский файл сбрасывает исполняемую область памяти.