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

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

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

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

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

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

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

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

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

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