مرحبا سكان! يعد الجهاز الظاهري BPF أحد أهم مكونات نواة Linux. سيمكن تطبيقه الذكي مهندسي الأنظمة من اكتشاف الأعطال وحل حتى أكثر المشكلات تعقيدًا. سوف تتعلم كيفية كتابة البرامج التي تراقب وتعديل سلوك النواة ، وستكون قادرًا على حقن التعليمات البرمجية بأمان لمراقبة الأحداث في النواة ، وغير ذلك الكثير. سوف يساعدك David Calavera و Lorenzo Fontana على إطلاق قوة BPF. قم بتوسيع معرفتك بتحسين الأداء والشبكات والأمان. - استخدم BPF لتتبع وتعديل سلوك Linux kernel. - أدخل التعليمات البرمجية لمراقبة الأحداث بأمان في النواة - دون الحاجة إلى إعادة تجميع النواة أو إعادة تشغيل النظام. - استخدم أمثلة التعليمات البرمجية المفيدة في C أو Go أو Python. - إدارة الموقف من خلال امتلاك دورة حياة برنامج BPF.
أمان Linux kernel والميزات و Seccomp
يوفر BPF طريقة قوية لتوسيع النواة دون المساس بالاستقرار أو الأمان أو السرعة. لهذا السبب ، اعتقد مطورو النواة أنه سيكون من الجيد الاستفادة من تعدد استخداماته لتحسين عزل العملية في Seccomp من خلال تنفيذ عوامل تصفية Seccomp المدعومة من برامج BPF المعروفة أيضًا باسم Seccomp BPF. في هذا الفصل ، سنشرح ما هو Seccomp وكيف يتم تطبيقه. ثم ستتعلم كيفية كتابة مرشحات Seccomp باستخدام برامج BPF. بعد ذلك ، دعنا نلقي نظرة على خطافات BPF المضمنة التي تمتلكها النواة لوحدات أمان Linux.
وحدات أمان Linux (LSM) هي نظام أساسي يوفر مجموعة من الوظائف التي يمكن استخدامها لتوحيد تنفيذ نماذج الأمان المختلفة. يمكن استخدام LSM مباشرة في شجرة مصدر kernel مثل Apparmor و SELinux و Tomoyo.
لنبدأ بمناقشة ميزات Linux.
قدرات
يتمثل جوهر قدرات Linux في أنك تحتاج إلى منح إذن عملية غير مميزة لأداء مهمة محددة ، ولكن دون suid لهذا الغرض ، أو جعل العملية مميزة ، مما يقلل من احتمالية الهجمات ويسمح للعملية بأداء مهام معينة. على سبيل المثال ، إذا احتاج تطبيقك إلى فتح منفذ ذي امتيازات ، على سبيل المثال 80 ، بدلاً من تشغيل العملية كجذر ، يمكنك ببساطة منحه إمكانية CAP_NET_BIND_SERVICE.
ضع في اعتبارك برنامج Go باسم main.go:
package main
import (
"net/http"
"log"
)
func main() {
log.Fatalf("%v", http.ListenAndServe(":80", nil))
}
يخدم هذا البرنامج خادم HTTP على المنفذ 80 (هذا منفذ ذو امتيازات). عادة ما نقوم بتشغيله مباشرة بعد التجميع:
$ go build -o capabilities main.go
$ ./capabilities
ومع ذلك ، نظرًا لأننا لا نمنح امتيازات الجذر ، فإن هذا الرمز سيؤدي إلى حدوث خطأ عند ربط المنفذ:
2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1
capsh (أداة التحكم في الصدفة) هي أداة تطلق قذيفة بمجموعة محددة من القدرات.
في هذه الحالة ، كما ذكرنا سابقًا ، بدلاً من منح حقوق الجذر الكاملة ، يمكنك تمكين روابط المنفذ المميزة عن طريق تمكين cap_net_bind_service مع كل شيء آخر موجود بالفعل في البرنامج. للقيام بذلك ، يمكننا تغليف برنامجنا في capsh:
# capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' \
--keep=1 --user="nobody" \
--addamb=cap_net_bind_service -- -c "./capabilities"
دعونا نفهم قليلا عن هذا الأمر.
- غطاء - استخدم الغطاء كصدفة.
- --caps = 'cap_net_bind_service + eip cap_setpcap ، cap_setuid ، cap_setgid + ep' - نظرًا لأننا نحتاج إلى تغيير المستخدم (لا نريد التشغيل كجذر) ، سنحدد cap_net_bind_service والقدرة على تغيير معرف المستخدم فعليًا من الجذر إلى لا أحد ، وهما cap_setuid و cap_setgid ...
- --keep=1 — , root.
- --user=«nobody» — , , nobody.
- --addamb=cap_net_bind_service — root.
- — -c "./capabilities" — .
— , , execve(). , , , , .
ربما تتساءل عما يعنيه + eip بعد تحديد إمكانية في خيار --caps. يتم استخدام هذه العلامات لتحديد الميزة: -
يجب تنشيطها (p) ؛
- متاح للتطبيق (هـ) ؛
- يمكن أن ترثها العمليات الفرعية (ط).
نظرًا لأننا نريد استخدام cap_net_bind_service ، فنحن بحاجة إلى القيام بذلك باستخدام العلم الإلكتروني. ثم نبدأ القشرة في القيادة. سيؤدي هذا إلى تشغيل ثنائي القدرات ونحتاج إلى تمييزه بعلامة i. أخيرًا ، نريد تنشيط الميزة (فعلنا ذلك دون تغيير UID) مع p. يبدو أن cap_net_bind_service + eip.
يمكنك التحقق من النتيجة مع ss. قم بتقليص الإخراج قليلاً ليلائم الصفحة ، لكنه سيظهر المنفذ المرتبط ومعرف المستخدم بخلاف 0 ، في هذه الحالة 65534:
# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0
في هذا المثال ، استخدمنا capsh ، لكن يمكنك كتابة shell باستخدام libcap. انظر رجل 3 libcap لمزيد من المعلومات.
عند كتابة البرامج ، غالبًا ما لا يعرف المطور مسبقًا جميع الإمكانات التي يتطلبها البرنامج في وقت التشغيل ؛ علاوة على ذلك ، قد تتغير هذه الميزات في الإصدارات الجديدة.
لفهم قدرات برنامجنا بشكل أفضل ، يمكننا استخدام أداة قادرة على BCC ، والتي تحدد kprobe لوظيفة cap_capable kernel:
/usr/share/bcc/tools/capable
TIME UID PID TID COMM CAP NAME AUDIT
10:12:53 0 424 424 systemd-udevd 12 CAP_NET_ADMIN 1
10:12:57 0 1103 1101 timesync 25 CAP_SYS_TIME 1
10:12:57 0 19545 19545 capabilities 10 CAP_NET_BIND_SERVICE 1
يمكننا تحقيق نفس الشيء باستخدام bpftrace مع kprobe من سطر واحد في دالة cap_capable kernel:
bpftrace -e \
'kprobe:cap_capable {
time("%H:%M:%S ");
printf("%-6d %-6d %-16s %-4d %d\n", uid, pid, comm, arg2, arg3);
}' \
| grep -i capabilities
سينتج هذا شيئًا مثل ما يلي إذا تم تنشيط إمكانيات برنامجنا بعد kprobe:
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 10 1
العمود الخامس هو القدرات التي تحتاجها العملية ، وبما أن هذا الإخراج يتضمن أحداثًا غير متعلقة بالتدقيق ، فإننا نرى جميع عمليات التحقق غير المتعلقة بالتدقيق وأخيراً القدرة المطلوبة مع علامة التدقيق (الأخيرة في الإخراج) مضبوطة على 1. القدرة. الذي نهتم به هو CAP_NET_BIND_SERVICE ، ويتم تعريفه على أنه ثابت في كود مصدر kernel في ملف include / uapi / linux /ability.h بمعرف 10:
/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10<source lang="go">
غالبًا ما يتم الاستفادة من الميزات في وقت التشغيل لحاويات مثل runC أو Docker للتشغيل في وضع غير مميز ، ولكن يُسمح فقط بتلك الميزات الضرورية لتشغيل معظم التطبيقات. عندما يتطلب أحد التطبيقات إمكانات محددة ، يمكن لـ Docker تزويدهم بـ --cap-add:
docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummy
سيوفر هذا الأمر للحاوية إمكانية CAP_NET_ADMIN ، والتي ستسمح لها بتكوين ارتباط شبكة لإضافة واجهة dummy0.
يوضح القسم التالي كيفية استخدام ميزات مثل التصفية ، ولكن بطريقة مختلفة تسمح لنا بتنفيذ عوامل التصفية الخاصة بنا برمجيًا.
سيكومب
Seccomp تعني الحوسبة الآمنة ، وهي طبقة أمان مطبقة في Linux kernel تتيح للمطورين تصفية استدعاءات نظام معينة. في حين أن Seccomp يمكن مقارنته بإمكانيات Linux ، إلا أن قدرته على التعامل مع مكالمات نظام معينة تجعله أكثر مرونة مما هو عليه.
قدرات Seccomp و Linux ليست حصرية ، وغالبًا ما تستخدم معًا للاستفادة من كلا الأسلوبين. على سبيل المثال ، قد ترغب في إعطاء عملية CAP_NET_ADMIN القدرة ، ولكن لا تسمح لها بقبول اتصالات مأخذ التوصيل عن طريق حظر قبول واستدعاء 4 النظام.
تعتمد طريقة تصفية Seccomp على عوامل تصفية BPF التي تعمل في وضع SECCOMP_MODE_FILTER ، ويتم إجراء تصفية استدعاء النظام بنفس الطريقة المتبعة مع الحزم.
يتم تحميل عوامل تصفية Seccomp باستخدام prctl عبر عملية PR_SET_SECCOMP. تكون هذه المرشحات في شكل برنامج BPF يتم تنفيذه لكل حزمة Seccomp ممثلة ببنية seccomp_data. تحتوي هذه البنية على البنية المرجعية ، ومؤشرًا لتعليمات المعالج أثناء استدعاء النظام ، وستة وسائط كحد أقصى لاستدعاء النظام ، معبرًا عنها بـ uint64.
هكذا تبدو بنية seccomp_data من مصدر kernel في ملف linux / seccomp.h:
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};
كما ترى من هذه البنية ، يمكننا التصفية حسب استدعاء النظام أو وسيطاته أو كليهما.
بعد استلام كل حزمة Seccomp ، يجب أن يقوم المرشح بإجراء المعالجة لاتخاذ قرار نهائي وإخبار النواة بما يجب القيام به بعد ذلك. يتم التعبير عن القرار النهائي في إحدى قيم الإرجاع (أكواد الحالة).
- SECCOMP_RET_KILL_PROCESS - إنهاء العملية بأكملها فورًا بعد تصفية استدعاء النظام الذي لم يتم تنفيذه بسبب ذلك.
- SECCOMP_RET_KILL_THREAD - إنهاء الخيط الحالي مباشرة بعد تصفية مكالمة النظام ، والتي بسبب ذلك لم يتم تنفيذها.
- SECCOMP_RET_KILL - اسم مستعار لـ SECCOMP_RET_KILL_THREAD ، ترك للتوافق مع الإصدارات السابقة.
- SECCOMP_RET_TRAP - يتم تعطيل مكالمة النظام ويتم إرسال إشارة SIGSYS (مكالمة نظام غير صالحة) إلى مهمة الاستدعاء.
- SECCOMP_RET_ERRNO - لم يتم تنفيذ استدعاء النظام ، ويتم تمرير جزء من القيمة المرجعة لمرشح SECCOMP_RET_DATA إلى مساحة المستخدم على أنه رقم خطأ. يتم إرجاع قيم errno مختلفة بناءً على سبب الخطأ. يتم سرد أرقام الخطأ في القسم التالي.
- SECCOMP_RET_TRACE - تستخدم لإخطار ptrace باستخدام - PTRACE_O_TRACESECCOMP للاعتراض عند إجراء مكالمة نظام لرؤية هذه العملية والتحكم فيها. إذا لم يتم توصيل جهاز التتبع ، يتم إرجاع خطأ ، ويتم تعيين errno إلى -ENOSYS ، ولا يتم تنفيذ استدعاء النظام.
- SECCOMP_RET_LOG - تم السماح باستدعاء النظام وتسجيله.
- SECCOMP_RET_ALLOW - يُسمح ببساطة باستدعاء النظام.
ptrace هو استدعاء نظام لتنفيذ آليات التتبع في عملية تسمى tracee ، مع القدرة على مراقبة تنفيذ العملية والتحكم فيها. يمكن لبرنامج التتبع التأثير بشكل فعال على التنفيذ وتغيير سجلات ذاكرة التتبع. في سياق Seccomp ، يتم استخدام ptrace عند تشغيله بواسطة رمز الحالة SECCOMP_RET_TRACE ، لذلك يمكن للمتتبع منع تنفيذ استدعاء النظام وتنفيذ منطقه الخاص.
أخطاء Seccomp
من وقت لآخر ، عند العمل مع Seccomp ، ستواجه العديد من الأخطاء ، والتي يتم تحديدها بواسطة قيمة إرجاع من النوع SECCOMP_RET_ERRNO. للإبلاغ عن خطأ ، سيعود استدعاء نظام seccomp -1 بدلاً من 0.
الأخطاء التالية ممكنة:
- EACCESS - غير مسموح للمتصل بإجراء مكالمة نظام. يحدث هذا عادةً لأنه لا يحتوي على امتياز CAP_SYS_ADMIN أو لم يتم تعيين no_new_privs مع prctl (المزيد حول ذلك لاحقًا) ؛
- EFAULT - لا تحتوي الوسائط التي تم تمريرها (args في بنية seccomp_data) على عنوان صالح ؛
- EINVAL - يمكن أن يكون هناك أربعة أسباب هنا: -
العملية المطلوبة غير معروفة أو غير مدعومة من قبل النواة في التكوين الحالي ؛
- العلامات المحددة غير صالحة للعملية المطلوبة ؛
-تشمل التشغيل BPF_ABS ، ولكن هناك مشاكل مع الإزاحة المحددة ، والتي قد تتجاوز حجم بنية seccomp_data ؛
- يتجاوز عدد التعليمات التي تم تمريرها إلى المرشح الحد الأقصى ؛
- ENOMEM - لا توجد ذاكرة كافية لتشغيل البرنامج ؛
- EOPNOTSUPP - أشارت العملية إلى أن الإجراء كان متاحًا مع SECCOMP_GET_ACTION_AVAIL ، لكن النواة لا تدعم الإرجاع في الوسائط ؛
- ESRCH - حدثت مشكلة أثناء مزامنة تيار آخر ؛
- ENOSYS - لا يوجد تتبع متصل بإجراء SECCOMP_RET_TRACE.
prctl هو استدعاء نظام يسمح لبرنامج مساحة المستخدم بمعالجة (تعيين والحصول على) جوانب محددة من العملية ، مثل تسلسل البايت ، وأسماء الخيط ، ووضع الحوسبة الآمنة (Seccomp) ، والامتيازات ، وأحداث الأداء ، وما إلى ذلك.
قد تبدو Seccomp مثل تقنية sandbox بالنسبة لك ، لكنها ليست كذلك. Seccomp هي أداة تتيح للمستخدمين تطوير آلية وضع الحماية. الآن دعونا نلقي نظرة على كيفية إنشاء برامج التفاعل المخصصة باستخدام مرشح يسمى مباشرة بواسطة استدعاء نظام Seccomp.
مثال عامل تصفية BPF Seccomp
سنبين هنا كيفية الجمع بين الإجراءين اللذين تمت مناقشتهما سابقًا ، وهما:
- كتابة برنامج Seccomp BPF ، والذي سيتم استخدامه كعامل تصفية برموز إرجاع مختلفة اعتمادًا على القرارات المتخذة ؛
- تحميل الفلتر باستخدام prctl.
أولاً ، نحتاج إلى رؤوس من المكتبة القياسية و Linux kernel:
#include <errno.h>
#include <linux/audit.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <unistd.h>
قبل تجربة هذا المثال ، نحتاج إلى التأكد من تجميع النواة باستخدام CONFIG_SECCOMP وتعيين CONFIG_SECCOMP_FILTER على y. على جهاز إنتاج ، يمكنك اختباره
cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP
على النحو التالي : باقي الكود عبارة عن وظيفة install_filter من جزأين. يحتوي الجزء الأول على قائمتنا الخاصة بإرشادات تصفية BPF:
static int install_filter(int nr, int arch, int error) {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
};
يتم تعيين التعليمات باستخدام وحدات الماكرو BPF_STMT و BPF_JUMP المحددة في ملف linux / filter.h.
دعنا نذهب من خلال التعليمات.
- BPF_STMT (BPF_LD + BPF_W + BPF_ABS (الإزاحة (البنية seccomp_data ، القوس)) - يتم تحميل النظام وتراكمه مع BPF_LD في شكل كلمة BPF_W ، توجد بيانات الحزمة عند إزاحة ثابتة BPF_ABS.
- BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K ، القوس ، 0 ، 3) - يتحقق باستخدام BPF_JEQ إذا كانت قيمة العمارة في ثابت المجمع BPF_K تساوي القوس. إذا كان الأمر كذلك ، فإنه يقفز عند الإزاحة 0 إلى التعليمات التالية ؛ وإلا فإنه يقفز عند الإزاحة 3 (في هذه الحالة) لإلقاء خطأ ، لأن القوس غير متطابق.
- BPF_STMT (BPF_LD + BPF_W + BPF_ABS (الإزاحة (البنية seccomp_data ، nr))) - التنزيلات والتراكم مع BPF_LD في شكل كلمة BPF_W ، وهو رقم استدعاء النظام المضمن في إزاحة ثابتة BPF_ABS.
- BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K، nr، 0، 1) - يقارن رقم استدعاء النظام مع قيمة المتغير nr. إذا كانت متساوية ، فسيستمر إلى العبارة التالية ولا يسمح باستدعاء النظام ؛ وإلا ، يمكّن استدعاء النظام مع SECCOMP_RET_ALLOW.
- BPF_STMT (BPF_RET + BPF_K، SECCOMP_RET_ERRNO | (خطأ & SECCOMP_RET_DATA)) - إنهاء البرنامج باستخدام BPF_RET ، ونتيجة لذلك ، يصدر خطأ SECCOMP_RET_ERRNO برقم من متغير الخطأ.
- BPF_STMT (BPF_RET + BPF_K، SECCOMP_RET_ALLOW) - ينهي البرنامج باستخدام BPF_RET ويسمح بتنفيذ مكالمة نظام باستخدام SECCOMP_RET_ALLOW.
SECCOMP IS CBPF
قد تتساءل عن سبب استخدام قائمة التعليمات بدلاً من كائن ELF المترجم أو برنامج JIT المترجم.
هناك سببان لهذا.
• أولاً ، يستخدم Seccomp cBPF (BPF الكلاسيكي) ، وليس eBPF ، مما يعني أنه ليس لديه سجلات ، ولكنه فقط مجمع لتخزين آخر نتيجة حسابية ، كما ترى في المثال.
• ثانيًا ، يأخذ Seccomp مؤشرًا إلى مجموعة من تعليمات BPF مباشرةً ولا شيء آخر. تساعد وحدات الماكرو التي استخدمناها فقط في تحديد هذه التعليمات في شكل مناسب للمبرمجين.
إذا كنت بحاجة إلى مزيد من المساعدة في فهم هذا التجميع ، ففكر في الكود الزائف الذي يفعل الشيء نفسه:
if (arch != AUDIT_ARCH_X86_64) {
return SECCOMP_RET_ALLOW;
}
if (nr == __NR_write) {
return SECCOMP_RET_ERRNO;
}
return SECCOMP_RET_ALLOW;
بعد تحديد كود المرشح في بنية socket_filter ، تحتاج إلى تعريف sock_fprog يحتوي على الكود وطول المرشح المحسوب. هناك حاجة إلى بنية البيانات هذه كحجة لإعلان عمل العملية في المستقبل:
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
لا يوجد سوى شيء واحد يجب القيام به في وظيفة install_filter - قم بتنزيل البرنامج نفسه! للقيام بذلك ، نستخدم prctl ، مع أخذ PR_SET_SECCOMP كخيار للدخول إلى وضع الحوسبة الآمنة. ثم نخبر الوضع بتحميل عامل التصفية باستخدام SECCOMP_MODE_FILTER ، الموجود في متغير prog من النوع sock_fprog:
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
perror("prctl(PR_SET_SECCOMP)");
return 1;
}
return 0;
}
أخيرًا ، يمكننا استخدام دالة install_filter الخاصة بنا ، ولكن قبل ذلك نحتاج إلى استخدام prctl لتعيين PR_SET_NO_NEW_PRIVS للتنفيذ الحالي وبالتالي تجنب الموقف الذي تحصل فيه العمليات التابعة على امتيازات أكثر من الوالدين. باستخدام هذا ، يمكننا إجراء الاستدعاءات التالية لـ prctl في وظيفة install_filter دون امتلاك حقوق الجذر.
الآن يمكننا استدعاء دالة install_filter. دعنا نحظر جميع استدعاءات نظام الكتابة المتعلقة بهندسة X86-64 ، ونمنح الإذن فقط ، والذي يمنع جميع المحاولات. بعد تثبيت الفلتر ، تابع التنفيذ باستخدام المتغير الأول:
int main(int argc, char const *argv[]) {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl(NO_NEW_PRIVS)");
return 1;
}
install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
return system(argv[1]);
}
هيا بنا نبدأ. يمكننا استخدام clang أو gcc لتجميع برنامجنا ، وفي كلتا الحالتين ، يتم فقط تجميع ملف main.c بدون خيارات خاصة:
clang main.c -o filter-write
كما لوحظ ، قمنا بحظر جميع الإدخالات في البرنامج. لاختبار هذا ، أنت بحاجة إلى برنامج ينتج شيئًا ما - يبدو أنه مرشح جيد. هذه هي الطريقة التي تتصرف بها عادة:
ls -la
total 36
drwxr-xr-x 2 fntlnz users 4096 Apr 28 21:09 .
drwxr-xr-x 4 fntlnz users 4096 Apr 26 13:01 ..
-rwxr-xr-x 1 fntlnz users 16800 Apr 28 21:09 filter-write
-rw-r--r-- 1 fntlnz users 19 Apr 28 21:09 .gitignore
-rw-r--r-- 1 fntlnz users 1282 Apr 28 21:08 main.c
تماما! هكذا يبدو برنامج shell الخاص بنا: نجتاز البرنامج الذي نريد اختباره كأول وسيط:
./filter-write "ls -la"
عند تنفيذه ، ينتج هذا البرنامج مخرجات فارغة تمامًا. ومع ذلك ، يمكننا استخدام الدعامة لمعرفة ما يحدث:
strace -f ./filter-write "ls -la"
تم تقصير نتيجة العمل بشكل كبير ، ولكن الجزء المقابل منها يوضح أن السجلات محظورة بسبب خطأ EPERM - وهو نفس الخطأ الذي قمنا بتكوينه. هذا يعني أن البرنامج لا يخرج أي شيء لأنه لا يمكنه الوصول إلى استدعاء نظام الكتابة:
[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "write error", 11) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "\n", 1) = -1 EPERM (Operation not permitted)
أنت الآن تفهم كيف يعمل Seccomp BPF ولديك فكرة جيدة عما يمكن فعله به. لكن ألا تريد أن تفعل الشيء نفسه مع eBPF بدلاً من cBPF من أجل استخدام قوتها الكاملة؟
عند التفكير في برامج eBPF ، يعتقد معظم الناس أنهم يكتبونها ويحملونها بامتيازات المسؤول. في حين أن هذه العبارة صحيحة بشكل عام ، إلا أن النواة تنفذ مجموعة من الآليات لحماية كائنات eBPF على مستويات مختلفة. تسمى هذه الآليات مصائد BPF LSM.
مصائد BPF LSM
لتوفير مراقبة مستقلة عن البنية لأحداث النظام ، تنفذ LSM مفهوم المصائد. استدعاء الخطاف يشبه من الناحية الفنية استدعاء النظام ، ولكنه مستقل عن النظام ومتكامل مع البنية التحتية. يوفر LSM مفهومًا جديدًا يمكن أن تساعد فيه طبقة التجريد في تجنب المشكلات التي تنشأ عند التعامل مع استدعاءات النظام على بنيات مختلفة.
في وقت كتابة هذه السطور ، كان للنواة سبعة خطافات مرتبطة ببرامج BPF ، وكان SELinux هو LSM المدمج الوحيد الذي ينفذها.
يوجد الكود المصدري للخطافات في شجرة النواة في ملف include / linux / security.h:
extern int security_bpf(int cmd, union bpf_attr *attr, unsigned int size);
extern int security_bpf_map(struct bpf_map *map, fmode_t fmode);
extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_alloc(struct bpf_map *map);
extern void security_bpf_map_free(struct bpf_map *map);
extern int security_bpf_prog_alloc(struct bpf_prog_aux *aux);
extern void security_bpf_prog_free(struct bpf_prog_aux *aux);
سيتم استدعاء كل منهم في مراحل مختلفة من التنفيذ:
- security_bpf - يقوم بإجراء فحوصات أولية لاستدعاءات نظام BPF المنفذة ؛
- security_bpf_map - يتحقق عندما تقوم النواة بإرجاع واصف ملف للخريطة ؛
- security_bpf_prog - للتحقق عندما تقوم النواة بإرجاع واصف ملف لبرنامج eBPF ؛
- security_bpf_map_alloc - يتحقق مما إذا كان حقل الأمان داخل خرائط BPF قد تمت تهيئته ؛
- security_bpf_map_free - يتحقق مما إذا كان حقل الأمان داخل خرائط BPF قد تم مسحه ؛
- security_bpf_prog_alloc - يتحقق مما إذا كان مجال الأمان قد تمت تهيئته داخل برامج BPF ؛
- security_bpf_prog_free - يتحقق مما إذا كان مجال الأمان داخل برامج BPF قد تم مسحه.
الآن بعد أن رأينا كل هذا ، نفهم أن الفكرة وراء اعتراض LSM BPF هي أنها يمكن أن توفر الحماية لكل كائن eBPF ، مما يضمن أن الأشخاص الذين لديهم الامتيازات المناسبة فقط يمكنهم تنفيذ العمليات على الخرائط والبرامج.
ملخص
الأمن ليس شيئًا يمكنك فرضه بأسلوب واحد يناسب الجميع لأي شيء تريد حمايته. من المهم أن تكون قادرًا على حماية الأنظمة على مستويات مختلفة وبطرق مختلفة. صدق أو لا تصدق ، أفضل طريقة لتأمين النظام هي تنظيم مستويات مختلفة من الحماية من مواقع مختلفة بحيث يمنع التدهور الأمني لمستوى واحد الوصول إلى النظام بأكمله. لقد قام مطورو النواة بعمل رائع بتزويدنا بمجموعة من الطبقات ونقاط الاتصال المختلفة. نأمل أن نكون قد قدمنا لك فهمًا جيدًا لماهية الطبقات وكيفية استخدام برامج BPF للعمل معها.
عن المؤلفين
ديفيد كالافيرا هو CTO في Netlify. لقد عمل في Docker Support وساهم في تطوير أدوات Runc و Go و BCC ، بالإضافة إلى مشاريع أخرى مفتوحة المصدر. معروف بعمله في مشاريع Docker وتطوير نظام Docker الإضافي البيئي. ديفيد مغرم جدًا بالرسوم البيانية اللهب ويسعى دائمًا لتحسين الأداء.
لورنزو فونتانا هو جزء من فريق تطوير المصدر المفتوح في Sysdig ، حيث يشارك بشكل أساسي في Falco ، وهو مشروع مؤسسة Cloud Native Computing Foundation الذي يوفر أمان وقت تشغيل الحاوية واكتشاف الأخطاء من خلال وحدة kernel و eBPF. إنه شغوف بالأنظمة الموزعة والشبكات المعرفة بالبرمجيات ونواة Linux وتحليل الأداء.
»يمكن العثور على مزيد من التفاصيل حول الكتاب على الموقع الإلكتروني لدار النشر
» جدول المحتويات
» مقتطفات
للساكنين خصم 25٪ على القسيمة - لينكس
عند الدفع مقابل النسخة الورقية من الكتاب ، يتم إرسال كتاب إلكتروني عبر البريد الإلكتروني.