ในหน้านี้ คุณจะได้เรียนรู้วิธีสร้างสภาพแวดล้อมแซนด์บ็อกซ์ของคุณเองด้วย Sandbox2 รวมถึงวิธีกำหนดนโยบายแซนด์บ็อกซ์และการปรับแต่งขั้นสูง แต่พบได้ทั่วไป ใช้ข้อมูลในหน้านี้เป็นแนวทางควบคู่ไปกับ ตัวอย่างและเอกสารประกอบโค้ดใน ไฟล์ส่วนหัว
1. เลือกวิธีดำเนินการแซนด์บ็อกซ์
การทำแซนด์บ็อกซ์เริ่มต้นด้วยตัวดำเนินการ (ดู ตัวดำเนินการ แซนด์บ็อกซ์) ซึ่ง มีหน้าที่รับผิดชอบในการเรียกใช้ Sandboxee ไฟล์ส่วนหัว executor.h มี API ที่จำเป็นสำหรับวัตถุประสงค์นี้ API มีความยืดหยุ่นสูงและช่วยให้คุณเลือกสิ่งที่เหมาะกับ Use Case ของคุณมากที่สุด ส่วนต่อไปนี้จะอธิบาย 3 วิธีที่แตกต่างกันซึ่งคุณเลือกได้
วิธีที่ 1: แบบสแตนด์อโลน - เรียกใช้ไบนารีที่เปิดใช้การทำแซนด์บ็อกซ์แล้ว
วิธีนี้เป็นวิธีที่ง่ายที่สุดในการใช้การทำแซนด์บ็อกซ์และเป็นวิธีที่แนะนำเมื่อคุณต้องการทำแซนด์บ็อกซ์ไบนารีทั้งหมดที่คุณไม่มีซอร์สโค้ด นอกจากนี้ยังเป็นวิธีที่ปลอดภัยที่สุดในการใช้การทำแซนด์บ็อกซ์ เนื่องจากไม่มีการเริ่มต้นใช้งานแบบไม่ทำแซนด์บ็อกซ์ซึ่งอาจส่งผลเสียได้
ในข้อมูลโค้ดด้านล่าง เราจะกำหนดเส้นทางของไบนารีที่จะทำแซนด์บ็อกซ์และอาร์กิวเมนต์ที่เราต้องส่งผ่านไปยัง syscall execve ดังที่คุณเห็นในไฟล์ส่วนหัว
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() คุณต้องสามารถกำหนดใน
โค้ดได้ว่าต้องการเริ่มทำแซนด์บ็อกซ์เมื่อใด และต้องเป็นแบบ Single-Threaded (อ่าน
เหตุผลในคำถามที่พบบ่อย)
ในข้อมูลโค้ดด้านล่าง เราใช้โค้ดเดียวกันกับที่ระบุไว้ในวิธีที่ 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);
เนื่องจากตอนนี้ตัวดำเนินการมีแซนด์บ็อกซ์ที่ปิดใช้จนกว่าจะได้รับการแจ้งเตือนจาก Sandboxee เราจึงต้องสร้างอินสแตนซ์ ::sandbox2::Client ตั้งค่าการสื่อสารระหว่างตัวดำเนินการและ Sandboxee จากนั้นแจ้งตัวดำเนินการว่าการเริ่มต้นใช้งานของเราเสร็จสิ้นแล้วและต้องการเริ่มทำแซนด์บ็อกซ์ตอนนี้โดยเรียกใช้ 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 เป็น
Sandboxee และจะแจ้งตัวดำเนินการ (crc4sandbox.cc) เมื่อควรเข้าสู่
แซนด์บ็อกซ์
วิธีที่ 3: Custom Forkserver - เตรียมไบนารี รอคำขอ Fork และทำแซนด์บ็อกซ์ด้วยตนเอง
โหมดนี้ช่วยให้คุณเริ่มไบนารี เตรียมไบนารีสำหรับการทำแซนด์บ็อกซ์ และทำให้ตัวดำเนินการใช้งานได้ในบางช่วงเวลาของวงจรชีวิตของไบนารี
ตัวดำเนินการจะส่งคำขอ Fork ไปยังไบนารี ซึ่งจะ fork() (ผ่าน ::sandbox2::ForkingClient::EnterForkLoop()) กระบวนการที่สร้างขึ้นใหม่จะพร้อมทำแซนด์บ็อกซ์ด้วย ::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 จริง ตัวอย่างการใช้งานทั่วไปอีกอย่างหนึ่งคือเมื่อ Sandboxee มีการเริ่มต้นใช้งานที่ใช้เวลานานและใช้ CPU มาก ซึ่งสามารถเรียกใช้ได้ก่อนที่จะประมวลผลข้อมูลที่ไม่น่าเชื่อถือ
ดูตัวอย่างของวิธีดำเนินการนี้ได้ที่ custom_fork
2. สร้างนโยบายแซนด์บ็อกซ์
เมื่อมีตัวดำเนินการแล้ว คุณอาจต้องการกำหนดนโยบาย แซนด์บ็อกซ์สำหรับ Sandboxee มิฉะนั้น Sandboxee จะได้รับการปกป้องโดยนโยบาย Syscall เริ่มต้นเท่านั้น
วัตถุประสงค์ของนโยบายแซนด์บ็อกซ์คือการจำกัด Syscall และอาร์กิวเมนต์ที่ Sandboxee สามารถใช้ได้ รวมถึงไฟล์ที่เข้าถึงได้ คุณจะต้องมีความเข้าใจโดยละเอียดเกี่ยวกับ Syscall ที่โค้ดที่คุณวางแผนจะทำแซนด์บ็อกซ์ต้องใช้ วิธีหนึ่งในการสังเกต Syscall คือการเรียกใช้โค้ดด้วยเครื่องมือบรรทัดคำสั่ง strace ของ Linux
เมื่อมีรายการ Syscall แล้ว คุณสามารถใช้ PolicyBuilder เพื่อกำหนดนโยบายได้
PolicyBuilder มาพร้อมกับฟังก์ชันอำนวยความสะดวกและฟังก์ชันตัวช่วยมากมายที่อนุญาตให้ดำเนินการทั่วไปหลายอย่าง (ซึ่งหลายอย่างรวมอยู่ใน
DefaultPolicyBuilder() แล้ว) รายการต่อไปนี้เป็นเพียงข้อความที่ตัดตอนมาของฟังก์ชันที่มีอยู่
- อนุญาต Syscall ใดก็ได้สำหรับการเริ่มต้นใช้งานกระบวนการ
AllowStaticStartup();AllowDynamicStartup();
- อนุญาต Syscall open/read/write* ใดก็ได้:
AllowOpen();AllowRead();AllowWrite();
- เพิ่ม Syscall ที่เกี่ยวข้องกับการออก/การเข้าถึง/สถานะใดก็ได้ในรายการที่อนุญาต
AllowExit();AllowStat();AllowAccess();
- เพิ่ม Syscall ที่เกี่ยวข้องกับการพัก/เวลาใดก็ได้ในรายการที่อนุญาต
AllowTime();AllowSleep();
ฟังก์ชันอำนวยความสะดวกเหล่านี้จะอนุญาต Syscall ที่เกี่ยวข้อง ซึ่งมีข้อดีคือสามารถใช้นโยบายเดียวกันในสถาปัตยกรรมต่างๆ ที่ไม่มี Syscall บางอย่าง (เช่น ARM64 ไม่มี Syscall OPEN) แต่มีความเสี่ยงด้านความปลอดภัยเล็กน้อยในการเปิดใช้ Syscall มากกว่าที่จำเป็น ตัวอย่างเช่น AllowOpen() ช่วยให้ Sandboxee เรียกใช้ 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();
}
3. ปรับขีดจำกัด
นโยบายแซนด์บ็อกซ์จะป้องกันไม่ให้ Sandboxee เรียกใช้ Syscall ที่เฉพาะเจาะจง จึงช่วยลดพื้นที่การโจมตี อย่างไรก็ตาม ผู้โจมตีอาจยังคงก่อให้เกิดผลที่ไม่พึงประสงค์ได้โดยการเรียกใช้กระบวนการอย่างไม่สิ้นสุดหรือใช้ RAM และทรัพยากรอื่นๆ จนหมด
เพื่อจัดการกับภัยคุกคามนี้ Sandboxee จะทำงานภายใต้ขีดจำกัดการดำเนินการที่เข้มงวดโดยค่าเริ่มต้น หากขีดจำกัดเริ่มต้นเหล่านี้ทำให้เกิดปัญหาในการดำเนินการโปรแกรมอย่างถูกต้อง คุณสามารถปรับขีดจำกัดได้โดยใช้คลาส 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 ได้ที่เครื่องมือตัวอย่าง
tool
4. เรียกใช้แซนด์บ็อกซ์
ในส่วนก่อนหน้า คุณได้เตรียมสภาพแวดล้อมแซนด์บ็อกซ์ นโยบาย ตัวดำเนินการ และ Sandboxee แล้ว ขั้นตอนถัดไปคือการสร้างออบเจ็กต์ Sandbox2 และเรียกใช้ออบเจ็กต์ดังกล่าว
เรียกใช้แบบซิงโครนัส
แซนด์บ็อกซ์สามารถทำงานแบบซิงโครนัสได้ ซึ่งจะบล็อกจนกว่าจะมีผลลัพธ์ ข้อมูลโค้ดด้านล่างแสดงการสร้างอินสแตนซ์ของออบเจ็กต์ Sandbox2 และการดำเนินการแบบซิงโครนัส ดูตัวอย่างโดยละเอียดเพิ่มเติมได้ที่
static.
#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();
เรียกใช้แบบอะซิงโครนัส
คุณยังเรียกใช้แซนด์บ็อกซ์แบบอะซิงโครนัสได้ด้วย ซึ่งจะไม่บล็อกจนกว่าจะมีผลลัพธ์ วิธีนี้มีประโยชน์ เช่น เมื่อสื่อสารกับ Sandboxee ข้อมูลโค้ดด้านล่างแสดง Use Case นี้ ดูตัวอย่างโดยละเอียดเพิ่มเติมได้ที่ 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. การสื่อสารกับ Sandboxee
โดยค่าเริ่มต้น ตัวดำเนินการจะสื่อสารกับ Sandboxee ผ่านตัวอธิบายไฟล์ได้ ซึ่งอาจเป็นสิ่งที่คุณต้องการทั้งหมด เช่น หากคุณเพียงต้องการแชร์ไฟล์กับ Sandboxee หรืออ่านเอาต์พุตมาตรฐานของ Sandboxee
อย่างไรก็ตาม คุณมีแนวโน้มที่จะต้องการตรรกะการสื่อสารที่ซับซ้อนมากขึ้นระหว่างตัวดำเนินการและ Sandboxee คุณสามารถใช้ Comms API (ดูไฟล์ส่วนหัว comms.h comms.h) เพื่อส่งจำนวนเต็ม สตริง บัฟเฟอร์ไบต์ โปรโตบัฟ หรือ ตัวอธิบายไฟล์
การแชร์ตัวอธิบายไฟล์
เมื่อใช้ Inter-Process Communication API (ดู
ipc.h)
คุณสามารถใช้ MapFd() หรือ ReceiveFd() ได้
ใช้
MapFd()เพื่อแมปตัวบอกไฟล์จากตัวดำเนินการกับ Sandboxee ซึ่งสามารถใช้เพื่อแชร์ไฟล์ที่เปิดจากตัวดำเนินการเพื่อใช้ใน Sandboxee ดูตัวอย่างการใช้งานได้ที่ static// The executor opened /proc/version and passes it to the sandboxee as stdin executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);ใช้
ReceiveFd()เพื่อสร้างปลายทาง socketpair ซึ่งสามารถใช้เพื่ออ่านเอาต์พุตมาตรฐานหรือข้อผิดพลาดมาตรฐานของ Sandboxee ดูตัวอย่างการใช้งานได้ที่ tool// The executor receives a file descriptor of the sandboxee stdout int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
การใช้ Comms API
Sandbox2 มี Comms API ที่สะดวก ซึ่งเป็นวิธีที่ง่ายในการแชร์จำนวนเต็ม สตริง หรือบัฟเฟอร์ไบต์ระหว่างตัวดำเนินการและ Sandboxee ด้านล่างนี้คือข้อมูลโค้ดบางส่วนที่คุณดูได้ในตัวอย่าง crc4
หากต้องการเริ่มต้นใช้งาน Comms API คุณต้องรับออบเจ็กต์ Comms จากออบเจ็กต์ Sandbox2 ก่อน
sandbox2::Comms* comms = s2.comms();
เมื่อออบเจ็กต์ Comms พร้อมใช้งานแล้ว คุณจะส่งข้อมูลไปยัง Sandboxee ได้โดยใช้ฟังก์ชัน Send* ฟังก์ชันใดฟังก์ชันหนึ่ง ดูตัวอย่างการใช้งาน Comms API
ได้ในตัวอย่าง crc4 ข้อมูลโค้ดด้านล่างแสดงตัวอย่างจากตัวอย่างดังกล่าว ตัวดำเนินการจะส่ง unsigned char
buf[size] ด้วย SendBytes(buf, size)
if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
/* handle error */
}
หากต้องการรับข้อมูลจาก Sandboxee ให้ใช้ฟังก์ชัน Recv* ฟังก์ชันใดฟังก์ชันหนึ่ง ข้อมูลโค้ด
ด้านล่างเป็นตัวอย่างจากตัวอย่าง
crc4 ตัวดำเนินการจะรับ Checksum เป็นจำนวนเต็มแบบไม่มีเครื่องหมาย 32 บิต: uint32_t crc4
if (!(comms->RecvUint32(&crc4))) {
/* handle error */
}
การแชร์ข้อมูลกับบัฟเฟอร์
ฟังก์ชันการทำงานอีกอย่างหนึ่งในการแชร์ข้อมูลคือการใช้ buffer API เพื่อแชร์ข้อมูลจำนวนมากและหลีกเลี่ยงการคัดลอกที่มีค่าใช้จ่ายสูงซึ่งส่งไปมาระหว่างตัวดำเนินการและ Sandboxee
ตัวดำเนินการจะสร้างบัฟเฟอร์ตามขนาดและข้อมูลที่จะส่งผ่าน หรือสร้างโดยตรง
จากตัวบอกไฟล์ แล้วส่งผ่านไปยัง Sandboxee โดยใช้ comms->SendFD()
ในตัวดำเนินการและ comms->RecvFD() ใน Sandboxee
ในข้อมูลโค้ดด้านล่าง คุณจะเห็นฝั่งตัวดำเนินการ แซนด์บ็อกซ์จะทำงานแบบอะซิงโครนัสและแชร์ข้อมูลผ่านบัฟเฟอร์กับ Sandboxee
// 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());
ในฝั่ง Sandboxee คุณต้องสร้างออบเจ็กต์บัฟเฟอร์และอ่านข้อมูลจากตัวบอกไฟล์ที่ตัวดำเนินการส่งมาด้วย
// 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. ออกจากแซนด์บ็อกซ์
คุณต้องปรับวิธีสิ้นสุดแซนด์บ็อกซ์และ Sandboxee ตามวิธีที่คุณเรียกใช้แซนด์บ็อกซ์ (ดู ขั้นตอนนี้)
การออกจากแซนด์บ็อกซ์ที่ทำงานแบบซิงโครนัส
หากแซนด์บ็อกซ์ทำงานแบบซิงโครนัส Run จะแสดงผลเมื่อ Sandboxee ทำงานเสร็จแล้วเท่านั้น จึงไม่จำเป็นต้องมีขั้นตอนเพิ่มเติมสำหรับการสิ้นสุด ข้อมูลโค้ดด้านล่างแสดงสถานการณ์นี้
Sandbox2::Result result = s2.Run();
LOG(INFO) << "Final execution status: " << result.ToString();
การออกจากแซนด์บ็อกซ์ที่ทำงานแบบอะซิงโครนัส
หากแซนด์บ็อกซ์ทำงานแบบอะซิงโครนัส คุณจะมีตัวเลือก 2 ตัวเลือกสำหรับการสิ้นสุด ตัวเลือกแรกคือรอให้ Sandboxee ทำงานเสร็จสมบูรณ์และรับสถานะการดำเนินการสุดท้าย
sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();
อีกทางเลือกหนึ่งคือคุณสามารถหยุด Sandboxee ได้ทุกเมื่อ แต่เรายังคงแนะนำให้เรียกใช้ AwaitResult() เนื่องจาก Sandboxee อาจสิ้นสุดการทำงานด้วยเหตุผลอื่นในระหว่างนี้
s2.Kill();
sandbox2::Result result = s2.AwaitResult();
LOG(INFO) << "Final execution status: " << result.ToString();
7. ทดสอบ
การติดตั้งใช้งานแซนด์บ็อกซ์ควรมีการทดสอบเช่นเดียวกับโค้ดอื่นๆ การทดสอบแซนด์บ็อกซ์ไม่ได้มีไว้เพื่อทดสอบความถูกต้องของโปรแกรม แต่มีไว้เพื่อตรวจสอบว่าโปรแกรมที่ทำแซนด์บ็อกซ์สามารถทำงานได้โดยไม่มีปัญหา เช่น การละเมิดแซนด์บ็อกซ์ นอกจากนี้ยังช่วยให้มั่นใจว่านโยบายแซนด์บ็อกซ์ถูกต้อง
โปรแกรมที่ทำแซนด์บ็อกซ์จะได้รับการทดสอบในลักษณะเดียวกับที่คุณจะเรียกใช้ในสภาพแวดล้อมที่ใช้งานจริง โดยใช้อาร์กิวเมนต์และไฟล์อินพุตที่โปรแกรมจะประมวลผลตามปกติ
การทดสอบเหล่านี้อาจเป็นเพียงการทดสอบเชลล์หรือการทดสอบ C++ โดยใช้กระบวนการย่อย ดูตัวอย่างเพื่อเป็นแรงบันดาลใจ
บทสรุป
ขอขอบคุณที่อ่านมาจนถึงตรงนี้ เราหวังว่าคุณจะชอบคำแนะนำของเราและตอนนี้รู้สึกพร้อมที่จะสร้างแซนด์บ็อกซ์ของคุณเองเพื่อช่วยรักษาความปลอดภัยให้ผู้ใช้
การสร้างแซนด์บ็อกซ์และนโยบายเป็นงานที่ยากและอาจเกิดข้อผิดพลาดเล็กน้อยได้ง่าย เราขอแนะนำให้ผู้เชี่ยวชาญด้านความปลอดภัยตรวจสอบนโยบายและโค้ดของคุณเพื่อความปลอดภัย