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