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