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();
}