เริ่มต้นใช้งาน Sandbox2

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

1. เลือกวิธีดำเนินการของแซนด์บ็อกซ์

แซนด์บ็อกซ์จะเริ่มต้นด้วยผู้ดำเนินการ (ดูผู้ดำเนินการแซนด์บ็อกซ์) ซึ่งมีหน้าที่ในการเรียกใช้แซนด์บ็อกซ์ ไฟล์ส่วนหัว executor.h มี API ที่จำเป็นสำหรับวัตถุประสงค์นี้ API มีความยืดหยุ่นสูงและให้คุณเลือกได้ที่เหมาะกับกรณีการใช้งานของคุณมากที่สุด ส่วนต่อไปนี้จะอธิบายถึง 3 วิธีการที่คุณสามารถเลือกได้

วิธีที่ 1: สแตนด์อโลน – เรียกใช้ไบนารีที่เปิดใช้แซนด์บ็อกซ์อยู่แล้ว

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

ในข้อมูลโค้ดต่อไปนี้ เรากำหนดเส้นทางของไบนารีที่จะทำการแซนด์บ็อกซ์และอาร์กิวเมนต์ที่เราต้องส่งผ่านไปยัง execve syscall ดังที่คุณเห็นในไฟล์ส่วนหัว executor.h เราจะไม่ระบุค่าสำหรับ envp ดังนั้นจึงคัดลอกสภาพแวดล้อมจากกระบวนการหลัก โปรดทราบว่าอาร์กิวเมนต์แรกจะเป็นชื่อของโปรแกรมที่จะดำเนินการเสมอ และข้อมูลโค้ดของเราไม่ได้กำหนดอาร์กิวเมนต์อื่น

ตัวอย่างของเมธอดไฟล์ดำเนินการนี้ ได้แก่ static และ tool

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};  // args[0] will become the sandboxed
                                         // process' argv[0], typically the
                                         // path to the binary.
auto executor = absl::make_unique<sandbox2::Executor>(path, args);

วิธีที่ 2: Sandbox2 Forkserver – แจ้งให้ผู้ดำเนินการทราบว่าต้องแซนด์บ็อกซ์เมื่อใด

วิธีนี้จะมอบความยืดหยุ่นในการไม่ได้อยู่ในแซนด์บ็อกซ์ในระหว่างการเริ่มต้น แล้วเลือกเวลาที่จะเข้าสู่การทำแซนด์บ็อกซ์ด้วยการเรียกใช้ ::sandbox2::Client::SandboxMeHere() กำหนดให้คุณต้องระบุในโค้ดเมื่อต้องการเริ่มต้นแซนด์บ็อกซ์ และต้องเป็นชุดข้อความเดียว (อ่านเหตุผลในคำถามที่พบบ่อย)

ในข้อมูลโค้ดต่อไปนี้ เราใช้โค้ดเดียวกับที่ระบุไว้ในวิธีที่ 1 ด้านบน อย่างไรก็ตาม เราจะเรียกใช้ set_enable_sandbox_before_exec(false) เพื่ออนุญาตให้โปรแกรมทำงานแบบไม่ใช้แซนด์บ็อกซ์ในระหว่างการเริ่มต้นทำงาน

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
executor->set_enable_sandbox_before_exec(false);

เนื่องจากตอนนี้ผู้ดำเนินการมีแซนด์บ็อกซ์ที่ปิดใช้อยู่จนกว่าแซนด์บ็อกซ์จะได้รับการแจ้งเตือน เราจึงต้องสร้างอินสแตนซ์ ::sandbox2::Client, ตั้งค่าการสื่อสารระหว่างผู้ดำเนินการและแซนด์บ็อกซ์ จากนั้นแจ้งให้ผู้ดำเนินการทราบว่าการเริ่มต้นของเราเสร็จสิ้นแล้ว และเราต้องการเริ่มแซนด์บ็อกซ์ทันทีโดยเรียกใช้ sandbox2_client.SandboxMeHere()

// main() of sandboxee
int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, false);

  // Set-up the sandbox2::Client object, using a file descriptor (1023).
  sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
  sandbox2::Client sandbox2_client(&comms);
  // Enable sandboxing from here.
  sandbox2_client.SandboxMeHere();
  …

ตัวอย่างของเมธอดไฟล์ดำเนินการนี้คือ crc4 โดยที่ crc4bin.cc คือแซนด์บ็อกซ์และแจ้งผู้ดำเนินการ (crc4sandbox.cc) เมื่อควรเข้าสู่แซนด์บ็อกซ์

วิธีที่ 3: Custom Forkserver – เตรียมไบนารี รอคำขอ Fork และใช้แซนด์บ็อกซ์ด้วยตนเอง

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

ตัวดำเนินการจะส่งคำขอ Fork ไปยังไบนารีของคุณ ซึ่งจะ fork() (ผ่าน ::sandbox2::ForkingClient::WaitAndFork()) กระบวนการที่สร้างขึ้นใหม่จะพร้อมสำหรับการแซนด์บ็อกซ์ด้วย ::sandbox2::Client::SandboxMeHere()

#include "sandboxed_api/sandbox2/executor.h"

// Start the custom ForkServer
std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto fork_executor = absl::make_unique<sandbox2::Executor>(path, args);
fork_executor->StartForkServer();

// Initialize Executor with Comms channel to the ForkServer
auto executor = absl::make_unique<sandbox2::Executor>(
    fork_executor->ipc()->GetComms());

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

ดูตัวอย่างเมธอดของผู้ดำเนินการนี้ได้ที่ custom_fork

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

3. ปรับขีดจำกัด

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

แซนด์บ็อกซ์ทำงานภายใต้ขีดจำกัดการดำเนินการที่เข้มงวดโดยค่าเริ่มต้นเพื่อจัดการกับภัยคุกคามนี้ หากขีดจำกัดเริ่มต้นเหล่านี้ทำให้เกิดปัญหาในการดำเนินการที่ถูกต้องของโปรแกรม คุณปรับขีดจำกัดได้โดยใช้คลาส sandbox2::Limits โดยการเรียกใช้ limits() ในออบเจ็กต์ผู้ดำเนินการ

ข้อมูลโค้ดด้านล่างแสดงตัวอย่างการปรับขีดจำกัด ตัวเลือกที่ใช้ได้ทั้งหมดจะบันทึกอยู่ในไฟล์ส่วนหัว limits.h

// Restrict the address space size of the sandboxee to 4 GiB.
executor->limits()->set_rlimit_as(4ULL << 30);
// Kill sandboxee with SIGXFSZ if it writes more than 1 GiB to the filesystem.
executor->limits()->set_rlimit_fsize(1ULL << 30);
// Number of file descriptors which can be used by the sandboxee.
executor->limits()->set_rlimit_nofile(1ULL << 10);
// The sandboxee is not allowed to create core files.
executor->limits()->set_rlimit_core(0);
// Maximum 300s of real CPU time.
executor->limits()->set_rlimit_cpu(300);
// Maximum 120s of wall time.
executor->limits()->set_walltime_limit(absl::Seconds(120));

ดูตัวอย่างการใช้คลาส sandbox2::Limits ได้ที่เครื่องมือตัวอย่าง

4. เรียกใช้แซนด์บ็อกซ์

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

เรียกใช้พร้อมกัน

แซนด์บ็อกซ์จะทำงานแบบซิงโครนัสได้ ซึ่งจะบล็อกจนกว่าจะมีผลลัพธ์ ข้อมูลโค้ดด้านล่างแสดงการสร้างอินสแตนซ์ของออบเจ็กต์ Sandbox2 และการดำเนินการพร้อมกันของออบเจ็กต์ ดูตัวอย่างโดยละเอียดเพิ่มเติมได้ที่แบบคงที่

#include "sandboxed_api/sandbox2/sandbox2.h"

sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
sandbox2::Result result = s2.Run();  // Synchronous
LOG(INFO) << "Result of sandbox execution: " << result.ToString();

เรียกใช้แบบไม่พร้อมกัน

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

#include "sandboxed_api/sandbox2/sandbox2.h"

sandbox2::Sandbox2 s2(std::move(executor), std::move(policy));
if (s2.RunAsync()) {
  // Communicate with sandboxee, use s2.Kill() to kill it if needed
  // ...
}
Sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

5. การสื่อสารกับแซนด์บ็อกซ์

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

แต่ส่วนใหญ่แล้วคุณย่อมต้องการตรรกะการสื่อสารที่ซับซ้อนมากขึ้นระหว่างผู้ดำเนินการและแซนด์บ็อกซ์ comms API (ดูไฟล์ส่วนหัว comms.h) สามารถใช้เพื่อส่งจำนวนเต็ม สตริง บัฟเฟอร์ไบต์ โปรโตคอล หรือตัวอธิบายไฟล์

การแชร์ตัวอธิบายไฟล์

การใช้ Inter-Process Communication API (ดูที่ ipc.h) คุณจะใช้ MapFd() หรือ ReceiveFd() ได้ดังนี้

  • ใช้ MapFd() เพื่อจับคู่ข้อบ่งชี้ไฟล์จากผู้ดำเนินการไปยังแซนด์บ็อกซ์ ซึ่งอาจใช้ในการแชร์ไฟล์ที่เปิดจากผู้ดำเนินการเพื่อใช้ในแซนด์บ็อกซ์ ตัวอย่างการใช้งานสามารถดูได้ในแบบคงที่

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • ใช้ ReceiveFd() เพื่อสร้างปลายทาง Socketpair สามารถใช้เพื่ออ่านเอาต์พุตมาตรฐานหรือข้อผิดพลาดมาตรฐานของแซนด์บ็อกซ์ ดูตัวอย่างการใช้งานได้ในเครื่องมือ

    // The executor receives a file descriptor of the sandboxee stdout
    int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
    

การใช้ Comms API

Sandbox2 มี Comms API ที่ใช้งานสะดวก นี่เป็นวิธีง่ายๆ ในการแชร์จำนวนเต็ม สตริง หรือบัฟเฟอร์ไบต์ระหว่างตัวดำเนินการและแซนด์บ็อกซ์ ด้านล่างคือข้อมูลโค้ดบางส่วนที่คุณจะพบในตัวอย่าง crc4

หากต้องการเริ่มต้นใช้งาน comms API ก่อนอื่นคุณต้องรับออบเจ็กต์ comms จากออบเจ็กต์ Sandbox2

sandbox2::Comms* comms = s2.comms();

เมื่อออบเจ็กต์การสื่อสารพร้อมใช้งาน ระบบจะส่งข้อมูลไปยังแซนด์บ็อกซ์โดยใช้ฟังก์ชันในตระกูล Send* ได้ ดูตัวอย่างการใช้ comms API ได้ในตัวอย่าง crc4 ข้อมูลโค้ดด้านล่างแสดงตัวอย่างบางส่วน ผู้ดำเนินการส่ง unsigned char buf[size] พร้อม SendBytes(buf, size):

if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
  /* handle error */
}

หากต้องการรับข้อมูลจากแซนด์บ็อกซ์ ให้ใช้ฟังก์ชัน Recv* อย่างใดอย่างหนึ่ง ข้อมูลโค้ดด้านล่างเป็นข้อความที่ตัดตอนมาจากตัวอย่าง crc4 ผู้ดำเนินการจะได้รับการตรวจสอบข้อผิดพลาดในจำนวนเต็มที่ไม่มีเครื่องหมาย 32 บิต: uint32_t crc4;

if (!(comms->RecvUint32(&crc4))) {
  /* handle error */
}

การแชร์ข้อมูลกับบัฟเฟอร์

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

ผู้ดำเนินการสร้างบัฟเฟอร์ ไม่ว่าจะตามขนาดและข้อมูลที่จะส่ง หรือโดยตรงจากคำอธิบายไฟล์ และส่งไปยังแซนด์บ็อกซ์โดยใช้ comms->SendFD() ในตัวดำเนินการและ comms->RecvFD() ในแซนด์บ็อกซ์

คุณจะเห็นด้านของผู้ดำเนินการในข้อมูลโค้ดด้านล่าง แซนด์บ็อกซ์ทำงานแบบไม่พร้อมกันและแชร์ข้อมูลผ่านบัฟเฟอร์กับแซนด์บ็อกซ์:

// start the sandbox asynchronously
s2.RunAsync();

// instantiate the comms object
sandbox2::Comms* comms = s2.comms();

// random buffer data we want to send
constexpr unsigned char buffer_data[] = /* random data */;
constexpr unsigned int buffer_dataLen = 34;

// create sandbox2 buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::CreateWithSize(1ULL << 20 /* 1Mib */);
std::unique_ptr<sandbox2::Buffer> buffer_ptr = std::move(buffer).value();

// point to the sandbox2 buffer and fill with data
uint8_t* buf = buffer_ptr‑>data();
memcpy(buf, buffer_data, buffer_data_len);

// send the data to the sandboxee
comms‑>SendFd(buffer_ptr‑>fd());

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

// establish the communication with the executor
int fd;
comms.RecvFD(&fd);

// create the buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::createFromFd(fd);

// get the data
auto buffer_ptr = std::move(buffer).value();
uint8_t* buf = buffer_ptr‑>data();

/* work with the buf object */

6. กำลังออกจากแซนด์บ็อกซ์

คุณต้องปรับวิธีการสิ้นสุดแซนด์บ็อกซ์และแซนด์บ็อกซ์ด้วย ทั้งนี้ขึ้นอยู่กับวิธีที่คุณเรียกใช้แซนด์บ็อกซ์ (ดูขั้นตอนนี้)

การออกจากแซนด์บ็อกซ์ที่ทำงานพร้อมกัน

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

Sandbox2::Result result = s2.Run();
LOG(INFO) << "Final execution status: " << result.ToString();

การออกจากแซนด์บ็อกซ์ที่ทำงานแบบไม่พร้อมกัน

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

sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

หรือคุณอาจปิดแซนด์บ็อกซ์ได้ทุกเมื่อ แต่เราขอแนะนำให้เรียกใช้ AwaitResult() เนื่องจากแซนด์บ็อกซ์อาจสิ้นสุดการทำงานเนื่องจากสาเหตุอื่นในระหว่างนี้

s2.Kill();
sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();

7. ทดสอบ

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

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

การทดสอบเหล่านี้อาจเป็นการทดสอบง่ายๆ ด้วยการทดสอบ Shell หรือการทดสอบ C++ โดยใช้กระบวนการย่อย ดูตัวอย่างเพื่อหาแรงบันดาลใจ

บทสรุป

ขอขอบคุณที่อ่านข้อมูลจนถึงตอนนี้ เราหวังว่าคุณจะชอบคู่มือของเรา และตอนนี้รู้สึกมั่นใจในการสร้างแซนด์บ็อกซ์ของคุณเองเพื่อช่วยให้ผู้ใช้ของคุณปลอดภัย

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