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

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

サンドボックス ポリシーの目的は、サンドボックス化されたプロセスが実行できるシステムコールと引数、およびアクセスできるファイルを制限することです。サンドボックス化するコードに必要なシステムコールを詳しく理解しておく必要があります。システムコールを観察する方法の 1 つは、Linux のコマンドライン ツール strace でコードを実行することです。

システムコールのリストを取得したら、PolicyBuilder を使用してポリシーを定義できます。PolicyBuilder には、多くの一般的なオペレーションを可能にする便利なヘルパー関数が多数用意されています。次のリストは、使用可能な関数の一部にすぎません。

  • プロセスの起動時に任意の syscall を許可します。
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • 開く/読み取り/書き込み* の syscall を許可リストに登録します。
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • 終了/アクセス/状態に関連する syscall を許可リストに登録します。
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • スリープ/時間関連の syscall を許可リストに登録します。
    • AllowTime();
    • AllowSleep();

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

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

syscall 関連の関数以外にも、PolicyBuilder は、ファイル/ディレクトリをサンドボックスにバインド マウントするための AddFile()AddDirectory() などのファイル システム関連の関数も多数提供しています。AddTmpfs() ヘルパーを使用して、サンドボックス内に一時ファイル ストレージを追加できます。

特に便利な関数は AddLibrariesForBinary() です。これは、バイナリに必要なライブラリとリンカーを追加します。

許可リストに登録するシステムコールを特定するには、まだ手動での作業が必要になります。バイナリに必要な syscall を含むポリシーを作成し、一般的なワークロードで実行します。違反がトリガーされた場合は、システムコールを許可リストに登録して、プロセスを繰り返します。許可リストに登録すると危険な違反が発生し、プログラムがエラーを適切に処理する場合は、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();
}