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