2. サンドボックス ポリシーを作成する

エグゼキュータを作成したら、サンドボックス化対象のサンドボックス ポリシーを定義します。それ以外の場合、Sandboxee はデフォルトの Syscall ポリシーによってのみ保護されます。

サンドボックス ポリシーの目的は、サンドボックスが実行できるシステムコールと引数、およびアクセスできるファイルを制限することです。サンドボックス化する予定のコードに必要なシステムコールについて、詳細な理解が必要です。システムコールを観察する方法の一つは、Linux のコマンドライン ツール strace を使用してコードを実行することです。

システムコールのリストを取得したら、PolicyBuilder を使用してポリシーを定義できます。

PolicyBuilder には、多くの一般的なオペレーション(その多くはすでに DefaultPolicyBuilder() に含まれています)を可能にする、多くの便利なヘルパー関数が用意されています。次のリストは、使用可能な関数のほんの一部です。

  • プロセスの起動時に任意のシステムコールを許可リストに登録します。
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • open/read/write* システムコールを許可リストに登録します。
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • 終了/アクセス/状態に関連するシステムコールを許可リストに登録します。
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • スリープ/時間関連の syscall を許可リストに登録:
    • AllowTime();
    • AllowSleep();

これらの便利な関数は、関連する syscall を許可リストに登録します。これには、特定のシステムコールが利用できない異なるアーキテクチャ(ARM64 には OPEN システムコールがないなど)でも同じポリシーを使用できるという利点がありますが、必要以上のシステムコールを有効にするという、セキュリティ上の小さなリスクがあります。たとえば、AllowOpen() を使用すると、Sandboxee はオープン関連の任意の syscall を呼び出すことができます。特定のシステムコールを 1 つだけ許可リストに登録する場合は、AllowSyscall(); を使用します。複数のシステムコールを一度に許可するには、AllowSyscalls() を使用します。

今のところ、このポリシーはシステムコール識別子のみをチェックします。ポリシーをさらに強化する必要があり、特定の引数を持つシステムコールのみを許可するポリシーを定義する場合は、AddPolicyOnSyscall() または AddPolicyOnSyscalls() を使用する必要があります。これらの関数は、syscall ID を引数として取るだけでなく、Linux カーネルの bpf ヘルパー マクロを使用して未加工の seccomp-bpf フィルタも引数として取ります。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();
}