2. יצירת מדיניות של ארגז חול
אחרי שיש לכם רכיב Executor, סביר להניח שתרצו להגדיר Sandbox Policy עבור Sandboxee. אחרת, ה-Sandboxee מוגן רק על ידי מדיניות ברירת המחדל של Syscall.
מטרת המדיניות של ארגז החול היא להגביל את קריאות המערכת (syscalls) והארגומנטים שאפשר לבצע בארגז החול, וגם את הקבצים שאפשר לגשת אליהם. צריך להבין לעומק את קריאות המערכת (syscalls) שנדרשות לקוד שמתכננים להריץ בסביבת Sandbox. אחת הדרכים לצפות בקריאות המערכת היא להריץ את הקוד באמצעות הכלי strace של Linux בשורת הפקודה.
אחרי שיש לכם את רשימת קריאות המערכת, אתם יכולים להשתמש ב-PolicyBuilder כדי להגדיר את המדיניות. PolicyBuilder כולל הרבה פונקציות נוחות ועזר שמאפשרות לבצע הרבה פעולות נפוצות. הרשימה הבאה היא רק קטע קטן של הפונקציות הזמינות:
- הוספה לרשימת ההיתרים של כל קריאת מערכת להפעלת תהליך:
AllowStaticStartup();
AllowDynamicStartup();
- הוספה לרשימת ההיתרים של כל קריאות המערכת (syscalls) מסוג open/read/write*:
AllowOpen();
AllowRead();
AllowWrite();
- הוספה לרשימת ההיתרים של כל קריאות המערכת שקשורות ליציאה, לגישה או למצב:
AllowExit();
AllowStat();
AllowAccess();
- הוספה לרשימת ההיתרים של כל קריאות המערכת שקשורות לשינה או לזמן:
AllowTime();
AllowSleep();
הפונקציות הנוחות האלה מאפשרות להוסיף לרשימת ההיתרים כל קריאת מערכת רלוונטית. היתרון בשיטה הזו הוא שאפשר להשתמש באותה מדיניות בארכיטקטורות שונות שבהן קריאות מערכת מסוימות לא זמינות (למשל, ב-ARM64 אין קריאת מערכת OPEN), אבל יש סיכון אבטחה קטן יותר שנובע מהפעלת יותר קריאות מערכת ממה שנדרש. לדוגמה, AllowOpen() מאפשרת ל-Sandboxee לקרוא לכל קריאת מערכת פתוחה שקשורה אליה. אם רוצים להוסיף לרשימת ההיתרים רק קריאת מערכת ספציפית אחת, אפשר להשתמש ב-AllowSyscall();
. כדי להוסיף לרשימת ההיתרים כמה קריאות מערכת בבת אחת, אפשר להשתמש ב-AllowSyscalls()
.
בשלב הזה המדיניות בודקת רק את מזהה קריאת המערכת. אם אתם רוצים להגדיר מדיניות שבה מותרת רק קריאת מערכת עם ארגומנטים מסוימים, אתם צריכים להשתמש ב-AddPolicyOnSyscall()
או ב-AddPolicyOnSyscalls()
. הפונקציות האלה מקבלות כארגומנט לא רק את מזהה קריאת המערכת, אלא גם מסנן seccomp-bpf גולמי באמצעות פקודות מאקרו של bpf helper מליבת לינוקס. מידע נוסף על BPF זמין במסמכי התיעוד של ליבת המערכת. אם אתם כותבים קוד BPF חוזר על עצמו ואתם חושבים שצריך להיות לו wrapper לשיפור השימושיות, אתם מוזמנים לשלוח בקשה להוספת תכונה.
בנוסף לפונקציות שקשורות ל-syscall, הכלי PolicyBuilder מספק גם מספר פונקציות שקשורות למערכת הקבצים, כמו AddFile()
או AddDirectory()
, כדי לבצע bind-mount של קובץ או ספריה בארגז החול. אפשר להשתמש בכלי העזר AddTmpfs()
כדי להוסיף אחסון זמני לקבצים בארגז החול.
פונקציה שימושית במיוחד היא AddLibrariesForBinary()
שמוסיפה את הספריות ואת ה-linker שנדרשים לקובץ בינארי.
לצערנו, עדיין צריך לעבוד קצת באופן ידני כדי להחליט אילו קריאות למערכת להוסיף לרשימת ההיתרים. יוצרים מדיניות עם קריאות המערכת שנדרשות לקובץ הבינארי ומריצים אותה עם עומס עבודה נפוץ. אם מופעלת הפרה, צריך להוסיף את קריאת המערכת לרשימת ההיתרים ולחזור על התהליך. אם נתקלתם בהפרה שלדעתכם עלולה להיות מסוכנת להוספה לרשימת ההיתרים, והתוכנית מטפלת בשגיאות בצורה חלקה, אתם יכולים לנסות לגרום לה להחזיר שגיאה במקום זאת באמצעות 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();
}