2. Создайте политику «песочницы»
После создания исполнителя вам, вероятно, потребуется определить политику песочницы для песочницы Sandboxee. В противном случае песочница Sandboxee будет защищена только политикой системных вызовов по умолчанию .
Цель политики «песочницы» — ограничить системные вызовы и аргументы, которые может выполнять «песочница», а также файлы, к которым она может получить доступ. Вам необходимо детально понимать системные вызовы, необходимые для кода, который вы планируете поместить в «песочницу». Один из способов наблюдения за системными вызовами — запустить код с помощью утилиты командной строки Linux strace.
Получив список системных вызовов, вы можете использовать PolicyBuilder для определения политики. PolicyBuilder содержит множество удобных и вспомогательных функций, позволяющих выполнять множество распространённых операций. Следующий список — лишь небольшая часть доступных функций:
- Разрешить любые системные вызовы для запуска процесса:
-
AllowStaticStartup();
-
AllowDynamicStartup();
-
- Добавить в список разрешенных открытых системных вызовов /read /write*:
-
AllowOpen();
-
AllowRead();
-
AllowWrite();
-
- Добавьте в список разрешенных системных вызовов, связанных с выходом/доступом/состоянием:
-
AllowExit();
-
AllowStat();
-
AllowAccess();
-
- Добавьте в список разрешенных системных вызовов, связанных со сном/временем:
-
AllowTime();
-
AllowSleep();
-
Эти удобные функции разрешают все соответствующие системные вызовы. Преимущество этого заключается в том, что одну и ту же политику можно использовать на разных архитектурах, где некоторые системные вызовы недоступны (например, в ARM64 нет системного вызова OPEN), но с небольшим риском для безопасности, связанным с включением большего количества системных вызовов, чем необходимо. Например, AllowOpen() позволяет Sandboxee вызывать любые связанные открытые системные вызовы. Если нужно разрешить только один конкретный системный вызов, можно использовать AllowSyscall();
чтобы разрешить несколько системных вызовов одновременно, можно использовать AllowSyscalls()
.
Пока что политика проверяет только идентификатор системного вызова. Если вам необходимо дополнительно усилить политику и определить политику, разрешающую системные вызовы только с определёнными аргументами, используйте AddPolicyOnSyscall()
или AddPolicyOnSyscalls()
. Эти функции принимают в качестве аргумента не только идентификатор системного вызова, но и необработанный фильтр seccomp-bpf, используя вспомогательные макросы bpf из ядра Linux. Подробнее о BPF см. в документации ядра . Если вы пишете повторяющийся код BPF, который, по вашему мнению, должен быть обёрнут для удобства использования, смело отправляйте запрос на добавление функции.
Помимо функций, связанных с системными вызовами, PolicyBuilder также предоставляет ряд функций, связанных с файловой системой, таких как AddFile()
и AddDirectory()
для привязки файла/каталога к песочнице. Вспомогательная функция AddTmpfs()
может использоваться для добавления временного хранилища файлов в песочницу.
Особенно полезная функция — AddLibrariesForBinary()
, которая добавляет библиотеки и компоновщик, необходимые двоичному файлу.
К сожалению, добавление системных вызовов в разрешённый список всё ещё требует ручной работы. Создайте политику с системными вызовами, которые, как вы знаете, нужны вашему исполняемому файлу, и запустите её с обычной рабочей нагрузкой. Если обнаружено нарушение, добавьте системный вызов в разрешённый список и повторите процесс. Если вы столкнулись с нарушением, которое, по вашему мнению, может быть рискованным для включения в разрешённый список, и программа корректно обрабатывает ошибки, вы можете попробовать заставить её возвращать ошибку с помощью BlockSyscallWithErrno()
.
#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"
std::unique_ptr<sandbox2::Policy> CreatePolicy() {
return sandbox2::PolicyBuilder()
.AllowSyscall(__NR_read) // See also AllowRead()
.AllowTime() // Allow time, gettimeofday and clock_gettime
.AddPolicyOnSyscall(__NR_write, {
ARG(0), // fd is the first argument of write (argument #0)
JEQ(1, ALLOW), // allow write only on fd 1
KILL, // kill if not fd 1
})
.AddPolicyOnSyscall(__NR_mprotect, {
ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
// macro here
JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
// kill the process
ARG(1), // len is a 64-bit argument
JNE(0x1000, KILL), // Allow single page syscalls only, otherwise kill
// the process
ALLOW, // Allow for the syscall to proceed, if prot and
// size match
})
// Allow the openat() syscall but always return "not found".
.BlockSyscallWithErrno(__NR_openat, ENOENT)
.BuildOrDie();
}