2. יצירת מדיניות Sandbox

לאחר שיש לך מנהל מערכת, סביר להניח שתרצה להגדיר מדיניות ארגז חול עבור Sandboxee. בכל מקרה אחר, Sandboxee מוגן רק על ידי מדיניות ברירת המחדל של Syscall.

בעזרת מדיניות Sandbox, המטרה היא להגביל את ה-syscall והארגומנטים ש-Sandboxee יכול ליצור, וכן את הקבצים שאליהם יש לו גישה. יהיה עליך להבין לעומק את ה-syscalls שנדרשים על ידי הקוד שאתם מתכננים להפעיל ב-Sandbox. אחת הדרכים לצפייה ב-syscalls היא הרצת הקוד באמצעות הרצועה של כלי שורת הפקודה של Linux.

אחרי שיוצרים את הרשימה של ה-syscalls, אפשר להשתמש ב-PolicyBuilder כדי להגדיר את המדיניות. PolicyBuilder כוללות הרבה פונקציות עזר ותכונות שימושיות שמאפשרות לבצע הרבה פעולות נפוצות. הרשימה הבאה היא רק חלק קטן מהפונקציות הזמינות:

  • רשימת היתרים של כל קריאה למערכת הפעלה של התהליך:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • יש להוסיף לרשימת ההיתרים כל מערכת הפעלה/קריאה/כתיבה*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • רשימת היתרים של שיחות מערכת שקשורות ליציאה/גישה/למצב:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • רשימת ההיתרים של שיחות מערכת שקשורות לשינה או לזמן:
    • AllowTime();
    • AllowSleep();

פונקציות הנוחות האלה מוסיפים לרשימת ההיתרים כל מערכת הפעלה רלוונטית. היתרון בכך הוא שניתן להשתמש באותה מדיניות בארכיטקטורות שונות שבהן קריאות למערכות (syscalls) מסוימות אינן זמינות (למשל, ל-ARM64 אין פונקציית syscall פתוחה), אך עם סיכון אבטחה קל להפעלת יותר Syscalls מהנדרש. לדוגמה, AllowOpen() מאפשר ל-Sandboxee לקרוא לכל מערכת הפעלה קשורה פתוחה. אם רוצים להוסיף לרשימת ההיתרים רק שיחה אחת ספציפית, אפשר להשתמש ב-AllowSyscall(); כדי לאשר כמה שיחות מערכת הפעלה בו-זמנית. ניתן להשתמש ב-AllowSyscalls().

עד עכשיו המדיניות בודקת רק את מזהה ה-Syscall. אם צריך לחזק עוד יותר את המדיניות, ואתם רוצים להגדיר מדיניות שבה אפשר להפעיל מערכת הפעלה רק עם ארגומנטים מסוימים, צריך להשתמש בפונקציה AddPolicyOnSyscall() או ב-AddPolicyOnSyscalls(). לא רק פונקציות אלו מקבלות את מזהה syscall כארגומנט, אלא גם מסנן גולמי seccomp-bpf באמצעות פקודות המאקרו bpf helper מליבת Linux. מידע נוסף על BPF זמין בתיעוד הליבה. אם נתקלתם בכתיבת קוד BPF שחוזר על עצמו, שלדעתכם צריך לכלול wrapper של נוחות שימוש, אתם יכולים להגיש בקשה להוספת תכונה.

מלבד פונקציות שקשורות ל-Syscall, PolicyBuilder מספק גם מספר פונקציות שקשורות למערכת קבצים כמו AddFile() או AddDirectory(), כדי לאגד קובץ או ספרייה בארגז החול. ניתן להשתמש בכלי העזר AddTmpfs() כדי להוסיף אחסון זמני של קבצים בתוך ארגז החול.

הפונקציה השימושית במיוחד היא AddLibrariesForBinary(), שמוסיפה את הספריות ואת המנגנון שנדרשים על ידי קובץ בינארי.

לצערי, יצירת מערכות הפעלה לרשימת ההיתרים עדיין כרוכה במעט עבודה ידנית. יצירת מדיניות עם ה-syscalls שאתם יודעים מה הצרכים הבינאריים שלכם ומריצים אותה עם עומס עבודה משותף. אם מתרחשת הפרה, מוסיפים את מערכת ההפעלה לרשימת ההיתרים וחוזרים על התהליך. אם נתקלתם בהפרה שלדעתכם עלולה להיות מסוכנת להוספה לרשימת ההיתרים, והתוכנית מטפלת בשגיאות בצורה חלקה, אפשר לנסות להחזיר שגיאה במקום זאת באמצעות 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();
}