
صندوق رمل لينكس خفيف الوزن ومتعدد الطبقات يجمع بين 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 الفجوة بين 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():
يتطلب 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:
يُولَّد مرشح BPF ديناميكياً: لكل إدخال في القائمة البيضاء، تُصدَر سلسلة قفزات تسمح بالاستدعاء (إذا تطابق استدعاء النظام) أو تصل إلى KILL. تُفحص البنية أولاً (AUDIT_ARCH_X86_64).
يُتحقَّق من المرشح بشكل مستقل عبر اختبار قائم بذاته (tests/seccomp_filter_test.c، نجاح 8/8) ينفّذ fork+execve لحالات اختبار مقابل prctl(PR_SET_SECCOMP) حقيقي دون الحاجة إلى صلاحيات الجذر.
كل عملية تنفيذ تُنتج سجلاً بتنسيق JSON:
{
"schema": "z-jail.audit/v1",
"build_id": "Z-Jail/v1+dev",
"timestamp": 1749000000,
"duration_ns": 8500000,
"executable": "/bin/ls",
"verdict": "DETERMINISTIC",
"exit_code": 0,
"sandbox": {
"seccomp_filter": "whitelist-v1",
"seccomp_whitelist_size": 15,
"seccomp_arg_rules_size": 2,
"namespaces": ["mount","pid","net","ipc","uts"],
"pivot_root": "/var/run/z-jail/roots/default",
"no_new_privs": true,
"capabilities_dropped": true
},
"content_fingerprint": "0e5751c026e543b2e8ab2eb06099daa1..."
}
يُكتب إلى build/audits/<binary-name>.audit.json. إن content_fingerprint هو تجزئة BLAKE2b-256 للملف الثنائي المستهدف، تُحسب بواسطة العملية الأم بعد انتهاء العملية الفرعية.
z_jail --root=<dir> [--seccomp-enforce] [--self-hash=<hex>]
[--quiet] [--verbose] -- <program> [args...]
# Run a static binary with all protections
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# Run with binary integrity verification
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# Quiet mode (no audit JSON)
sudo z_jail --root=./roots --quiet -- bin/program
make # build z_jail (~130 KiB PIE binary)
make install # install to /usr/local/bin + man page
make clean # remove build artifacts
make dist # create release tarball
make check # smoke test (--version + --help)
يُبنى الملف الثنائي كملف تنفيذي مستقل عن الموقع (PIE) مع -fstack-protector-strong و-D_FORTIFY_SOURCE=2 وRELRO كامل و-z now.
make CC=clang CFLAGS="-O3 -march=native" # custom compiler/flags
# seccomp filter logic (8 tests)
tests/build/seccomp_filter_test
# BLAKE2b known-answer test
tests/build/blake2b_known
لا تحتاج هذه إلى صلاحيات الجذر وتُنجز في أقل من 100 ms.
make -C tests setup # build payloads + test roots
sudo bash tests/run_tests.sh # 17 scenarios
يتطلب صلاحيات الجذر لإنشاء مساحات الأسماء. تغطي مجموعة الاختبارات:
قِيس على WSL2 (النواة 6.18.x-microsoft-standard-WSL2، Kali Linux)، 50 عينة لكل أداة، حمولة موحدة (ملف ثنائي ثابت مستقل نصُّه exit_group(0))، مع ضبط الوقت عبر أداة getrusage. راجع docs/BENCHMARKS.md للمنهجية.
| المقياس | القيمة |
|---|---|
| حجم الملف الثنائي | ~73 KiB غير مجرد (~28 KiB مجرد) |
| متوسط زمن استجابة بيئة العزل | 5.85 ± 1.45 ms (فاصل ثقة 95% [5.45, 6.25]) |
| ذروة RSS | 1.62 MiB |
| أسطر الكود (الأساسي) |
في الظروف المختبرة، يمتلك Z-Jail أصغر مجموعة مقيمة بين بيئات العزل الثلاث على مستوى العمليات، وزمن استجابة بين bwrap وnsjail. Bubblewrap هو الأسرع لكنه لا يجري أي ترشيح seccomp افتراضياً، لذا يؤدي عملاً تحضيرياً أقل؛ يثبّت Z-Jail قائمة seccomp بيضاء، ويسقط القدرات، وينفّذ pivot_root في كل تشغيل. يتعطل gVisor (runsc) بخطأ segfault على نواة WSL2 هذه ولم يمكن قياسه؛ يعزل Firecracker عبر microVM (إقلاع بارد للجهاز الافتراضي، وهو مقياس مختلف) ويُستبعد من جدول fork-to-exec. هذه أرقام من مضيف واحد — تعامل معها على أنها نسبية.
ملاحظة: الأرقام الموثقة سابقاً (~8 ms، ~4 MiB، ~130 KiB) لا تطابق بناء
makeحالياً (~73 KiB، ~5.9 ms) ويبدو أنها كانت غير دقيقة؛ أُعيد قياس الأرقام أعلاه على قاعدة الأكواد هذه. ما تزال Truthimatics جزءاً من الكود ولم تُحذف (تقاريرaxiom_jailالقديمة استخدمت ببساطة اسم ملف ثنائي وأداة مختلفة). أُصلح خطأ في انتشار التركيب (mount propagation) اكتُشف أثناء القياس فيsrc/sandbox.c(MS_REC|MS_PRIVATEقبل ربط-التركيب)؛ تساهم إعادة التركيب العودية بجزء من زمن الاستجابة المقاس.
chroot، mount، ptrace، socket، process_vm_writevRLIMIT_CPU)، واستنزاف الذاكرة (RLIMIT_AS)execvesetuid / الموصول الديناميكي / LD_PRELOAD/proc و/sys المشتركةCLONE_NEWNET + حظر socketclone(CLONE_NEWNS|CLONE_NEWPID|...) (يتطلب CAP_SYS_ADMIN)--root)--self-hash=<hex> في عمليات النشر الإنتاجيةMIT — راجع LICENSE للنص الكامل.
بُني Z-Jail على WSL2 (Kali Linux، GCC 15.2.0)، مستهدفاً لينكس 5.4+. تتم الصيانة بواسطة Division-36. أبلغ عن المشكلات في متتبع المشكلات.
| 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 واحد | معقّد | معقّد | بسيط | متوسط |
| مساحة الاسم | العلامة | الغرض |
|---|
| Mount | CLONE_NEWNS | شجرة نظام ملفات معزولة |
| PID | CLONE_NEWPID | فضاء معرفات العمليات (الطفل هو pid 1) |
| Net | CLONE_NEWNET | لا واجهات شبكة |
| IPC | CLONE_NEWIPC | لا ذاكرة مشتركة / إشارات semaphores |
| UTS | CLONE_NEWUTS | اسم مضيف منفصل |
| استدعاء النظام | الرقم | ملاحظات |
|---|
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 | بيانات الملف الوصفية |
| العلامة | الوصف |
|---|
--root=<dir> | دليل جذر بيئة العزل (مطلوب) |
--seccomp-enforce | تفعيل القائمة البيضاء لاستدعاءات النظام seccomp-BPF |
--self-hash=<hex> | التحقق من تطابق الملف الثنائي مع تجزئة BLAKE2b-256 المتوقعة |
--quiet | إخفاء مخرجات التدقيق |
--verbose | تفعيل تسجيل التصحيح |
--version | إظهار معرّف البناء (Z-Jail/v1+dev) |
--help | إظهار الاستخدام والخروج |
| الرمز | المعنى |
|---|
| 0 | خرجت العملية الفرعية بشكل طبيعي (الحكم: DETERMINISTIC) |
| 1 | قُتلت العملية الفرعية بإشارة (الحكم: REJECT) |
| 2 | التجزئة الذاتية: سلسلة hex غير صالحة أو ملف غير قابل للقراءة |
| 3 | التجزئة الذاتية: عدم تطابق (تم العبث بالملف الثنائي) |
| 101 | خطأ في إعداد العملية الفرعية (rlimit، إلخ) |
| 102 | فشل تثبيت مرشح seccomp لدى العملية الفرعية |
| 103 | فشل execve لدى العملية الفرعية (الملف الثنائي غير موجود، لا صلاحية تنفيذ) |
| 104 | فشل pivot_root لدى العملية الفرعية |
| 105 | فشل إسقاط القدرات لدى العملية الفرعية |
| 125 | فشل إنشاء مساحة الأسماء (هل تعمل بصلاحيات الجذر؟ هل يدعم النواة؟) |
| # | السيناريو | النوع | ماذا يختبر |
|---|
| 0 | blake2b_regress | known-answer | صحة تنفيذ BLAKE2b |
| 1 | seccomp_filter | standalone BPF | 8 اختبارات فرعية لمنطق مرشح BPF |
| 2 | hello_static | ok | تنفيذ ملف ثنائي ثابت أساسي |
| 3 | hello_dynamic | ok | ملف ثنائي ديناميكي مع ld-linux + libc |
| 4 | execve_replacement | ok | execve داخل بيئة العزل (محظور بواسطة seccomp) |
| 5 | fd_inherited_read | ok | stdin/stdout موروثان بشكل صحيح |
| 6 | mmap_bad_flags | killed | mmap مع MAP_SHARED محظور |
| 7 | mmap_good_allowed | ok | mmap مع MAP_PRIVATE|ANONYMOUS مسموح |
| 8 | mmap_prot_exec | killed | mmap مع PROT_EXEC محظور |
| 9 | mmap_self_modify | killed | الكود ذاتي التعديل محظور |
| 10 | ptrace | killed | ptrace محظور |
| 11 | socket | killed | إنشاء socket محظور |
| 12 | chroot_escape | killed | استدعاء نظام chroot محظور |
| 13 | double_chroot | killed | chroot المزدوج محظور |
| 14 | mount_replay | killed | استدعاء نظام mount محظور |
| 15 | cpu_exhaust | killed | RLIMIT_NPROC يمنع قنبلة fork |
| 16 | signal_parent | killed | الإشارة إلى العملية الأم محظورة |
| 17 | self_hash | ok | التحقق من سلامة الملف الثنائي |
| ~900 |
| الأداة | متوسط زمن الاستجابة ± الانحراف المعياري | ذروة RSS | seccomp الافتراضي |
|---|
| Z-Jail | 5.85 ± 1.45 ms | 1.62 MiB | نعم |
| bwrap | 3.56 ± 0.40 ms | 2.19 MiB | لا |
| nsjail | 8.98 ± 1.68 ms | 7.91 MiB | نعم |
| الملف | الوصف |
|---|
README.md | هذا الملف |
docs/ARCHITECTURE.md | نظرة عامة على البنية |
docs/SANDBOX.md | دواخل بيئة العزل طبقة بطبقة |
docs/SECCOMP.md | تصميم القائمة البيضاء seccomp-BPF |
docs/AUDIT_SCHEMA.md | مرجع مخطط JSON للتدقيق |
docs/THREAT_MODEL.md | الافتراضات الأمنية والنطاق |
docs/BLAKE2B.md | تفاصيل تنفيذ BLAKE2b |
docs/BENCHMARKS.md | معايير الأداء |
docs/BUILD.md | تعليمات البناء |
docs/adr/ | سجلات القرارات المعمارية (4 مستندات) |
man/z_jail.1 | صفحة الدليل |
SECURITY.md | السياسة الأمنية والإبلاغ |
CONTRIBUTING.md | كيفية المساهمة |
CHANGELOG.md | سجل الإصدارات |
ROADMAP.md | الخطط المستقبلية |
TODO.md | الفجوات المعروفة والعمل المخطط |