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

エグゼキュータを設定したら、Sandboxee 用にサンドボックス ポリシーを定義することをおすすめします。それ以外の場合、Sandboxee はデフォルトの Syscall ポリシーでのみ保護されます。

サンドボックス ポリシーの目標は、Sandboxee が作成できるシステムコールと引数、およびアクセスできるファイルを制限することです。サンドボックス化するコードに必要なシステムコールを詳細に理解する必要があります。システムコールを監視する 1 つの方法は、Linux のコマンドライン ツールの strace を使用してコードを実行することです。

システムコールのリストを取得したら、PolicyBuilder を使用してポリシーを定義できます。PolicyBuilder には、多くの一般的な操作を実行できる便利なヘルパー関数が多数用意されています。使用可能な関数のごく一部のみを次のリストに示します。

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

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

今のところ、ポリシーはシステムコール ID のみをチェックします。ポリシーをさらに強化する必要があり、特定の引数を持つシステムコールのみを許可するポリシーを定義する場合は、AddPolicyOnSyscall() または AddPolicyOnSyscalls() を使用する必要があります。これらの関数は、システムコール ID を引数として受け取るだけでなく、Linux カーネルの bpf ヘルパーマクロを使用する生の seccomp-bpf フィルタも受け取ります。BPF の詳細については、カーネル ドキュメントをご覧ください。ユーザビリティ ラッパーが必要なと思われる BPF コードを繰り返し記述していると思われる場合は、お気軽に機能リクエストをお送りください。

PolicyBuilder には、syscall 関連の関数とは別に、ファイル/ディレクトリをサンドボックスにバインド マウントするための 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();
}