ในหน้านี้ คุณจะได้เรียนรู้วิธีสร้างสภาพแวดล้อมแซนด์บ็อกซ์ของคุณเองด้วย Sandbox2 คุณจะได้เรียนรู้วิธีกำหนดนโยบาย Sandbox และการปรับแต่งขั้นสูง แต่เป็นเรื่องปกติ ใช้ข้อมูลที่นี่เป็นแนวทางควบคู่ไปกับตัวอย่างและเอกสารประกอบโค้ดในไฟล์ส่วนหัว
1. เลือกวิธีการดำเนินการแซนด์บ็อกซ์
การแซนด์บ็อกซ์เริ่มต้นด้วยตัวดำเนินการ (ดูตัวดำเนินการ แซนด์บ็อกซ์) ซึ่งมีหน้าที่ รับผิดชอบในการเรียกใช้Sandboxee ไฟล์ส่วนหัว executor.h มี API ที่จำเป็นสำหรับวัตถุประสงค์นี้ API มีความยืดหยุ่นสูง และช่วยให้คุณเลือกสิ่งที่เหมาะกับกรณีการใช้งานของคุณมากที่สุดได้ ส่วนต่อไปนี้ จะอธิบาย 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: Forkserver ของ Sandbox2 - บอกผู้ปฏิบัติงานเมื่อใดที่ควรอยู่ในแซนด์บ็อกซ์
วิธีนี้ช่วยให้คุณมีความยืดหยุ่นในการยกเลิกการแซนด์บ็อกซ์ในระหว่างการเริ่มต้น
แล้วเลือกเวลาที่จะเข้าสู่แซนด์บ็อกซ์โดยการเรียกใช้
::sandbox2::Client::SandboxMeHere()
คุณต้องกำหนดในโค้ดได้ว่าต้องการเริ่มแซนด์บ็อกซ์เมื่อใด และต้องเป็นแบบ Single-Thread (อ่านเหตุผลในคำถามที่พบบ่อย)
ในข้อมูลโค้ดด้านล่าง เราใช้โค้ดเดียวกันกับที่ระบุไว้ในวิธีที่ 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);
เนื่องจากตอนนี้ตัวดำเนินการมี Sandbox ที่ปิดใช้จนกว่าจะได้รับการแจ้งเตือนจาก
Sandboxee เราจึงต้องสร้างอินสแตนซ์ ::sandbox2::Client
ตั้งค่า
การสื่อสารระหว่างตัวดำเนินการกับ Sandboxee แล้วแจ้งให้ตัวดำเนินการทราบ
ว่าการเริ่มต้นของเราเสร็จสิ้นแล้ว และเราต้องการเริ่มใช้ Sandbox ตอนนี้โดย
เรียกใช้ 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: 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 จริง อีกตัวอย่างการใช้งานทั่วไป คือเมื่อ Sandboxee มีการเริ่มต้นที่ใช้ CPU เป็นเวลานาน ซึ่งสามารถเรียกใช้ได้ก่อนที่จะประมวลผลข้อมูลที่ไม่น่าเชื่อถือ
ดูตัวอย่างของเมธอด Executor นี้ได้ที่ custom_fork
2. สร้างนโยบายแซนด์บ็อกซ์
เมื่อมีโปรแกรมเรียกใช้แล้ว คุณอาจต้องกำหนดนโยบาย Sandbox สำหรับ Sandboxee ไม่เช่นนั้น Sandboxee จะได้รับการปกป้องโดยนโยบาย Syscall เริ่มต้นเท่านั้น
นโยบายแซนด์บ็อกซ์มีวัตถุประสงค์เพื่อจำกัด Syscall และอาร์กิวเมนต์ ที่ Sandboxee สามารถทำได้ รวมถึงไฟล์ที่เข้าถึงได้ คุณจะต้องมีความเข้าใจโดยละเอียดเกี่ยวกับ Syscall ที่โค้ดที่คุณวางแผนจะแซนด์บ็อกซ์ต้องการ วิธีหนึ่งในการสังเกต Syscall คือการเรียกใช้โค้ดด้วยเครื่องมือบรรทัดคำสั่ง strace ของ Linux
เมื่อมีรายการ Syscall แล้ว คุณจะใช้ PolicyBuilder เพื่อกำหนดนโยบายได้ PolicyBuilder มาพร้อมฟังก์ชันอำนวยความสะดวกและฟังก์ชันตัวช่วยมากมาย ที่ช่วยให้ดำเนินการทั่วไปได้หลายอย่าง รายการต่อไปนี้เป็นเพียง ตัวอย่างฟังก์ชันที่มีอยู่เพียงเล็กน้อย
- เพิ่ม Syscall ลงในรายการที่อนุญาตสำหรับการเริ่มต้นกระบวนการ
AllowStaticStartup();
AllowDynamicStartup();
- เพิ่ม Syscall ที่เปิด/อ่าน/เขียน* ลงในรายการที่อนุญาต
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()
#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
ได้ที่เครื่องมือตัวอย่าง
4. เรียกใช้แซนด์บ็อกซ์
ในส่วนก่อนหน้า คุณได้เตรียมสภาพแวดล้อมแซนด์บ็อกซ์ นโยบาย และ
Executor และ 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 ข้อมูลโค้ดด้านล่างแสดงกรณีการใช้งานนี้ ดูตัวอย่างแบบละเอียดเพิ่มเติมได้ที่ 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
อย่างไรก็ตาม คุณอาจต้องใช้ตรรกะการสื่อสารที่ซับซ้อนมากขึ้น ระหว่าง Executor กับ Sandboxee คุณใช้ Comms API (ดูไฟล์ส่วนหัว comms.h) เพื่อส่งจำนวนเต็ม สตริง บัฟเฟอร์ไบต์ Protobuf หรือตัวอธิบายไฟล์ได้
การแชร์ตัวบอกไฟล์
เมื่อใช้ Inter-Process Communication API (ดู ipc.h)
คุณจะใช้ MapFd()
หรือ ReceiveFd()
ได้
ใช้
MapFd()
เพื่อแมปตัวอธิบายไฟล์จาก Executor ไปยัง 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 ได้ ตัวอย่างการใช้งานดูได้ในเครื่องมือ// 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 ผู้ดำเนินการจะได้รับ
ผลรวมตรวจสอบในจำนวนเต็มแบบไม่มีเครื่องหมาย 32 บิต: uint32_t crc4;
if (!(comms->RecvUint32(&crc4))) {
/* handle error */
}
การแชร์ข้อมูลกับบัฟเฟอร์
ฟังก์ชันการแชร์ข้อมูลอีกอย่างคือการใช้ buffer API เพื่อแชร์ข้อมูลจำนวนมากและหลีกเลี่ยงการคัดลอกที่มีค่าใช้จ่ายสูงซึ่งส่งกลับไปกลับมาระหว่างตัวดำเนินการและ Sandboxee
โดยจะสร้างบัฟเฟอร์ตามขนาดและข้อมูลที่จะส่ง หรือสร้างจากตัวอธิบายไฟล์โดยตรง
แล้วส่งไปยัง Sandboxee โดยใช้ comms->SendFD()
ใน Executor และ 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. ทดสอบ
การติดตั้งใช้งาน Sandbox ควรมีการทดสอบเช่นเดียวกับโค้ดอื่นๆ การทดสอบแซนด์บ็อกซ์ ไม่ได้มีไว้เพื่อทดสอบความถูกต้องของโปรแกรม แต่มีไว้เพื่อตรวจสอบ ว่าโปรแกรมที่อยู่ในแซนด์บ็อกซ์สามารถทำงานได้โดยไม่มีปัญหา เช่น การละเมิดแซนด์บ็อกซ์ นอกจากนี้ยังช่วยให้มั่นใจได้ว่านโยบายแซนด์บ็อกซ์ถูกต้อง
โปรแกรมแซนด์บ็อกซ์จะได้รับการทดสอบในลักษณะเดียวกับที่คุณจะเรียกใช้ในเวอร์ชันที่ใช้งานจริง โดยมีอาร์กิวเมนต์และไฟล์อินพุตที่โปรแกรมจะประมวลผลตามปกติ
การทดสอบเหล่านี้อาจเป็นเพียงการทดสอบเชลล์หรือการทดสอบ C++ โดยใช้กระบวนการย่อย ดูตัวอย่างเพื่อหาแรงบันดาลใจ
บทสรุป
ขอขอบคุณที่อ่านมาจนถึงตรงนี้ เราหวังว่าคุณจะชอบคู่มือนี้และรู้สึกพร้อม ที่จะสร้างแซนด์บ็อกซ์ของคุณเองเพื่อช่วยรักษาความปลอดภัยให้ผู้ใช้
การสร้างแซนด์บ็อกซ์และนโยบายเป็นงานที่ยากและมีแนวโน้มที่จะเกิดข้อผิดพลาดเล็กๆ น้อยๆ เราขอแนะนำให้คุณปรึกษาผู้เชี่ยวชาญด้านความปลอดภัยเพื่อตรวจสอบนโยบายและโค้ดของคุณเพื่อความปลอดภัย