2. สร้างนโยบายแซนด์บ็อกซ์

เมื่อมีผู้ปฏิบัติงานแล้ว คุณอาจต้องการกำหนดSandbox Policyสำหรับแซนด์บ็อกซ์ มิเช่นนั้น แซนด์บ็อกซ์จะได้รับการปกป้องโดยนโยบาย Syscall เริ่มต้น เท่านั้น

วัตถุประสงค์ของนโยบายแซนด์บ็อกซ์คือการจำกัด Syscall และอาร์กิวเมนต์ที่แซนด์บ็อกซ์สามารถทำได้ รวมถึงไฟล์ที่เข้าถึงได้ คุณจะต้องมีความเข้าใจโดยละเอียดเกี่ยวกับ Syscall ที่โค้ดที่คุณวางแผนจะแซนด์บ็อกซ์ต้องใช้ วิธีหนึ่งในการสังเกต Syscall คือการเรียกใช้โค้ดด้วยเครื่องมือบรรทัดคำสั่ง strace ของ Linux

เมื่อมีรายการ Syscall แล้ว คุณสามารถใช้ PolicyBuilder เพื่อกำหนดนโยบายได้

PolicyBuilder มาพร้อมกับฟังก์ชันความสะดวกและฟังก์ชันตัวช่วยมากมายที่อนุญาตให้ดำเนินการทั่วไปหลายอย่าง (ซึ่งหลายอย่างรวมอยู่ใน DefaultPolicyBuilder() แล้ว) รายการต่อไปนี้เป็นเพียงส่วนเล็กๆ ของ ฟังก์ชันที่มีอยู่

  • อนุญาต Syscall ใดก็ได้สำหรับการเริ่มต้นกระบวนการ
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • อนุญาต Syscall /read/write* ใดก็ได้
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • เพิ่ม Syscall ที่เกี่ยวข้องกับการออก/การเข้าถึง/สถานะใดก็ได้ในรายการที่อนุญาต
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • เพิ่ม Syscall ที่เกี่ยวข้องกับการพัก/เวลาใดก็ได้ในรายการที่อนุญาต
    • AllowTime();
    • AllowSleep();

ฟังก์ชันความสะดวกเหล่านี้จะอนุญาต Syscall ที่เกี่ยวข้อง ซึ่งมีข้อดีคือสามารถใช้นโยบายเดียวกันในสถาปัตยกรรมต่างๆ ที่ไม่มี Syscall บางอย่าง (เช่น ARM64 ไม่มี Syscall OPEN) แต่มีความเสี่ยงด้านความปลอดภัยเล็กน้อยในการเปิดใช้ Syscall มากกว่าที่จำเป็น ตัวอย่างเช่น AllowOpen() ช่วยให้แซนด์บ็อกซ์เรียกใช้ Syscall ที่เกี่ยวข้องกับการเปิดได้ หากต้องการอนุญาต Syscall เฉพาะเพียงรายการเดียว คุณสามารถใช้ AllowSyscall(); หากต้องการอนุญาต Syscall หลายรายการพร้อมกัน คุณสามารถใช้ AllowSyscalls()

ปัจจุบันนโยบายจะตรวจสอบเฉพาะตัวระบุ Syscall หากต้องการเสริมความแข็งแกร่งของนโยบายเพิ่มเติมและต้องการกำหนดนโยบายที่อนุญาต Syscall ที่มีอาร์กิวเมนต์เฉพาะเท่านั้น คุณต้องใช้ AddPolicyOnSyscall() หรือ AddPolicyOnSyscalls() ฟังก์ชันเหล่านี้ไม่เพียงแต่ใช้รหัส Syscall เป็นอาร์กิวเมนต์เท่านั้น แต่ยังใช้ฟิลเตอร์ seccomp-bpf แบบดิบโดยใช้มาโครตัวช่วย bpf จากเคอร์เนล Linux ด้วย ดูข้อมูลเพิ่มเติมเกี่ยวกับ BPF ได้ที่เอกสารประกอบของเคอร์เนล หากคุณพบว่าตัวเองกำลังเขียนโค้ด BPF ซ้ำๆ ที่คุณคิดว่าควรมี Wrapper ที่ใช้งานง่าย โปรดส่งคำขอฟีเจอร์

นอกเหนือจากฟังก์ชันที่เกี่ยวข้องกับ Syscall แล้ว PolicyBuilder ยังมีฟังก์ชันที่เกี่ยวข้องกับระบบไฟล์จำนวนมาก เช่น AddFile() หรือ AddDirectory() เพื่อติดตั้งไฟล์/ไดเรกทอรีในแซนด์บ็อกซ์ คุณสามารถใช้ตัวช่วย AddTmpfs() เพื่อเพิ่มพื้นที่เก็บข้อมูลไฟล์ชั่วคราวภายในแซนด์บ็อกซ์

AddLibrariesForBinary() เป็นฟังก์ชันที่มีประโยชน์อย่างยิ่ง ซึ่งจะเพิ่มไลบรารีและตัวลิงก์ที่ไบนารีต้องใช้

น่าเสียดายที่การคิด Syscall ที่จะเพิ่มในรายการที่อนุญาตยังคงต้องทำด้วยตนเอง สร้างนโยบายด้วย Syscall ที่คุณทราบว่าไบนารีต้องใช้ แล้วเรียกใช้ด้วยภาระงานทั่วไป หากมีการละเมิดเกิดขึ้น ให้เพิ่ม Syscall ลงในรายการที่อนุญาตแล้วทำซ้ำกระบวนการ หากพบการละเมิดที่คุณคิดว่าอาจมีความเสี่ยงที่จะเพิ่มลงในรายการที่อนุญาต และโปรแกรมจัดการข้อผิดพลาดได้อย่างราบรื่น คุณสามารถลองทำให้โปรแกรมแสดงข้อผิดพลาดแทนได้ด้วย 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();
}