۲. ایجاد خط‌مشی جعبه شنی

وقتی مجری داشته باشید، احتمالاً می‌خواهید خط‌مشی جعبه شنی را برای «جعبه‌شنی‌شونده» تعریف کنید. درغیراین‌صورت، Sandboxee فقط با خط‌مشی پیش‌فرض فراخوان سیستمی محافظت می‌شود.

با «خط‌مشی جعبه شنی»، هدف محدود کردن syscalls و آرگومان‌هایی است که Sandboxee می‌تواند ایجاد کند، و همچنین فایل‌هایی که می‌تواند به آن‌ها دسترسی داشته باشد. باید درک دقیقی از syscallهای موردنیاز کدی که قصد دارید در محیط امن اجرا کنید داشته باشید. یکی از راه‌های مشاهده syscalls اجرای کد با ابزار خط فرمان strace در Linux است.

وقتی فهرست فراخوانی‌های سیستم را دریافت کردید، می‌توانید از PolicyBuilder برای تعریف خط‌مشی استفاده کنید.

PolicyBuilder با بسیاری از توابع کمکی و راحتی ارائه می‌شود که بسیاری از عملیات‌های رایج را ممکن می‌سازد (بسیاری از آن‌ها قبلاً در DefaultPolicyBuilder() گنجانده شده‌اند). فهرست زیر تنها بخش کوچکی از توابع دردسترس است:

  • مجاز کردن هر فراخوان سیستمی برای راه‌اندازی پردازش:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • مجاز کردن هرگونه فراخوان سیستمی باز کردن/خواندن/نوشتن*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • فهرست مجاز فراخوان‌های سیستمی مرتبط با خروج/دسترسی/وضعیت:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • همه فراخوان‌های سیستمی مربوط به خواب/زمان را در فهرست مجاز قرار دهید:
    • AllowTime();
    • AllowSleep();

این توابع راحتی هر فراخوان سیستمی مربوطه را در فهرست مجاز قرار می‌دهند. این کار این مزیت را دارد که می‌توان از یک خط‌مشی یکسان در معماری‌های مختلفی که در آن‌ها برخی‌از فراخوان‌های سیستمی دردسترس نیستند (برای نمونه، ARM64 فراخوان سیستمی OPEN ندارد) استفاده کرد، اما با خطر امنیتی جزئی فعال کردن فراخوان‌های سیستمی بیشتر از آنچه ممکن است لازم باشد. برای مثال، AllowOpen() به Sandboxee اجازه می‌دهد هر سیستم‌فراخوان مربوط به باز کردن را فراخوانی کند. اگر فقط می‌خواهید یک فراخوان سیستمی خاص را در فهرست مجازها قرار دهید، می‌توانید از AllowSyscall(); استفاده کنید برای قرار دادن چند فراخوان سیستمی در فهرست مجازها به‌طور هم‌زمان می‌توانید از AllowSyscalls() استفاده کنید.

تاکنون این خط‌مشی فقط شناسه syscall را بررسی می‌کند. اگر نیاز دارید خط‌مشی را بیشتر تقویت کنید و می‌خواهید خط‌مشی‌ای تعریف کنید که در آن فقط سیستم‌فراخوانی با آرگومان‌های خاصی مجاز باشد، باید از AddPolicyOnSyscall() یا AddPolicyOnSyscalls() استفاده کنید. این توابع نه تنها شناسه فراخوان سیستمی را به‌عنوان آرگومان می‌گیرند، بلکه از فیلتر خام seccomp-bpf بااستفاده از کلان‌دستورهای کمکی bpf از هسته Linux نیز استفاده می‌کنند. برای اطلاعات بیشتر درباره BPF، مستندسازی هسته را ببینید. اگر متوجه شدید که کد BPF تکراری می‌نویسید که فکر می‌کنید باید پوشاننده قابلیت استفاده داشته باشد، می‌توانید درخواست ویژگی ارسال کنید.

جدااز توابع مربوط به syscall،‏ PolicyBuilder تعدادی تابع مربوط به سیستم فایل نیز ارائه می‌دهد، مثل AddFile() یا AddDirectory()، برای bind-mount کردن فایل/دایرکتوری در sandbox. از دستیار AddTmpfs() می‌توان برای افزودن فضای ذخیره‌سازی فایل موقت در چارچوب امن استفاده کرد.

یکی از عملکردهای بسیار مفید AddLibrariesForBinary() است که کتابخانه‌ها و پیونددهنده موردنیاز یک باینری را اضافه می‌کند.

متأسفانه، تهیه فهرست مجاز فراخوان‌های سیستمی هنوز کمی کار دستی است. خط‌مشی‌ای با فراخوان‌های سیستمی که می‌دانید دودویی‌تان نیاز دارد ایجاد کنید و آن را با حجم کاری معمول اجرا کنید. اگر نقضی ایجاد شد، سیستم‌فراخوان را در فهرست مجازها قرار دهید و فرایند را تکرار کنید. اگر با تخلفی مواجه شدید که فکر می‌کنید ممکن است فهرست سفید کردن آن خطرناک باشد و برنامه خطاها را به‌خوبی مدیریت می‌کند، می‌توانید با 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();
}