2. إنشاء سياسة "وضع الحماية"

بعد توفّر برنامج تنفيذي، من المحتمل أن تحتاج إلى تحديد سياسة Sandbox لبرنامج Sandboxee. بخلاف ذلك، لا تتم حماية Sandboxee إلا من خلال سياسة Syscall التلقائية ‎(Default Syscall Policy).

تهدف "سياسة وضع الحماية" إلى حصر طلبات النظام (syscalls) والوسيطات التي يمكن أن يقدّمها Sandboxee، بالإضافة إلى الملفات التي يمكنه الوصول إليها. يجب أن يكون لديك فهم تفصيلي لطلبات النظام المطلوبة من الرمز الذي تخطّط لوضعه في بيئة معزولة. تتمثل إحدى طرق مراقبة طلبات النظام في تشغيل الرمز باستخدام أداة سطر الأوامر strace في Linux.

بعد الحصول على قائمة باستدعاءات النظام، يمكنك استخدام PolicyBuilder لتحديد السياسة. تتضمّن PolicyBuilder العديد من الوظائف المساعدة والمريحة التي تتيح تنفيذ العديد من العمليات الشائعة. القائمة التالية هي مجرد مقتطف صغير من الدوال المتاحة:

  • إدراج أي استدعاء نظام في القائمة المسموح بها لبدء العملية:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • إضافة أي استدعاءات نظام مفتوحة/قراءة/كتابة* إلى القائمة المسموح بها:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • إضافة أي استدعاءات نظام مرتبطة بالخروج أو الوصول أو الحالة إلى القائمة المسموح بها:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • إضافة أي طلبات نظام ذات صلة بالنوم/الوقت إلى القائمة المسموح بها:
    • AllowTime();
    • AllowSleep();

تسمح وظائف التيسير هذه بإدراج أي استدعاء نظام ذي صلة في القائمة المسموح بها. ويتميّز ذلك بميزة إمكانية استخدام السياسة نفسها في بنى مختلفة لا تتوفّر فيها بعض استدعاءات النظام (مثل ARM64 التي لا تتضمّن استدعاء نظام OPEN)، ولكن مع خطر أمني بسيط يتمثّل في تفعيل عدد أكبر من استدعاءات النظام التي قد تكون ضرورية. على سبيل المثال، يتيح AllowOpen() لـ Sandboxee استدعاء أي نظام syscall مفتوح ذي صلة. إذا أردت إدراج استدعاء نظام واحد فقط في القائمة المسموح بها، يمكنك استخدام AllowSyscall();. أما إذا أردت إدراج استدعاءات نظام متعددة في القائمة المسموح بها في آنٍ واحد، فيمكنك استخدام AllowSyscalls().

حتى الآن، تتحقّق السياسة من معرّف استدعاء النظام فقط. إذا كنت بحاجة إلى تعزيز السياسة بشكل أكبر وأردت تحديد سياسة لا تسمح إلا باستدعاء نظامي مع وسيطات معيّنة، عليك استخدام AddPolicyOnSyscall() أو AddPolicyOnSyscalls(). لا تقبل هذه الدوال معرّف طلب النظام كوسيط فحسب، بل تقبل أيضًا فلتر seccomp-bpf أوليًا باستخدام وحدات ماكرو مساعدة bpf من نواة Linux. يمكنك الاطّلاع على مستندات نظام التشغيل للحصول على مزيد من المعلومات حول BPF. إذا كنت تكتب رمز BPF متكررًا تعتقد أنّه يجب أن يتضمّن برنامج تضمين قابل للاستخدام، يمكنك تقديم طلب للحصول على ميزة.

بالإضافة إلى الدوال ذات الصلة باستدعاءات النظام، يوفّر PolicyBuilder أيضًا عددًا من الدوال ذات الصلة بنظام الملفات، مثل AddFile() أو AddDirectory()، لربط ملف أو دليل في وضع الحماية. يمكن استخدام أداة AddTmpfs() المساعدة لإضافة مساحة تخزين مؤقتة للملفات داخل البيئة الافتراضية.

إحدى الدوال المفيدة بشكل خاص هي AddLibrariesForBinary() التي تضيف المكتبات وبرنامج الربط المطلوبَين بواسطة ملف ثنائي.

لا يزال تحديد استدعاءات النظام التي يجب إضافتها إلى القائمة المسموح بها يتطلّب بعض العمل اليدوي. أنشئ سياسة تتضمّن استدعاءات النظام التي تعرف أنّ برنامجك الثنائي يحتاجها، ونفِّذها باستخدام عبء عمل شائع. في حال حدوث انتهاك، أضِف 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();
}