2. Создайте политику песочницы

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

Целью политики песочницы является ограничение системных вызовов и аргументов, которые может выполнять песочница, а также файлов, к которым она может получить доступ. Вам необходимо иметь детальное представление о системных вызовах, необходимых для кода, который вы планируете поместить в «песочницу». Один из способов наблюдения за системными вызовами — запустить код с помощью инструмента командной строки Linux strace.

Получив список системных вызовов, вы можете использовать PolicyBuilder для определения политики. PolicyBuilder поставляется со множеством удобных и вспомогательных функций, которые позволяют выполнять множество распространенных операций. Следующий список представляет собой лишь небольшую часть доступных функций:

  • Разрешить любой системный вызов для запуска процесса:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • В список разрешенных системных вызовов открытия /чтения /записи*:
    • 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();
}
,

2. Создайте политику песочницы

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

Целью политики песочницы является ограничение системных вызовов и аргументов, которые может выполнять песочница, а также файлов, к которым она может получить доступ. Вам необходимо иметь детальное представление о системных вызовах, необходимых для кода, который вы планируете поместить в «песочницу». Один из способов наблюдения за системными вызовами — запустить код с помощью инструмента командной строки Linux strace.

Получив список системных вызовов, вы можете использовать PolicyBuilder для определения политики. PolicyBuilder поставляется со множеством удобных и вспомогательных функций, которые позволяют выполнять множество распространенных операций. Следующий список представляет собой лишь небольшую часть доступных функций:

  • Разрешить любой системный вызов для запуска процесса:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • В список разрешенных системных вызовов открытия /чтения /записи*:
    • 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();
}