seccomp(2)

الاسم
seccomp - العمل على حالة الحوسبة الآمنة للعملية

المكتبة
مكتبة سي المعيارية (libc، -lc)

موجز
#include <linux/seccomp.h>  /* تعريف ثوابت SECCOMP_* */
#include <linux/filter.h>   /* تعريف struct sock_fprog */
#include <linux/audit.h>    /* تعريف ثوابت AUDIT_* */
#include <linux/signal.h>   /* تعريف ثوابت SIG* */
#include <sys/ptrace.h>     /* تعريف ثوابت PTRACE_* */
#include <sys/syscall.h>    /* تعريف ثوابت SYS_* */
#include <unistd.h>

int syscall(SYS_seccomp, unsigned int operation, unsigned int flags,
            void *args);

ملاحظة: لا توفر glibc غلافًا للدالة seccomp()، مما يستلزم استخدام syscall(2).

الوصف
يعمل استدعاء النظام seccomp() على حالة الحوسبة الآمنة (seccomp) للعملية المستدعِيَة.

يدعم لينكس حاليًا قيم operation التالية:

SECCOMP_SET_MODE_STRICT
    استدعاءات النظام الوحيدة التي يُسمح للخيط المستدعِي بإجرائها هي read(2)، وwrite(2)، و_exit(2) (وليس exit_group(2))، وsigreturn(2). تؤدي استدعاءات النظام الأخرى إلى إنهاء الخيط المستدعِي، أو إنهاء العملية بأكملها بإشارة SIGKILL عندما لا يوجد سوى خيط واحد. يُعد وضع الحوسبة الآمنة الصارم مفيدًا للتطبيقات كثيفة الحسابات التي قد تحتاج إلى تنفيذ شيفرة بايت غير موثوقة، ربما استُحصل عليها بالقراءة من أنبوب أو مقبس.

    لاحظ أنه على الرغم من أن الخيط المستدعِي لم يعد بإمكانه استدعاء sigprocmask(2)، إلا أنه يمكنه استخدام sigreturn(2) لحجب جميع الإشارات باستثناء SIGKILL وSIGSTOP. وهذا يعني أن alarm(2) (على سبيل المثال) ليس كافيًا لتقييد وقت تنفيذ العملية. وبدلاً من ذلك، ولإنهاء العملية بشكل موثوق، يجب استخدام SIGKILL. يمكن القيام بذلك باستخدام timer_create(2) مع ضبط SIGEV_SIGNAL وsigev_signo على SIGKILL، أو باستخدام setrlimit(2) لضبط الحد الصارم لـ RLIMIT_CPU.

    تتوفر هذه العملية فقط إذا تم ضبط النواة مع تمكين CONFIG_SECCOMP.

    يجب أن تكون قيمة flags تساوي 0، ويجب أن يكون args هو NULL.

    هذه العملية متطابقة وظيفيًا مع الاستدعاء:

        prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);

SECCOMP_SET_MODE_FILTER
    تُحدد استدعاءات النظام المسموح بها بواسطة مؤشر إلى مرشح حزم بيركلي (BPF) يُمرر عبر args. هذا المعامل هو مؤشر إلى struct sock_fprog؛ يمكن تصميمه لترشيح استدعاءات نظام ومعاملات استدعاء نظام عشوائية. إذا كان المرشح غير صالح، يفشل seccomp()، ويعيد EINVAL في errno.

    إذا سمح المرشح بـ fork(2) أو clone(2)، فستُقيد أي عمليات وليدة بنفس مرشحات استدعاء النظام الخاصة بالأب. إذا سُمح بـ execve(2)، فستُحفظ المرشحات الحالية عبر استدعاء execve(2).

    من أجل استخدام عملية SECCOMP_SET_MODE_FILTER، إما أن يمتلك الخيط المستدعِي صلاحية CAP_SYS_ADMIN في مساحة اسم المستخدم الخاصة به، أو يجب أن يكون بت no_new_privs مضبوطًا بالفعل لدى الخيط. إذا لم يكن هذا البت مضبوطًا بالفعل من قِبل سلف لهذا الخيط، فيجب على الخيط إجراء الاستدعاء التالي:

        prctl(PR_SET_NO_NEW_PRIVS, 1);

    خلاف ذلك، تفشل عملية SECCOMP_SET_MODE_FILTER وتُعيد EACCES في errno. يضمن هذا المتطلب عدم تمكن عملية غير مميزة من تطبيق مرشح خبيث ثم استدعاء برنامج bit-set-user-ID أو أي برنامج مميز آخر باستخدام execve(2)، مما قد يؤدي إلى اختراق ذلك البرنامج. (قد يتسبب مثل هذا المرشح الخبيث، على سبيل المثال، في جعل محاولة استخدام setuid(2) لضبط معرفات مستخدم المستدعِي على قيم غير صفرية تُعيد القيمة 0 بدلاً من ذلك دون إجراء استدعاء النظام فعليًا. وبالتالي، قد يُخدع البرنامج للاحتفاظ بامتيازات المستخدم الخارق في ظروف يمكن فيها التأثير عليه للقيام بأشياء خطيرة لأنه لم يتخلَ فعليًا عن الامتيازات).

    إذا سُمح بـ prctl(2) أو seccomp() بواسطة المرشح الملحق، فيمكن إضافة المزيد من المرشحات. سيؤدي هذا إلى زيادة وقت التقييم، ولكنه يسمح بمزيد من تقليل سطح الهجوم أثناء تنفيذ الخيط.

    تتوفر عملية SECCOMP_SET_MODE_FILTER فقط إذا ضُبطت النواة مع تمكين CONFIG_SECCOMP_FILTER.

    عندما يكون flags مساويًا لـ 0، تكون هذه العملية متطابقة وظيفيًا مع الاستدعاء:

        prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);

    الرايات (flags) المعروفة هي:

    SECCOMP_FILTER_FLAG_LOG (منذ لينكس 4.14)
        ينبغي تسجيل جميع إجراءات إرجاع المرشح باستثناء SECCOMP_RET_ALLOW. يمكن للمسؤول تجاوز راية المرشح هذه عن طريق منع تسجيل إجراءات معينة عبر ملف /proc/sys/kernel/seccomp/actions_logged.

    SECCOMP_FILTER_FLAG_NEW_LISTENER (منذ لينكس 5.0)
        بعد تثبيت برنامج المرشح بنجاح، يُعاد واصف ملف إشعار جديد في مساحة المستخدم. (تُضبط راية الإغلاق عند التنفيذ close-on-exec لواصف الملف). عندما يُعيد المرشح SECCOMP_RET_USER_NOTIF، سيُرسل إشعار إلى واصف الملف هذا.

        يمكن تثبيت مرشح seccomp واحد كحد أقصى باستخدام راية SECCOMP_FILTER_FLAG_NEW_LISTENER لكل خيط.

        انظر seccomp_unotify(2) لمزيد من التفاصيل.

    SECCOMP_FILTER_FLAG_SPEC_ALLOW (منذ لينكس 4.17)
        عطّل تخفيف تجاوز التخزين التخميني (Speculative Store Bypass).

    SECCOMP_FILTER_FLAG_TSYNC
        عند إضافة مرشح جديد، قم بمزامنة جميع الخيوط الأخرى للعملية المستدعِيَة مع شجرة مرشحات seccomp نفسها. "شجرة المرشحات" هي القائمة المرتبة للمرشحات الملحقة بالخيط. (إلحاق مرشحات متطابقة في استدعاءات seccomp() منفصلة يؤدي إلى مرشحات مختلفة من هذا المنظور).

        إذا تعذر على أي خيط المزامنة مع نفس شجرة المرشحات، فلن يقوم الاستدعاء بإلحاق مرشح seccomp الجديد، وسيفشل، مع إرجاع أول معرف خيط عُثر عليه لا يمكنه المزامنة. ستفشل المزامنة إذا كان خيط آخر في نفس العملية في وضع SECCOMP_MODE_STRICT أو إذا ألحق مرشحات seccomp جديدة بنفسه، مما يؤدي إلى تباعده عن شجرة مرشحات الخيط المستدعِي.

SECCOMP_GET_ACTION_AVAIL (منذ لينكس 4.14)
    اختبر لترى ما إذا كان الإجراء مدعومًا من قبل النواة. هذه العملية مفيدة للتأكد من أن النواة تعرف إجراء إرجاع مرشح أضيف حديثًا، لأن النواة تعامل جميع الإجراءات المجهولة على أنها SECCOMP_RET_KILL_PROCESS.

    يجب أن تكون قيمة flags تساوي 0، ويجب أن يكون args مؤشرًا إلى إجراء إرجاع مرشح 32-بت غير موقع.

SECCOMP_GET_NOTIF_SIZES (منذ لينكس 5.0)
    احصل على أحجام هياكل إشعارات seccomp في مساحة المستخدم. وبما أن هذه الهياكل قد تتطور وتكبر بمرور الوقت، يمكن استخدام هذا الأمر لتحديد مقدار الذاكرة التي يجب تخصيصها لإرسال واستلام الإشعارات.

    يجب أن تكون قيمة flags تساوي 0، ويجب أن يكون args مؤشرًا إلى struct seccomp_notif_sizes، الذي له الشكل التالي:

        struct seccomp_notif_sizes
            __u16 seccomp_notif;      /* حجم هيكل الإشعار */
            __u16 seccomp_notif_resp; /* حجم هيكل الاستجابة */
            __u16 seccomp_data;       /* حجم 'struct seccomp_data' */
        };

    انظر seccomp_unotify(2) لمزيد من التفاصيل.

  المرشحات
عند إضافة مرشحات عبر SECCOMP_SET_MODE_FILTER، يشير args إلى برنامج مرشح:

    struct sock_fprog {
        unsigned short      len;    /* عدد تعليمات BPF */
        struct sock_filter *filter; /* مؤشر إلى مصفوفة من
                                       تعليمات BPF */
    };

يجب أن يحتوي كل برنامج على تعليمة BPF واحدة أو أكثر:

    struct sock_filter {            /* كتلة المرشح */
        __u16 code;                 /* شيفرة المرشح الفعلية */
        __u8  jt;                   /* قفز في حال الصحة */
        __u8  jf;                   /* قفز في حال الخطأ */
        __u32 k;                    /* حقل عام متعدد الاستخدامات */
    };

عند تنفيذ التعليمات، يعمل برنامج BPF على معلومات استدعاء النظام المتاحة (أي باستخدام وضع العنونة BPF_ABS) كدرء (للقراءة فقط) بالشكل التالي:

    struct seccomp_data {
        int   nr;                   /* رقم استدعاء النظام */
        __u32 arch;                 /* قيمة AUDIT_ARCH_*
                                       (انظر <linux/audit.h>) */
        __u64 instruction_pointer;  /* مؤشر تعليمات وحدة المعالجة المركزية */
        __u64 args[6];              /* ما يصل إلى 6 معاملات لاستدعاء النظام */
    };

نظرًا لأن ترقيم استدعاءات النظام يختلف بين البنيات المعمارية، وتسمح بعض البنيات (مثل x86-64) لشفرة مساحة المستخدم باستخدام اصطلاحات الاستدعاء لبنيات متعددة (وقد يختلف الاصطلاح المستخدم خلال عمر العملية التي تستخدم execve(2) لتنفيذ ملفات ثنائية تستخدم اصطلاحات مختلفة)، فمن الضروري عادةً التحقق من قيمة حقل arch.

يوصى بشدة باستخدام نهج "قائمة السماح" كلما أمكن ذلك لأن مثل هذا النهج أكثر قوة وبساطة. سيتعين تحديث "قائمة المنع" كلما أُضيف استدعاء نظام يحتمل أن يكون خطيرًا (أو راية أو خيار خطير إذا كانت هذه مدرجة في قائمة المنع)، وغالبًا ما يكون من الممكن تغيير تمثيل القيمة دون تغيير معناها، مما يؤدي إلى تجاوز قائمة المنع. انظر أيضًا Caveats أدناه.

حقل arch ليس فريدًا لجميع اصطلاحات الاستدعاء. يستخدم كل من x86-64 ABI وx32 ABI القيمة AUDIT_ARCH_X86_64 كـ arch، ويعملان على نفس المعالجات. وبدلاً من ذلك، يُستخدم القناع __X32_SYSCALL_BIT في رقم استدعاء النظام للتمييز بين واجهتي التطبيق الثنائية (ABIs).

هذا يعني أن السياسة يجب أن تمنع جميع استدعاءات النظام مع __X32_SYSCALL_BIT أو يجب أن تتعرف على الاستدعاءات مع ضبط __X32_SYSCALL_BIT وبدونه. قائمة استدعاءات النظام التي سيتم منعها بناءً على nr والتي لا تحتوي أيضًا على قيم nr مع ضبط __X32_SYSCALL_BIT يمكن تجاوزها بواسطة برنامج خبيث يضبط __X32_SYSCALL_BIT.

بالإضافة إلى ذلك، سمحت النوى السابقة للينكس 5.4 بشكل غير صحيح بـ nr في النطاقات 512-547 بالإضافة إلى استدعاءات النظام المقابلة لغير x32 التي أُجريت عليها عملية OR مع __X32_SYSCALL_BIT. على سبيل المثال، nr == 521 و nr == (101 | __X32_SYSCALL_BIT) سيؤديان إلى استدعاءات ptrace(2) مع دلالات x32 مقابل x86_64 قد تكون مشوشة في النواة. السياسات المخصصة للعمل على نوى قبل لينكس 5.4 يجب أن تضمن منع هذه الاستدعاءات أو التعامل معها بشكل صحيح. في لينكس 5.4 والأحدث، ستفشل استدعاءات النظام هذه مع الخطأ ENOSYS، دون القيام بأي شيء.

يوفر حقل instruction_pointer عنوان تعليمة لغة الآلة التي أجرت استدعاء النظام. قد يكون هذا مفيدًا بالاقتران مع استخدام /proc/pid/maps لإجراء فحوصات بناءً على أي منطقة (تخطيط) من البرنامج أجرت استدعاء النظام. (ربما من الحكمة قفل استدعاءات النظام mmap(2) وmprotect(2) لمنع البرنامج من تخريب مثل هذه الفحوصات).

عند فحص القيم من args، ضع في اعتبارك أن المعاملات غالبًا ما تُبتر بصمت قبل معالجتها، ولكن بعد فحص seccomp. على سبيل المثال، يحدث هذا إذا استخدم i386 ABI على نواة x86-64: على الرغم من أن النواة لن تنظر عادةً إلى ما بعد أدنى 32 بت من المعاملات، إلا أن قيم سجلات 64 بت الكاملة ستكون موجودة في بيانات seccomp. مثال أقل إثارة للدهشة هو أنه إذا استُخدم x86-64 ABI لإجراء استدعاء نظام يأخذ معاملًا من نوع int، فإن النصف الأكثر أهمية من سجل المعامل يُتجاهل بواسطة استدعاء النظام، ولكنه يكون مرئيًا في بيانات seccomp.

يُعيد مرشح seccomp قيمة 32-بت تتكون من جزأين: أهم 16 بتًا (المقابلة للقناع المحدد بالثابت SECCOMP_RET_ACTION_FULL) تحتوي على إحدى قيم "الإجراء" المدرجة أدناه؛ وأدنى 16 بتًا (المحددة بالثابت SECCOMP_RET_DATA) هي "بيانات" ترتبط بقيمة الإرجاع هذه.

في حال وجود مرشحات متعددة، فإنه يتم تنفيذها جميعها، بترتيب عكسي لإضافتها إلى شجرة المرشحات؛ أي أن المرشح المثبت مؤخرًا يُنفذ أولاً. (لاحظ أنه سيتم استدعاء جميع المرشحات حتى لو أعاد أحد المرشحات السابقة SECCOMP_RET_KILL. يتم ذلك لتبسيط كود النواة ولتوفير تسريع بسيط في تنفيذ مجموعات المرشحات عن طريق تجنب الفحص لهذه الحالة غير الشائعة). قيمة الإرجاع لتقييم استدعاء نظام معين هي أول قيمة إجراء مرئية ذات أعلى أسبقية (إلى جانب البيانات المصاحبة لها) تُرجعها عملية تنفيذ جميع المرشحات.

بترتيب تنازلي للأسبقية، قيم الإجراءات التي قد يُرجعها مرشح seccomp هي:

SECCOMP_RET_KILL_PROCESS (منذ لينكس 4.14)
    تؤدي هذه القيمة إلى إنهاء فوري للعملية، مع تفريغ الذاكرة (core dump). لا يُنفذ استدعاء النظام. وعلى النقيض من SECCOMP_RET_KILL_THREAD أدناه، تُنهى جميع الخيوط في مجموعة الخيوط. (لمناقشة مجموعات الخيوط، انظر وصف راية CLONE_THREAD في clone(2)).

    تُنتهى العملية كما لو قُتلت بإشارة SIGSYS. حتى لو سُجل معالج إشارة لـ SIGSYS، فسيتم تجاهل المعالج في هذه الحالة وتنتهي العملية دائمًا. بالنسبة لعملية أب تنتظر هذه العملية (باستخدام waitpid(2) أو ما شابه)، فإن wstatus المُرجع سيشير إلى أن وليدها قد أُنهي كما لو كان بإشارة SIGSYS.

SECCOMP_RET_KILL_THREAD (أو SECCOMP_RET_KILL)
    تؤدي هذه القيمة إلى إنهاء فوري للخيط الذي أجرى استدعاء النظام. لا يُنفذ استدعاء النظام. ستستمر الخيوط الأخرى في نفس مجموعة الخيوط في التنفيذ.

    ينتهي الخيط كما لو قُتل بإشارة SIGSYS. انظر SECCOMP_RET_KILL_PROCESS أعلاه.

    قبل لينكس 4.11، لم تكن أي عملية أُنهيت بهذه الطريقة لتؤدي إلى تفريغ الذاكرة (على الرغم من أن SIGSYS موثق في signal(7) على أنه يمتلك إجراءً مبدئيًا بالإنهاء مع تفريغ الذاكرة). منذ لينكس 4.11، ستقوم العملية أحادية الخيط بتفريغ الذاكرة إذا أُنهيت بهذه الطريقة.

    مع إضافة SECCOMP_RET_KILL_PROCESS في لينكس 4.14، أُضيف SECCOMP_RET_KILL_THREAD كمرادف لـ SECCOMP_RET_KILL، من أجل التمييز بوضوح أكبر بين الإجراءين.

    ملاحظة: من المحتمل أن يؤدي استخدام SECCOMP_RET_KILL_THREAD لقتل خيط واحد في عملية متعددة الخيوط إلى ترك العملية في حالة غير متسقة بشكل دائم وربما تالفة.

SECCOMP_RET_TRAP
    تؤدي هذه القيمة إلى قيام النواة بإرسال إشارة SIGSYS موجهة للخيط إلى الخيط المسبب. (لا يُنفذ استدعاء النظام). ستُضبط حقول مختلفة في هيكل siginfo_t (انظر sigaction(2)) المرتبط بالإشارة:

    •   سيحتوي si_signo على SIGSYS.

    •   سيعرض si_call_addr عنوان تعليمة استدعاء النظام.

    •   سيوضح si_syscall و si_arch استدعاء النظام الذي جرت محاولته.

    •   سيحتوي si_code على SYS_SECCOMP.

    •   سيحتوي si_errno على جزء SECCOMP_RET_DATA من قيمة إرجاع المرشح.

    سيكون عداد البرنامج كما لو أن استدعاء النظام قد حدث (أي أن عداد البرنامج لن يشير إلى تعليمة استدعاء النظام). سيحتوي سجل قيمة الإرجاع على قيمة تعتمد على البنية المعمارية؛ إذا استؤنف التنفيذ، فاصبطه على شيء مناسب لاستدعاء النظام. (الاعتماد على البنية هو لأن استبداله بـ ENOSYS قد يكتب فوق بعض المعلومات المفيدة).

SECCOMP_RET_ERRNO
    تؤدي هذه القيمة إلى تمرير جزء SECCOMP_RET_DATA من قيمة إرجاع المرشح إلى مساحة المستخدم كقيمة errno دون تنفيذ استدعاء النظام.

SECCOMP_RET_USER_NOTIF (منذ لينكس 5.0)
    حول استدعاء النظام إلى عملية مشرفة ملحقة في مساحة المستخدم للسماح لتلك العملية بتقرير ما يجب فعله باستدعاء النظام. إذا لم يكن هناك مشرف ملحق (إما لأن المرشح لم يُثبت مع راية SECCOMP_FILTER_FLAG_NEW_LISTENER أو لأن واصف الملف أُغلق)، يُرجع المرشح ENOSYS (على غرار ما يحدث عندما يُرجع المرشح SECCOMP_RET_TRACE ولا يوجد متتبع). انظر seccomp_unotify(2) لمزيد من التفاصيل.

    لاحظ أنه لن يتم إخطار عملية المشرف إذا أعاد مرشح آخر قيمة إجراء بأسبقية أعلى من SECCOMP_RET_USER_NOTIF.

SECCOMP_RET_TRACE
    عند إرجاعها، ستتسبب هذه القيمة في قيام النواة بمحاولة إخطار متتبع يعتمد على ptrace(2) قبل تنفيذ استدعاء النظام. إذا لم يكن هناك متتبع موجود، فلن يُنفذ استدعاء النظام ويُرجع حالة فشل مع ضبط errno على ENOSYS.

    سيُخطر المتتبع إذا طلب PTRACE_O_TRACESECCOMP باستخدام ptrace(PTRACE_SETOPTIONS). سيُخطر المتتبع بـ PTRACE_EVENT_SECCOMP وسيكون جزء SECCOMP_RET_DATA من قيمة إرجاع المرشح متاحًا للمتتبع عبر PTRACE_GETEVENTMSG.

    يمكن للمتتبع تخطي استدعاء النظام عن طريق تغيير رقم استدعاء النظام إلى -1. وبدلاً من ذلك، يمكن للمتتبع تغيير استدعاء النظام المطلوب عن طريق تغيير استدعاء النظام إلى رقم استدعاء نظام صالح. إذا طلب المتتبع تخطي استدعاء النظام، فسيظهر استدعاء النظام وكأنه يُرجع القيمة التي يضعها المتتبع في سجل قيمة الإرجاع.

    قبل لينكس 4.8، لن يُشغل فحص seccomp مرة أخرى بعد إخطار المتتبع. (وهذا يعني أنه في النوى القديمة، يجب ألا تسمح البيئات المعزولة القائمة على seccomp باستخدام ptrace(2) - حتى للعمليات المعزولة الأخرى - بدون عناية فائقة؛ إذ يمكن للمتتبعين استخدام هذه الآلية للهروب من بيئة seccomp المعزولة).

    لاحظ أنه لن يتم إخطار عملية المتتبع إذا أعاد مرشح آخر قيمة إجراء بأسبقية أعلى من SECCOMP_RET_TRACE.

SECCOMP_RET_LOG (منذ لينكس 4.14)
    تؤدي هذه القيمة إلى تنفيذ استدعاء النظام بعد تسجيل إجراء إرجاع المرشح. يمكن للمسؤول تجاوز تسجيل هذا الإجراء عبر ملف /proc/sys/kernel/seccomp/actions_logged.

SECCOMP_RET_ALLOW
    تؤدي هذه القيمة إلى تنفيذ نداء النظام.

إذا حُددت قيمة إجراء بخلاف القيم المذكورة أعلاه، فسيُعامل إجراء المرشح إما كـ SECCOMP_RET_KILL_PROCESS (منذ لينكس 4.14) أو SECCOMP_RET_KILL_THREAD (في لينكس 4.13 وما قبله).

  واجهات /proc
توفر الملفات الموجودة في الدليل /proc/sys/kernel/seccomp معلومات وضبط seccomp إضافي:

actions_avail (منذ لينكس 4.14)
    قائمة مرتبة للقراءة فقط لإجراءات إرجاع مرشح seccomp في شكل سلسلة نصية. الترتيب، من اليسار إلى اليمين، هو بترتيب تنازلي للأسبقية. تمثل القائمة مجموعة إجراءات إرجاع مرشح seccomp التي تدعمها النواة.

actions_logged (منذ لينكس 4.14)
    قائمة مرتبة للقراءة والكتابة لإجراءات إرجاع مرشح seccomp المسموح بتسجيلها. لا يلزم أن تكون عمليات الكتابة في الملف مرتبة، ولكن عمليات القراءة من الملف ستكون مرتبة بنفس طريقة ملف actions_avail.

    من المهم ملاحظة أن قيمة actions_logged لا تمنع تسجيل إجراءات إرجاع مرشح معينة عندما يُضبط النظام الفرعي للتدقيق لتدقيق مهمة ما. إذا لم يُعثر على الإجراء في ملف actions_logged، فإن القرار النهائي بشأن تدقيق الإجراء لتلك المهمة يُترك في النهاية للنظام الفرعي للتدقيق ليقرر لجميع إجراءات إرجاع المرشح بخلاف SECCOMP_RET_ALLOW.

    لا تُقبل السلسلة "allow" في ملف actions_logged لأنه لا يمكن تسجيل إجراءات SECCOMP_RET_ALLOW. ستفشل محاولة كتابة "allow" في الملف مع الخطأ EINVAL.

  تسجيل التدقيق لإجراءات seccomp
منذ لينكس 4.14، توفر النواة وسيلة لتسجيل الإجراءات التي تعيدها مرشحات seccomp في سجل التدقيق. تتخذ النواة قرار تسجيل إجراء بناءً على نوع الإجراء، وما إذا كان الإجراء موجودًا في ملف actions_logged، وما إذا كان تدقيق النواة مفعلًا (على سبيل المثال، عبر خيار إقلاع النواة audit=1). القواعد هي كما يلي:

•   إذا كان الإجراء SECCOMP_RET_ALLOW، فلا يُسجل الإجراء.

•   وإلا، إذا كان الإجراء إما SECCOMP_RET_KILL_PROCESS أو SECCOMP_RET_KILL_THREAD، وظهر هذا الإجراء في ملف actions_logged، فيُسجل الإجراء.

•   وإلا، إذا طلب المرشح التسجيل (راية SECCOMP_FILTER_FLAG_LOG) وظهر الإجراء في ملف actions_logged، فيُسجل الإجراء.

•   وإلا، إذا كان تدقيق النواة مفعلًا وكانت العملية تخضع للتدقيق (autrace(8))، فيُسجل الإجراء.

•   خلاف ذلك، لا يُسجل الإجراء.

قيمة الإرجاع
عند النجاح، يعيد seccomp() القيمة 0. عند الخطأ، إذا استُخدمت SECCOMP_FILTER_FLAG_TSYNC، فإن القيمة المعادة هي معرف الخيط (thread) الذي تسبب في فشل المزامنة. (هذا المعرف هو معرف خيط نواة من النوع الذي يعيده clone(2) و gettid(2)). في الأخطاء الأخرى، تُعاد -1، ويُضبط errno للإشارة إلى الخطأ.

الأخطاء
يمكن أن يفشل seccomp() للأسباب التالية:

EACCES
    لم يمتلك المستدعِي قدرة CAP_SYS_ADMIN في مساحة أسماء المستخدم الخاصة به، أو لم يضبط no_new_privs قبل استخدام SECCOMP_SET_MODE_FILTER.

EBUSY
    أثناء تثبيت مرشح جديد، حُددت الراية SECCOMP_FILTER_FLAG_NEW_LISTENER، ولكن ثُبِّت مرشح سابق بالفعل مع هذه الراية.

EFAULT
    ‏args لم يكن عنوانًا صالحًا.

EINVAL
    العملية operation غير معروفة أو غير مدعومة من قِبل إصدار النواة هذا أو الضبط.

EINVAL
    الرايات flags المحددة غير صالحة للعملية operation المعطاة.

EINVAL
    تضمنت العملية operation القيمة BPF_ABS، ولكن الإزاحة المحددة لم تكن محاذية لحد 32 بت أو تجاوزت sizeof(struct seccomp_data).

EINVAL
    وُضع وضع حوسبة آمن بالفعل، وتختلف العملية operation عن الإعداد الحالي.

EINVAL
    حددت العملية operation القيمة SECCOMP_SET_MODE_FILTER، ولكن برنامج المرشح الذي تشير إليه args لم يكن صالحًا أو كان طول برنامج المرشح صفرًا أو تجاوز BPF_MAXINSNS (4096) تعليمة.

ENOMEM
    نفدت الذاكرة.

ENOMEM
    تجاوز الطول الإجمالي لجميع برامج المرشحات الملحقة بخيط الاستدعاء الحد MAX_INSNS_PER_PATH (32768) تعليمة. لاحظ أنه لأغراض حساب هذا الحد، يتحمل كل برنامج مرشح موجود مسبقًا عقوبة إضافية قدرها 4 تعليمات.

EOPNOTSUPP
    حددت العملية operation القيمة SECCOMP_GET_ACTION_AVAIL، ولكن النواة لا تدعم إجراء إرجاع المرشح المحدد بواسطة args.

ESRCH
    تسبب خيط آخر في حدوث فشل أثناء مزامنة الخيوط، ولكن لم يمكن تحديد معرفه.

المعايير
لينكس.

التاريخ
لينكس 3.17.

ملاحظات
بدلاً من ترميز مرشحات seccomp يدويًا كما هو موضح في المثال أدناه، قد تفضل استخدام مكتبة libseccomp، التي توفر واجهة برمجية لتوليد مرشحات seccomp.

يوفر الحقل Seccomp في ملف /proc/pid/status طريقة لعرض وضع seccomp لعملية ما؛ انظر proc(5).

يوفر seccomp() مجموعة موسعة من الوظائف التي توفرها عملية prctl(2) PR_SET_SECCOMP (التي لا تدعم الرايات flags).

منذ لينكس 4.4، يمكن استخدام عملية ptrace(2) PTRACE_SECCOMP_GET_FILTER لتفريغ مرشحات seccomp الخاصة بالعملية.

  دعم المعماريات لمرشح seccomp BPF
يتوفر دعم المعمارية لترشيح seccomp BPF على المعماريات التالية:

•   x86-64, i386, x32 (منذ لينكس 3.5)
•   ARM (منذ لينكس 3.8)
•   s390 (منذ لينكس 3.8)
•   MIPS (منذ لينكس 3.16)
•   ARM-64 (منذ لينكس 3.19)
•   PowerPC (منذ لينكس 4.3)
•   Tile (منذ لينكس 4.3)
•   PA-RISC (منذ لينكس 4.6)

  تحذيرات
هناك تفاصيل دقيقة متنوعة يجب مراعاتها عند تطبيق مرشحات seccomp على برنامج، بما في ذلك ما يلي:

•   بعض نداءات النظام التقليدية لها تطبيقات في مساحة المستخدم في vdso(7) على العديد من المعماريات. تشمل الأمثلة البارزة clock_gettime(2) و gettimeofday(2) و time(2). في مثل هذه المعماريات، لن يكون لترشيح seccomp لهذه النداءات أي تأثير. (ومع ذلك، هناك حالات قد تتراجع فيها تطبيقات vdso(7) وتستدعي نداء النظام الحقيقي، وفي هذه الحالة سترى مرشحات seccomp نداء النظام).

•   يعتمد ترشيح Seccomp على أرقام نداءات النظام. ومع ذلك، لا تستدعي التطبيقات عادةً نداءات النظام مباشرة، بل تستدعي دوال غلاف في مكتبة C والتي بدورها تستدعي نداءات النظام. وبناءً على ذلك، يجب الانتباه لما يلي:

    •   قد تستخدم أغلفة glibc لبعض نداءات النظام التقليدية في الواقع نداءات نظام بأسماء مختلفة في النواة. على سبيل المثال، تستخدم دالة الغلاف exit(2) في الواقع نداء النظام exit_group(2)، وتستدعي دالة الغلاف fork(2) في الواقع clone(2).

    •   قد يختلف سلوك دوال الغلاف عبر المعماريات، وفقًا لمجموعة نداءات النظام الموفرة على تلك المعماريات. بمعنى آخر، قد تستدعي نفس دالة الغلاف نداءات نظام مختلفة على معماريات مختلفة.

    •   أخيرًا، يمكن أن يتغير سلوك دوال الغلاف عبر إصدارات glibc. على سبيل المثال، في الإصدارات القديمة، كانت دالة غلاف glibc لـ open(2) تستدعي نداء النظام الذي يحمل نفس الاسم، ولكن بدءًا من glibc 2.26، انتقل التطبيق إلى استدعاء openat(2) على جميع المعماريات.

والنتيجة المترتبة على النقاط المذكورة أعلاه هي أنه قد يكون من الضروري الترشيح لنداء نظام آخر غير المتوقع. توفر صفحات الدليل المتنوعة في القسم 2 تفاصيل مفيدة حول الفروق بين دوال الغلاف ونداءات النظام الأساسية في الأقسام الفرعية المعنونة C library/kernel differences.

علاوة على ذلك، لاحظ أن تطبيق مرشحات seccomp قد يتسبب في حدوث علل (bugs) في التطبيق، عندما تتسبب المرشحات في إخفاقات غير متوقعة للعمليات المشروعة التي قد يحتاج التطبيق إلى تنفيذها. قد لا تُكتشف هذه العلل بسهولة عند اختبار مرشحات seccomp إذا حدثت في مسارات برمجية نادرة الاستخدام في التطبيق.

  تفاصيل BPF الخاصة بـ Seccomp
لاحظ تفاصيل BPF التالية الخاصة بمرشحات seccomp:

•   معدلات الحجم BPF_H و BPF_B غير مدعومة: يجب أن تقوم جميع العمليات بتحميل وتخزين كلمات (4 بايت) (BPF_W).

•   للوصول إلى محتويات مخزن seccomp_data المؤقت، استخدم معدل وضع العنونة BPF_ABS.

•   ينتج عن معدل وضع العنونة BPF_LEN معامل وضع فوري قيمته هي حجم مخزن seccomp_data المؤقت.

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

•   إذا لم يكن البرنامج يعمل على المعمارية المحددة، فإن مرشح BPF يتسبب في فشل نداءات النظام مع الخطأ ENOSYS.

•   إذا حاول البرنامج تنفيذ نداء النظام بالرقم المحدد، فإن مرشح BPF يتسبب في فشل نداء النظام، مع ضبط errno على رقم الخطأ المحدد.

تحدد وسائط سطر الأوامر المتبقية مسار ووسائط إضافية لبرنامج يجب على البرنامج المثال محاولة تنفيذه باستخدام execv(3) (دالة مكتبية تستخدم نداء النظام execve(2)). تظهر أدناه بعض عمليات تشغيل البرنامج كمثال.

أولاً، نعرض المعمارية التي نعمل عليها (x86-64) ثم ننشئ دالة صدفة تبحث عن أرقام نداءات النظام في هذه المعمارية:

    $ uname -m;
    x86_64
    $ syscall_nr() {
        cat /usr/src/linux/arch/x86/syscalls/syscall_64.tbl | \
        awk '$2 != "x32" && $3 == "'$1'" { print $1 }'
    };

عندما يرفض مرشح BPF نداء نظام (الحالة [2] أعلاه)، فإنه يتسبب في فشل نداء النظام برقم الخطأ المحدد في سطر الأوامر. في التجارب الموضحة هنا، سنستخدم رقم الخطأ 99:

    $ errno 99;
    EADDRNOTAVAIL 99 Cannot assign requested address

في المثال التالي، نحاول تشغيل الأمر whoami(1)، لكن مرشح BPF يرفض نداء النظام execve(2)، بحيث لا يُنفذ الأمر أبدًا:

    $ syscall_nr execve;
    59
    $ ./a.out;
    Usage: ./a.out <syscall_nr> <arch> <errno> <prog> [<args>]
    Hint for <arch>: AUDIT_ARCH_I386: 0x40000003
                     AUDIT_ARCH_X86_64: 0xC000003E
    $ ./a.out 59 0xC000003E 99 /bin/whoami;
    execv: Cannot assign requested address

في المثال التالي، يرفض مرشح BPF نداء النظام write(2)، بحيث أنه على الرغم من تشغيل الأمر whoami(1) بنجاح، إلا أنه لا يتمكن من كتابة أي مخرجات:

    $ syscall_nr write;
    1
    $ ./a.out 1 0xC000003E 99 /bin/whoami;

في المثال الأخير، يرفض مرشح BPF نداء نظام لا يستخدمه الأمر whoami(1)، لذا يتمكن من التنفيذ بنجاح وإخراج المخرجات:

    $ syscall_nr preadv;
    295
    $ ./a.out 295 0xC000003E 99 /bin/whoami;
    cecilia

  مصدر البرنامج
    #include <linux/audit.h>
    #include <linux/filter.h>
    #include <linux/seccomp.h>
    #include <stdcountof.h>
    #include <stddef.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/prctl.h>
    #include <sys/syscall.h>
    #include <unistd.h>

    #define X32_SYSCALL_BIT 0x40000000

    static int
    install_filter(int syscall_nr, unsigned int t_arch, int f_errno)
    {
        unsigned int upper_nr_limit = 0xffffffff;

        /* نفترض أن AUDIT_ARCH_X86_64 تعني ABI x86-64 العادي
           (في ABI x32، تحتوي جميع نداءات النظام على بت 30 مضبوطًا في
           حقل 'nr'، مما يعني أن الأرقام هي >= X32_SYSCALL_BIT).  */
        if (t_arch == AUDIT_ARCH_X86_64)
            upper_nr_limit = X32_SYSCALL_BIT - 1;

        struct sock_filter filter[] = {
            /* [0] تحميل المعمارية من مخزن 'seccomp_data' المؤقت إلى
                   المركم.  */
            BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
                     (offsetof(struct seccomp_data, arch))),

            /* [1] القفز للأمام 5 تعليمات إذا كانت المعمارية لا
                   تطابق 't_arch'.  */
            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, t_arch, 0, 5),

            /* [2] تحميل رقم نداء النظام من مخزن 'seccomp_data' المؤقت إلى
                   المركم.  */
            BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
                     (offsetof(struct seccomp_data, nr))),

            /* [3] فحص ABI - مطلوب فقط لـ x86-64 في حالات استخدام
                   قائمة الحظر. استخدم BPF_JGT بدلاً من الفحص ضد قناع
                   البتات لتجنب الاضطرار لإعادة تحميل رقم نداء النظام.  */
            BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, upper_nr_limit, 3, 0),

            /* [4] القفز للأمام تعليمية واحدة إذا كان رقم نداء النظام
                   لا يطابق 'syscall_nr'.  */
            BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall_nr, 0, 1),

            /* [5] تطابق المعمارية ونداء النظام: لا تنفذ
               نداء النظام، وأعد 'f_errno' في 'errno'.  */
            BPF_STMT(BPF_RET | BPF_K,
                     SECCOMP_RET_ERRNO | (f_errno & SECCOMP_RET_DATA)),

            /* [6] وجهة عدم تطابق رقم نداء النظام: السماح بنداءات
                   النظام الأخرى.  */
            BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),

            /* [7] وجهة عدم تطابق المعمارية: قتل العملية.  */
            BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
        };

        struct sock_fprog prog = {
            .len = countof(filter),
            .filter = filter,
        };

        if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) {
            perror("seccomp");
            return 1;
        }

        return 0;
    }

    int
    main(int argc, char *argv[])
    {
        if (argc < 5) {
            fprintf(stderr, "Usage: "
                    "%s <syscall_nr> <arch> <errno> <prog> [<args>]\n"
                    "Hint for <arch>: AUDIT_ARCH_I386: 0x%X\n"
                    "                 AUDIT_ARCH_X86_64: 0x%X\n"
                    "\n", argv[0], AUDIT_ARCH_I386, AUDIT_ARCH_X86_64);
            exit(EXIT_FAILURE);
        }

        if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
            perror("prctl");
            exit(EXIT_FAILURE);
        }

        if (install_filter(strtol(argv[1], NULL, 0),
                           strtoul(argv[2], NULL, 0),
                           strtol(argv[3], NULL, 0)))
            exit(EXIT_FAILURE);

        execv(argv[4], &argv[4]);
        perror("execv");
        exit(EXIT_FAILURE);
    }

انظر أيضًا
bpfc(1), strace(1), bpf(2), prctl(2), ptrace(2), seccomp_unotify(2), sigaction(2), proc(5), signal(7), socket(7)

صفحات متنوعة من مكتبة libseccomp، تشمل: scmp_sys_resolver(1) و seccomp_export_bpf(3) و seccomp_init(3) و seccomp_load(3) و seccomp_rule_add(3).

ملفات مصدر النواة Documentation/networking/filter.rst و Documentation/userspace-api/seccomp_filter.rst.

McCanne, S. and Jacobson, V. (1992) The BSD Packet Filter: A New Architecture for User-level Packet Capture, Proceedings of the USENIX Winter 1993 Conference http://www.tcpdump.org/papers/bpf-usenix93.pdf

Terence Kelly and Edison Fuh (2025) Sandboxing: Foolproof Boundaries vs. Unbounded Foolishness https://dl.acm.org/doi/pdf/10.1145/3733699

ترجمة
تُرجمت هذه الصفحة من الدليل بواسطة زايد السعيدي <zayed.alsaidi@gmail.com>

هذه الترجمة هي وثيقة مجانية؛ راجع رخصة جنو العامة الإصدار 3 (https://www.gnu.org/licenses/gpl-3.0.html) أو ما بعده للاطلاع على شروط حقوق النشر. لا توجد أي ضمانات.

إذا وجدت أي أخطاء في ترجمة صفحة الدليل هذه، يرجى إرسال بريد إلكتروني إلى قائمة بريد المترجمين: kde-l10n-ar@kde.org.