فشل chroot في OpenVPN: قاعدة AppArmor أم قدرة؟
إضافة مسار إلى ملف AppArmor التعريفي لا تمنح CAP_SYS_CHROOT أبدًا، وقد يأتي رفض chroot من ثلاث طبقات منفصلة. حدّد أيّ طبقة يجب تغييرها.
يسجّل البرنامج الخفي فشل chroot ويبدو ملف التعريف سخيًا أصلًا، لذا فالإصلاح المغري هو توسيع قاعدة مسار وإعادة التشغيل. هذا عادةً لا يغيّر شيئًا، لأن chroot يحرسه امتياز لم يكن الملف قادرًا على منحه أبدًا. تحديد الطبقة التي يجب لمسها يتطلب ثلاثة فحوص، وهي رخيصة: ما يُسمح للعملية بحمله، وما تسمح به السياسة، وما أزاله المشغّل قبل أن يكون لأي منهما أهمية.
بوابتان منفصلتان، وواحدة فقط تخص AppArmor
يحتاج chroot إلى CAP_SYS_CHROOT في فضاء أسماء المستخدمين الذي تعمل فيه العملية. هذا امتياز لينكس تقليدي، تتحقق منه النواة قبل أن يفعل النداء أي شيء بالمسارات. وبشكل منفصل، يمكن لملف AppArmor التعريفي في وضع الإنفاذ أن يرفض القدرة حتى عندما تحملها العملية، لأن السياسة الإلزامية تُستشار بالإضافة إلى بيانات الاعتماد لا بدلًا منها. التمييز مهم عمليًا: الطبقتان تعيشان في ملفات مختلفة ويصونهما عادةً أشخاص مختلفون.
تفشل هاتان البوابتان بالخطأ نفسه. كلتاهما تنتج EPERM، وكلتاهما تظهر كشكوى أذونات عامة في سجل البرنامج الخفي، ولهذا يهدر التخمين في محتوى الملف التعريفي وقتًا كبيرًا. قاعدة المسار التي كنت على وشك إضافتها تتحكم في الوصول إلى الملفات بعد نجاح chroot؛ وليس لها علاقة بكون العملية مسموحة أصلًا.
بوابة ثالثة تقع أعلى من كلتيهما. إذا بدأ مدير الخدمة أو غلاف ما العملية دون أن تكون القدرة في مجموعة حدودها، فلا يمكن لأي تغيير في AppArmor أن يعيدها، وإضافة قدرات إلى الملف التعريفي توثّق فقط نيّة لن تحترمها النواة.
اقرأ حالة الامتياز قبل تحرير السياسة
ابدأ من العملية الجارية لا من الإعداد الذي تظنه نشطًا. يعرض ملف الحالة خمسة أقنعة للقدرات وراية no-new-privileges، وكل منها يجيب عن سؤال مختلف:
grep -E '^(CapInh|CapPrm|CapEff|CapBnd|CapAmb|NoNewPrivs):' /proc/$(pidof openvpn)/status
sudo aa-status | grep -i openvpnCapEff هي المجموعة المهمة للنداء: القدرة الموجودة في CapBnd والغائبة من CapEff مسموح بها بالسقف وغير محمولة حاليًا. أما CapAmb فتهم عندما لا يكون الملف التنفيذي مميزًا بالمعنى المعتاد لـ setuid، لأن القدرات المحيطة هي طريقة الخدمة في الاحتفاظ بامتياز عبر exec دون قدرات ملف. وتحتوي CapInh على بتات يمكن توريثها عبر exec، لكن فقط عندما تسمح مجموعة الوراثة للملف المنفّذ بذلك، لذا فبت واحد هناك لا يمنح شيئًا بمفرده.
ثم تأكد من أي ملف تعريف مرتبط فعليًا. يمكن تقييد عملية باسم ملف تعريف لم تتوقعه، خصوصًا حين تشحن توزيعة ما ملف وحدتها وملفها التعريفي معًا، وقراءة /proc/PID/attr/current تحسم ذلك في سطر واحد. ومقارنة ملفك المحرَّر بالملف الذي تبلّغ عنه النواة هي الفرق بين إصلاح السبب وتحرير ملف لا يحمّله أحد.
أضف قاعدة القدرة فقط عندما يقول أثر التدقيق ذلك
تسمّي سجلات النواة والتدقيق الطبقة الرافضة. ابحث عن رفض يحمل العملية capable والاسم sys_chroot، مع الملف التعريفي الذي أنتجه. فإن وُجد ذلك السجل، فقد كانت العملية تحمل القدرة ورفضها AppArmor، والتغيير الموجّه هو قاعدة قدرة داخل الملف التعريفي الحقيقي:
capability sys_chroot,أبقِها مقتطفًا داخل الملف التعريفي المرتبط فعلًا، وتحقق من الكلمة المفتاحية مقابل مرجع ملفات apparmor.d بدل نسخ قاعدة من درس غير ذي صلة. فوضعها في تضمين مشترك يمنحها لكل برنامج مقيّد يسحب ذلك الملف، والتبديل إلى وضع الشكوى يخفي الرفض التالي بدل حلّ هذا الرفض. قاعدة الملف التعريفي تزيل رفضًا سياسيًا؛ وهي لا تصنع امتيازًا، لذا فالعملية التي لم تحمل CAP_SYS_CHROOT يومًا في مجموعتها الفعلية ستظل تفشل ولو كانت القاعدة موجودة.
إذا لم يُظهر سجل التدقيق أي رفض من AppArmor إطلاقًا، فكفّ عن تحرير الملف التعريفي. أنت أمام مشكلة بيانات اعتماد، والعمل المتبقي في ملف الوحدة أو العملية الأب، لا في لغة السياسة.
مدير الخدمة يحتفظ بسقفه الخاص
يضبط systemd مجموعة حدود قدرات لكل خدمة، وهي ضيقة افتراضيًا. وكل ما يُستبعد هناك غير متاح للعملية مهما قال الملف التعريفي، ولهذا قد تفشل وحدات مشدّدة في عملية كانت تنجح حين كان البرنامج الخفي يُشغَّل يدويًا. وتوجيهات ذلك سهلة القراءة في الوحدة وسهلة الإغفال حين يبلّغ التطبيق عن الفشل. وملفات الإسقاط التي تشحنها حزمة توزيعة هي المصدر المعتاد لسقف لا يتذكر أحد أنه ضبطه.
رفع السقف ومنح القدرة تغييران لا تغيير واحد. فمدخل في مجموعة الحدود يقول إن العملية يجوز أن تحمل القدرة؛ وهو لا يضيفها إلى المجموعة الفعلية، وNoNewPrivs يستطيع منع أي رفع لاحق من النجاح حتى لو تطابق كل شيء آخر. أضف فقط ما تحتاجه العملية، وأبقِ بقية التشديد سليمًا، وقاوم الاختصار بمنح كل القدرات لتمرير نداء واحد.
الترتيب مهم هنا أيضًا. غيّر سقف الوحدة أولًا، وتأكد من أن CapEff صارت تحتوي البت، وبعدها فقط قرّر إن كان AppArmor ما زال يرفض. أما إجراء التغييرين معًا فيتركك تخمّن مجددًا إن استمر الفشل.
بعد زوال الرفض، افحص السجن نفسه
نقل chroot الناجح يحرّك الجذر، لذا فكل مسار تلمسه العملية بعده يُحلّ داخل الشجرة الجديدة. الإعداد الناقص أو عقدة جهاز غائبة أو مجلد شهادات لم يُنسخ سيظهر الآن كخطأ ملف، وذلك الخطأ مشكلة تخطيط لا مشكلة قدرة. اقرأ الحدث التالي بدل إضافة إذن واسع آخر. والنقل يغيّر أيضًا معنى المسار النسبي، ولهذا تصرّ أدلة التشديد على استخدام مسارات مطلقة في إعداد البرنامج الخفي.
يفيد أيضًا أن تتذكر ما لا يفعله chroot. فهو حدّ مسارات لا صندوق رمل: العملية التي تحتفظ بالامتياز تستطيع الإفلات منه، ولهذا تُسقَط القدرة عادةً بعد النداء، ولهذا يجب أن يبقى التقييد حول البرنامج الخفي. الإبقاء على ملف AppArmor التعريفي ومجموعة حدود القدرات وقيود نظام الملفات معًا هو جوهر التمرين، لا فضلة تُزال متى بدأت الخدمة.
قاعدة القرار قصيرة. إذا سمّى سجل التدقيق AppArmor، فأضف قاعدة قدرة واحدة إلى الملف التعريفي المرتبط. وإذا كانت المجموعة الفعلية فارغة، فأصلح سقف الوحدة وترتيب إسقاط الامتياز. وإذا لم يكن الملف التعريفي هو الذي حرّرته، فكفّ عن التحرير واعثر على الحقيقي.