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

بعد الحصول على برنامج تنفيذي، من المحتمل أن تحتاج إلى تحديد سياسة Sandbox لبرنامج Sandboxee. وفي الحالات الأخرى، لا تتم حماية Sandboxee إلا من خلال سياسة Syscall التلقائية.

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

بالإضافة إلى الدوال ذات الصلة باستدعاءات النظام، يوفّر PolicyBuilder أيضًا عددًا من الدوال ذات الصلة بنظام الملفات، مثل AddFile() أو AddDirectory()، لربط ملف أو دليل في وضع الحماية. يمكن استخدام دالة المساعدة 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();
}