2. Membuat Kebijakan Sandbox

Setelah memiliki executor, Anda mungkin ingin menentukan Sandbox Policy untuk Sandboxee. Jika tidak, Sandboxee hanya dilindungi oleh Kebijakan Syscall Default.

Dengan Kebijakan Sandbox, tujuannya adalah membatasi syscall dan argumen yang dapat dilakukan Sandboxee, serta file yang dapat diaksesnya. Anda harus memahami secara mendetail syscall yang diperlukan oleh kode yang akan Anda sandbox. Salah satu cara untuk mengamati syscall adalah dengan menjalankan kode menggunakan alat command line strace Linux.

Setelah memiliki daftar panggilan sistem, Anda dapat menggunakan PolicyBuilder untuk menentukan kebijakan. PolicyBuilder dilengkapi dengan banyak fungsi praktis dan helper yang memungkinkan banyak operasi umum. Daftar berikut hanyalah cuplikan kecil dari fungsi yang tersedia:

  • Mengizinkan syscall apa pun untuk startup proses:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Mengizinkan semua syscall open/read/write*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Izinkan syscall terkait keluar/akses/status:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Izinkan semua panggilan sistem terkait waktu/tidur:
    • AllowTime();
    • AllowSleep();

Fungsi praktis ini memasukkan syscall yang relevan ke dalam daftar yang diizinkan. Hal ini memiliki keuntungan bahwa kebijakan yang sama dapat digunakan di berbagai arsitektur yang tidak memiliki syscall tertentu (misalnya, ARM64 tidak memiliki syscall OPEN), tetapi dengan risiko keamanan kecil karena mengaktifkan lebih banyak syscall daripada yang mungkin diperlukan. Misalnya, AllowOpen() memungkinkan Sandboxee memanggil syscall terkait pembukaan apa pun. Jika Anda hanya ingin memasukkan satu syscall tertentu ke dalam daftar yang diizinkan, Anda dapat menggunakan AllowSyscall(); untuk mengizinkan beberapa syscall sekaligus, Anda dapat menggunakan AllowSyscalls().

Sejauh ini, kebijakan hanya memeriksa ID syscall. Jika Anda perlu memperkuat kebijakan lebih lanjut dan ingin menentukan kebijakan yang hanya mengizinkan syscall dengan argumen tertentu, Anda harus menggunakan AddPolicyOnSyscall() atau AddPolicyOnSyscalls(). Fungsi ini tidak hanya menggunakan ID syscall sebagai argumen, tetapi juga filter seccomp-bpf mentah menggunakan makro helper bpf dari kernel Linux. Lihat dokumentasi kernel untuk mengetahui informasi selengkapnya tentang BPF. Jika Anda menulis kode BPF berulang yang menurut Anda harus memiliki wrapper kegunaan, jangan ragu untuk mengajukan permintaan fitur.

Selain fungsi terkait syscall, PolicyBuilder juga menyediakan sejumlah fungsi terkait sistem file seperti AddFile() atau AddDirectory() untuk memasang file/direktori ke sandbox. Helper AddTmpfs() dapat digunakan untuk menambahkan penyimpanan file sementara dalam sandbox.

Fungsi yang sangat berguna adalah AddLibrariesForBinary() yang menambahkan library dan linker yang diperlukan oleh biner.

Sayangnya, membuat daftar yang diizinkan untuk syscall masih memerlukan sedikit pekerjaan manual. Buat kebijakan dengan syscall yang Anda ketahui dibutuhkan biner Anda dan jalankan dengan workload umum. Jika pelanggaran dipicu, masukkan syscall ke daftar yang diizinkan dan ulangi prosesnya. Jika Anda menemukan pelanggaran yang menurut Anda berisiko untuk dimasukkan ke daftar yang diizinkan dan program menangani error dengan baik, Anda dapat mencoba membuatnya menampilkan error dengan 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();
}