2. Как создать правило для изолированной среды
После того как вы создадите исполнитель, вам, скорее всего, потребуется определить правила песочницы для Sandboxee. В противном случае Sandboxee будет защищен только правилом Default Syscall Policy.
Цель политики песочницы – ограничить системные вызовы и аргументы, которые может использовать изолированный процесс, а также файлы, к которым он может получить доступ. Вам нужно будет детально разобраться в системных вызовах, необходимых для кода, который вы планируете поместить в изолированную среду. Один из способов отслеживания системных вызовов – запустить код с помощью инструмента командной строки strace в Linux.
Получив список системных вызовов, вы можете использовать PolicyBuilder, чтобы определить правила.
В PolicyBuilder есть множество удобных вспомогательных функций, которые позволяют выполнять распространенные операции (многие из них уже включены в DefaultPolicyBuilder()). Ниже приведен лишь небольшой список доступных функций.
- Добавьте в белый список все системные вызовы для запуска процесса:
AllowStaticStartup();AllowDynamicStartup();
- Добавьте в белый список все системные вызовы open/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().
В следующем примере показано, как создать sandbox2::PolicyBuilder, чтобы ограничить аргументы write и mprotect и заблокировать openat (которое в противном случае было бы разрешено с помощью DefaultPolicyBuilder()):
#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();
}