2. Membuat Kebijakan Sandbox

Setelah memiliki eksekutor, Anda mungkin perlu menentukan Kebijakan Sandbox untuk Sandboxee. Jika tidak, Sandboxee hanya dilindungi oleh Kebijakan Syscall Default.

Dengan Kebijakan Sandbox, tujuannya adalah membatasi syscall dan argumen yang dapat dibuat oleh Sandboxee, serta file yang dapat diaksesnya. Anda harus memiliki pemahaman terperinci tentang {i>syscall<i} yang diperlukan oleh kode yang Anda rencanakan untuk {i>sandbox<i}. Salah satu cara untuk mengamati syscall adalah menjalankan kode dengan strace alat command line Linux.

Setelah memiliki daftar syscall, Anda dapat menggunakan PolicyBuilder untuk menentukan kebijakan. PolicyBuilder dilengkapi dengan banyak fungsi bantuan dan kemudahan yang memungkinkan banyak operasi umum. Daftar berikut ini hanya cuplikan kecil dari fungsi yang tersedia:

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

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

Sejauh ini kebijakan tersebut hanya memeriksa ID syscall. Jika Anda perlu memperkuat kebijakan lebih lanjut dan ingin menentukan kebijakan yang mengizinkan syscall hanya 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 bantuan bpf dari kernel Linux. Lihat dokumentasi kernel untuk informasi selengkapnya tentang BPF. Jika Anda mendapati diri Anda menulis kode BPF berulang yang menurut Anda seharusnya 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 dalam 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.

Namun, membuat syscall ke daftar yang diizinkan masih memerlukan banyak pekerjaan manual. Buat kebijakan dengan syscall yang Anda ketahui kebutuhan biner Anda dan jalankan dengan beban kerja umum. Jika pelanggaran dipicu, izinkan syscall dan ulangi prosesnya. Jika Anda menemukan pelanggaran yang menurut Anda mungkin berisiko untuk 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();
}