
صندوق رمل لينكس خفيف الوزن ومتعدد الطبقات يجمع بين namespaces وpivot_root وseccomp-bpf وإسقاط الصلاحيات، ومحرك قرارات قائم على الأدلة (النسخة العامة من Truthimatics) لتنفيذ كود آمن وقابل للتدقيق.
حاجز حماية متعدد الطبقات لتنفيذ الأكواد الأصلية على لينكس.
سبع طبقات دفاع مستقلة — لا تبعيات خارجية، ملف ثنائي PIE بحجم ~73 KiB.
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics PV (evidence-based verdict engine) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (chroot on steroids) │
│ Capabilities (drop all, lock securebits) │
│ NO_NEW_PRIVS (no privilege escalation) │
│ seccomp-BPF (whitelist: 15 syscalls only) │
│ Audit (JSON logging + BLAKE2b hashing) │
└──────────────────────────────────────────────────────┘
git clone https://github.com/Division-36/Z-Jail.git
cd Z-Jail
make
sudo ./z_jail --root=/path/to/rootfs --seccomp-enforce -- /bin/ls
يجب أن يحتوي دليل --root على نظام ملفات أدنى يتضمن الملف الثنائي المستهدف وتبعياته (بالنسبة للملفات الثنائية ثابتة الربط، يكفي الملف الثنائي وحده).
حلول العزل الحالية تنطوي على مقايضات:
| Z-Jail | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| التبعيات الخارجية | صفر | libc, seccomp | Go runtime | libc | libc, protobuf |
| حجم الملف الثنائي | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| عزل الجهاز الافتراضي | لا | نعم (microVM) | لا (بيئة عزل) | لا | لا |
| القائمة البيضاء لـ seccomp | نعم | لا | نعم | اختياري | نعم |
| تجزئة المحتوى | نعم | لا | لا | لا | لا |
| تدقيق JSON | نعم | لا | نعم | لا | جزئي |
| تعقيد البناء | أمر make واحد | معقّد | معقّد | بسيط | متوسط |
يسدّ Z-Jail الفجوة بين bwrap (بسيط، بدون seccomp افتراضياً) وnsjail (غني بالميزات، تبعيات ثقيلة). وهو مصمم لـخطوط CI، وتحديات CTF jail، وتقييم الأكواد خفيف الوزن حيث تحتاج إلى دفاع متعمق دون سحب بيئة تشغيل حاويات.
flowchart LR
CLI[CLI args] --> P[parse_args]
P --> C{clone namespaces}
C -->|child| CR[child_run]
C -->|parent| W[waitpid]
CR --> RL[setrlimit]
RL --> FD[close fds >= 3]
FD --> DUMP[PR_SET_DUMPABLE=0]
DUMP --> PV[pivot_root]
PV --> NNP[PR_SET_NO_NEW_PRIVS]
NNP --> CAP[drop capabilities]
CAP --> SC[seccomp-BPF]
SC --> SIG[signal parent]
SIG --> EX[execve target]
W --> A[audit JSON]
A --> EXIT[exit]
تُرتَّب الطبقات بحيث لا يمكن لطبقة لاحقة أن تُبطَل بواسطة طبقة سابقة:
capset بعد هذه النقطةsequenceDiagram
participant P as Parent
participant C as Child
P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
Note over C: close(all fds > 2)
Note over C: PR_SET_DUMPABLE=0
Note over C: pivot_root → chdir("/") → umount -l
Note over C: PR_SET_NO_NEW_PRIVS
Note over C: capset(all zero) + securebits
Note over C: seccomp(SECCOMP_MODE_FILTER, whitelist)
C->>P: write(pipe, ready=1)
Note over C: execve(target)
P->>P: waitpid
P->>P: write audit JSON
محرك أحكام قائم على الأدلة. يجمع ملاحظات موزونة حول الملف الثنائي المنفَّذ ويحدد حكماً نهائياً (DETERMINISTIC أو REJECT أو UNCERTAIN). تحمل كل ملاحظة وزناً؛ أي ملاحظة واحدة بوزن أكبر من 50% من الإجمالي تحسم الحكم.
تُنشأ خمس مساحات أسماء عبر clone():
| مساحة الاسم | العلامة | الغرض |
|---|---|---|
| Mount | CLONE_NEWNS | شجرة نظام ملفات معزولة |
| PID | CLONE_NEWPID | فضاء معرفات العمليات (الطفل هو pid 1) |
| Net | CLONE_NEWNET | لا واجهات شبكة |
| IPC | CLONE_NEWIPC | لا ذاكرة مشتركة / إشارات semaphores |
| UTS | CLONE_NEWUTS | اسم مضيف منفصل |
يتطلب CAP_SYS_ADMIN في مساحة الأسماء الأولية.
يستبدل جذر مساحة أسماء التركيب بدليل --root:
MS_BIND|MS_REC)pivot_root(new_root, put_old) — مبادلة شجرة التركيبchdir("/") — الانتقال إلى الجذر الجديدumount2("/.pivot_old", MNT_DETACH) — فصل الجذر القديمrmdir("/.pivot_old") — التنظيفهذا أقوى بصرامة من chroot(2) — لا توجد طريقة للعملية المعزولة للهروب عائدة إلى جذر المضيف، حتى مع CLONE_NEWNS من داخل بيئة العزل (وهو محظور أصلاً بواسطة seccomp).
تُسقَط جميع القدرات عبر:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
تُسقط العملية setuid/setgid قبل capset بحيث يسري تغيير uid بينما ما تزال CAP_SETUID محتفظاً بها. بعد capset، تختفي جميع القدرات ويُقفل securebits — لا يمكن إعادة التمكين.
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
يمنع العملية أو عملياتها الفرعية من اكتساب امتيازات جديدة عبر الملفات الثنائية setuid، أو قدرات الملفات، أو انتقالات LSM. لا رجعة فيه.
قائمة سماح من 15 استدعاء نظام — أي شيء غير موجود في القائمة يحصل على SECCOMP_RET_KILL:
| استدعاء النظام | الرقم | ملاحظات |
|---|---|---|
read | 0 | stdin |
write | 1 | stdout/stderr + أنبوب التقرير |
openat | 257 | الوصول إلى الملفات (وليس open) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | إدارة الكومة |
mmap | 9 | مقيد بالوسائط: flags & 4 == 0 (بدون MAP_SHARED)، flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | تنفيذ واحد عند الإقلاع |
exit_group | 231 | خروج نظيف للعملية |
rt_sigaction | 13 | معالجات الإشارات |
rt_sigprocmask | 14 | حجب الإشارات |
getrandom | 318 | مصدر أرقام عشوائية |
clock_gettime | 228 | التوقيت |
fstat | 5 | بيانات الملف الوصفية |
يُولَّد مرشح BPF ديناميكياً: لكل إدخال في القائمة البيضاء، تُصدَر سلسلة قفزات تسمح بالاستدعاء (إذا تطابق استدعاء النظام) أو تصل إلى KILL. تُفحص البنية أولاً (AUDIT_ARCH_X86_64).
يُتحقَّق من المرشح بشكل مستقل عبر اختبار قائم بذاته (tests/seccomp_filter_test.c، نجاح 8/8) ينفّذ fork+execve لحالات اختبار مقابل prctl(PR_SET_SECCOMP) حقيقي دون الحاجة إلى صلاحيات الجذر.