Libseccomp: RFE: أصبح seccomp_rule_add بطيئًا جدًا منذ الإصدار 2.4.0

تم إنشاؤها على ١ مايو ٢٠١٩  ·  23تعليقات  ·  مصدر: seccomp/libseccomp

مرحبا،
تم تقديم المشكلة من خلال الالتزام ce3dda9a1747cc6a4c044eafe5a2eb653c974919 بين الإصدار 2.3.3 و v2.4.0. خذ بعين الاعتبار المثال التالي: foo.c.zip .
يضيف عددًا كبيرًا جدًا من القواعد. ويعمل بشكل أبطأ بنحو 100 مرة بعد الالتزام المذكور أعلاه.

وقت التنفيذ foo.c باستخدام الإصدار 2.4.1: 0.448
وقت تنفيذ foo.c باستخدام الإصدار 2.3.3: 0.077

لقد بحثت قليلاً واكتشفت أن db_col_transaction_start () ينسخ مجموعة المرشحات الموجودة بالفعل ويستخدم arch_filter_rule_add () لتكرار قواعد التصفية. لكن arch_filter_rule_add () يستدعي arch_syscall_translate () الذي يستدعي arch_syscall_resolve_name () الذي يعمل في O (عدد مرات syscalls على العمارة المحددة). لذا فإن إضافة قاعدة واحدة تعمل على الأقل في O (عدد القواعد المضافة بالفعل * عدد syscalls على البنى المستخدمة) وهو IMO السيئ حقًا.
لقد حسبت عدد المكالمات إلى arch_filter_rule_add () في المثال أعلاه وهو يساوي 201152 .

قبل ذلك ، كان عدد الاستدعاءات لـ arch_filter_rule_add () هو 896 . ومما أفهمه من الكود ، فإن db_col_transaction_start () ينسخ أيضًا مجموعة المرشحات الموجودة بالفعل ولا يستخدم arch_filter_rule_add (). وهو ما يعطينا تقديرًا: وقت إضافة قاعدة حول O (عدد القواعد المضافة بالفعل + عدد syscalls في البنية المعينة) ، وهو أفضل بكثير.

ومع ذلك ، لا يجب أن ترتبط IMO بعدد القواعد المضافة بالفعل ، لأن إضافة قواعد n تعمل في O (n ^ 2) بعد ذلك. لكن هذا موضوع لنقاش مختلف ، لذلك لا ينبغي أن يكون مشكلة بالنسبة للفلاتر الصغيرة أو المرشحات التي يتم إنشاؤها بشكل غير منتظم.

لماذا هذه المشكلة مهمة؟
تحتاج بعض المرشحات إلى تنفيذ برامج PID (مثل السماح للخيط بإرسال إشارات إلى نفسه فقط). لذلك إذا كان البرنامج المقيد يحتاج إلى أن يتم تنفيذه عددًا كبيرًا من المرات ، فإنه يصبح عبئًا مرئيًا جدًا. لدي عامل تصفية يضم حوالي 300 قاعدة وتبلغ تكلفة libseccomp حوالي 0.16 ثانية لكل شوط من عملية وضع الحماية (أقوم بتشغيل العملية عشرات المرات).

شكرا لكم مقدما على مساعدتكم!

enhancement prioritlow

التعليق الأكثر فائدة

نحن نرى مهلات من المستخدمين بسبب هذا التغيير. لقد أبطأ الأشياء حقًا من حيث الحجم.

ال 23 كومينتر

مرحباvarqox.

نعم ، يمكن لوظائف محلل syscall أن تستخدم بعض التحسينات ، في الواقع إذا نظرت إلى الكود ، فسترى العديد من التعليقات مثل ما يلي:

/* XXX - plenty of room for future improvement here */

إذا كنت تريد النظر في تحسين هذا الرمز ، فيجب علينا استخدام المساعدة!

كما ذكر pcmoore ، هناك فرص كبيرة لتسريع إنشاء فلتر seccomp باستخدام libseccomp. حدد بحثك أعلاه أحد المجالات العديدة التي يمكن أن تستخدم التحسين. لم يكن هذا مصدر قلق للمستخدمين ، لذلك لم أركز عليه.

فيما يتعلق بأداء _runtime_ ، أعمل حاليًا على استخدام شجرة ثنائية لمرشحات كبيرة مثل تلك التي قدمتها في foo.c. تبدو النتائج الأولية مع عملائي الداخليين واعدة ، لكني أرغب في الحصول على مجموعة أخرى من العيون على التغييرات. انظر طلب السحب https://github.com/seccomp/libseccomp/pull/152

حسنًا ، أرى أنه يمكن تحسين حل مكالمة النظام ، ولكنه ليس السبب الجذري للمشكلة. وهو ، كما أراه ، إنشاء لقطة في db_col_transaction_start () . هناك يسمى arch_filter_rule_add () وهو بطيء بسبب حل syscall ، والذي تم حله في القاعدة الأصلية.

أراه على النحو التالي: نريد تكرار المجموعة الكاملة من المرشحات الحالية (ويعرف أيضًا باسم Struct db_filter) مع جميع قواعدها ، لذلك نحن _بني_ جميع المرشحات من البداية بدلاً من الاستفادة مما لدينا بالفعل ونسخ_ جميع المرشحات. لسنا مضطرين للبناء من الصفر ، فلدينا مرشح بناء كامل نريد فقط نسخة منه. ربما فاتني شيء ما ، ولكن يبدو أنه قد يتم إجراء الكثير من التحسينات على وظيفة db_col_transaction_start ().

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

هذا لا يعني أن رمز المعاملة لا يمكن تحسينه - إنه بالتأكيد يمكن - ولكن الكود الحالي هو ما هو عليه لسبب ، البساطة في المقام الأول.

نحن نرى مهلات من المستخدمين بسبب هذا التغيير. لقد أبطأ الأشياء حقًا من حيث الحجم.

فكرة أخرى ، يمكننا على الأرجح تغيير هذا حتى نكرر فقط القواعد عند بدء المعاملة ، وليس الشجرة بأكملها ، ونعيد إنشاء الشجرة فقط في معاملة فاشلة. إنه ليس مثاليًا ، لكن هذا يجب أن يستعيد جزءًا كبيرًا من الوقت.

نحتاج إلى القيام بشيء ما لأن أوقات بدء الحاويات وعمليات التنفيذ تشهد تراجعاً هائلاً في الأداء وتتسبب في تثبيت الأشخاص على 2.3x

لن أعلق أكثر على الطبيعة _ "الضخمة" _ للمشكلة ، فقد تم عمل هذا المنظور عدة مرات بالفعل وأنا أعتبره نسبيًا ويعتمد على حالة الاستخدام. ومع ذلك ، فقد أردت تذكير الجميع بأن إصدارات libseccomp قبل الإصدار 2.4 معرضة لثغرة أمنية محتملة تم الإعلان عنها (الإصدار رقم 139).

بالنسبة لأولئك الذين يهتمون بهذه المشكلة ، فقد تم وضع علامة عليها حاليًا لإصدار v2.5.

لقد أجريت عملية إعادة بناء ديون وكان لها تأثيرات أداء "ضخمة" في إصدار ثانوي ولن تكون مفيدًا من خلال تفجير هذا الأمر بالقول إنه يعتمد على حالة الاستخدام. يرجى أخذ هذا الأمر على محمل الجد حيث سيبدأ الناس في ملاحظة تحديث التوزيعات إلى 2.4

crosbymichael ، لم يكن التغيير مجرد إعادة بناء ، بل كان ضروريًا لإصلاح المشكلات ودعم التغييرات في النواة (وعلى الأخص الحاجة إلى دعم كل من مكالمات الاتصال متعددة الإرسال والمباشرة ، على سبيل المثال syscalls على 32-bit x86).

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

لاحظ لنفسك وأي شخص آخر يفكر في محاولة حل هذا ...

تم تذكيرني مؤخرًا بسبب قيامنا بما نقوم به فيما يتعلق بالمعاملات (نسخ كل شيء مقدمًا) ؛ نقوم بذلك لأننا نحتاج إلى التمكن من التراجع عن معاملة دون الإخفاق. لماذا ا؟
تحتاج عملية seccomp_rule_add () العادية إلى الحفاظ على المرشح سليمًا حتى في حالة الفشل ؛ إذا فشلنا في معاملة متعددة الأجزاء (على سبيل المثال socket / ipc syscalls على x86 / s390 / s390x / إلخ.) كجزء من إضافة قاعدة عادية ، يجب أن نكون قادرين على العودة إلى عامل التصفية في بداية المعاملة دون فشل ( بغض النظر عن ضغط الذاكرة ، وما إلى ذلك).

سيظل تكرار الشجرة بدون القواعد يمثل تحديًا نظرًا لطبيعة الشجرة والربط داخل الشجرة ، ولكن قد نتمكن من الاختيار بشكل انتقائي عندما نحتاج إلى إنشاء معاملة داخلية ، وتخطيها للكثيرين الحالات عندما لا تكون هناك حاجة إليها.

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

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

  • يجب أن يحاول db_col_transaction_start() استخدام معاملة الظل إذا كانت موجودة ، ولكن إذا لم يكن الأمر كذلك ، فيجب أن ترجع إلى السلوك الحالي.
  • يجب أن يتصرف db_col_transaction_abort() بنفس الطريقة التي يتصرف بها الآن ؛ هذا يعني أن المعاملة الفاشلة ستمسح معاملة الظل (تحتاج إلى شجرة لاستعادة عامل التصفية) ، لكن المعاملة الناجحة التالية ستعيد الظل. يجب أن تكون المعاملة الفاشلة نادرة بما يكفي بحيث لا تكون هذه مشكلة كبيرة.
  • قد نحتاج إلى مسح معاملة الظل في عمليات أخرى ، على سبيل المثال ، arch / ABI ops؟ ، ولكن هذا شيء سنحتاج إلى التحقق منه. بغض النظر ، يجب أن يكون مسح معاملة الظل تافهًا.
  • هذا له ميزة ليس فقط تسريع إضافة القواعد ، ولكن تسريع المعاملات بشكل عام. قد لا يكون هذا مهمًا الآن ، ولكنه سيكون مفيدًا عندما نعرض وظيفة المعاملة للمستخدمين (سيكون هذا ضروريًا إذا أردنا القيام بآلية مماثلة لـ BSD "التعهد").

قضيت بعض الوقت بعد العشاء الليلة لذا قمت بتمرير سريع في تنفيذ فكرة معاملة الظل أعلاه. لا يزال الرمز خامًا ، واختباراتي (أدناه) أكثر فظاظة ، ولكن يبدو أننا نشهد بعض مكاسب الأداء مع هذا النهج:

  • اختبار خط الأساس فوق
# time for i in {0..20000}; do /bin/true; done
real    0m10.479s
user    0m7.641s
sys     0m3.924s
  • الفرع الرئيسي الحالي
# time for i in {0..20000}; do ./42-sim-adv_chains > /dev/null; done

real    0m16.303s
user    0m12.584s
sys     0m4.501s
  • مرمم
time for i in {0..20000}; do ./42-sim-adv_chains > /dev/null; done

real    0m15.021s
user    0m11.540s
sys     0m4.387s

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

varqox و / أو crosbymichael بمجرد أن أنظف البقع قليلاً وأنشئ العلاقات العامة ، هل ستتمكن من اختبار ذلك في بيئتك؟

حالة الاختبار الخاصة بي موجودة هنا بالفعل:

مرحبا،
تم تقديم المشكلة من خلال الالتزام ce3dda9 بين الإصدار 2.3.3 و v2.4.0. خذ بعين الاعتبار المثال التالي: foo.c.zip .
يضيف عددًا كبيرًا جدًا من القواعد. ويعمل بشكل أبطأ بنحو 100 مرة بعد الالتزام المذكور أعلاه.

وقت التنفيذ foo.c باستخدام الإصدار 2.4.1: 0.448
وقت تنفيذ foo.c باستخدام الإصدار 2.3.3: 0.077

ولكن بمجرد أن تصبح العلاقات العامة جاهزة ، يمكنني اختبارها في بيئتي.

مرحبًا varqox ، نعم رأيت أنك قمت بتضمين حالة اختبار في التقرير الأصلي ، لكنني مهتم أكثر بسماع كيفية أدائها في الاستخدام الحقيقي . إذا كان بإمكانك تجربة PR # 180 والإبلاغ مرة أخرى ، فسأكون ممتنًا حقًا - شكرًا!

مرحبا pcmoore ،

شكرا لجعل هذا العلاقات العامة.
لقد قمت ببناء واختبار PR # 180 الخاص بك ، وكانت النتيجة واعدة لحالة الاختبار الخاصة بي. أشاهد هذه المشكلة لأن العملاء يستخدمون فحص عامل الإرساء وعانوا من مشكلة الأداء في libseccomp 2.4.x .
في حالة الاختبار الخاصة بي ، فإن أداء PR هذا يمكن مقارنته بـ libseccomp 2.3.3 . التفاصيل على النحو التالي:

بيئة

Ubuntu 19.04 VM (2 وحدة معالجة مركزية ، ذاكرة 2G) على MacBook Pro (15 بوصة ، منتصف 2015)
Kernel 5.0.0-32-عام
CE عامل ميناء 19.03.2

حالة اختبار:

تحضير 20 حاوية:

for i in $(seq 1 20)
do
  docker run -d --name bb$i busybox sleep 3d
done

شغّل الاختبار بإطلاق docker exec على جميع الحاويات في نفس الوقت

for i in $(seq 1 20)
do 
  /usr/bin/time -f "%E real" docker exec bb$i true & 
done

نتائج

libseccomp 2.3.3

0:01.05 real
0:01.12 real
0:01.16 real
0:01.20 real
0:01.23 real
0:01.27 real
0:01.31 real
0:01.35 real
0:01.37 real
0:01.38 real
0:01.40 real
0:01.41 real
0:01.40 real
0:01.40 real
0:01.45 real
0:01.46 real
0:01.47 real
0:01.48 real
0:01.48 real
0:01.49 real

libseccomp 2.4.1

0:00.98 real
0:01.63 real
0:01.67 real
0:01.95 real
0:02.55 real
0:02.70 real
0:02.70 real
0:02.96 real
0:03.04 real
0:03.16 real
0:03.17 real
0:03.21 real
0:03.23 real
0:03.27 real
0:03.24 real
0:03.29 real
0:03.27 real
0:03.29 real
0:03.28 real
0:03.27 real

بناء العلاقات العامة الخاص بك

0:00.95 real
0:01.12 real
0:01.20 real
0:01.23 real
0:01.28 real
0:01.29 real
0:01.31 real
0:01.37 real
0:01.38 real
0:01.40 real
0:01.43 real
0:01.43 real
0:01.44 real
0:01.45 real
0:01.42 real
0:01.47 real
0:01.48 real
0:01.48 real
0:01.48 real
0:01.50 real

ملاحظات متنوعة

  • قمت بتعيين 2.4.1 في AC_INIT في configure.ac قبل أن أقوم ببناء PR هذا.
  • تم تثبيت هذا الإصدار المخصص في /usr/local/lib ، لقد تحققت منه عن طريق تشغيل ldd /usr/bin/runc للتأكد من استخدام الإصدار المخصص أثناء الاختبار.
  • أجريت الاختبار عدة مرات ، وهناك اختلافات صغيرة جدًا في النتائج. لذلك أنا واثق من أنها ليست نتائج عرضية.

هذا رائع ، شكرًا للمساعدة xinfengliu!

مرحبا pcmoore ،
شكرا على هذا العلاقات العامة.
في حالتي ، فإنه يعيد أداء libseccomp إلى مستوى مماثل لـ v2.3.3.

نتائج

foo.c

g++ foo.c -lseccomp -o foo -O3
for ((i=0; i<10; ++i)); do time ./foo; done 

libseccomp 2.3.3

./foo  0.01s user 0.00s system 98% cpu 0.018 total
./foo  0.02s user 0.00s system 98% cpu 0.020 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.018 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.018 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total

المتوسط: 0.0188 s

libseccomp 2.4.2

./foo  0.19s user 0.00s system 99% cpu 0.195 total
./foo  0.19s user 0.00s system 99% cpu 0.194 total
./foo  0.19s user 0.00s system 99% cpu 0.193 total
./foo  0.19s user 0.00s system 99% cpu 0.196 total
./foo  0.19s user 0.00s system 99% cpu 0.195 total
./foo  0.20s user 0.00s system 99% cpu 0.196 total
./foo  0.19s user 0.00s system 99% cpu 0.194 total
./foo  0.20s user 0.00s system 99% cpu 0.197 total
./foo  0.19s user 0.00s system 99% cpu 0.195 total
./foo  0.19s user 0.00s system 99% cpu 0.194 total

المتوسط: 0.1949 s

PR # 180

./foo  0.01s user 0.01s system 98% cpu 0.012 total
./foo  0.01s user 0.00s system 97% cpu 0.013 total
./foo  0.01s user 0.00s system 96% cpu 0.013 total
./foo  0.01s user 0.01s system 97% cpu 0.014 total
./foo  0.01s user 0.00s system 97% cpu 0.012 total
./foo  0.01s user 0.00s system 98% cpu 0.013 total
./foo  0.01s user 0.00s system 98% cpu 0.012 total
./foo  0.01s user 0.00s system 98% cpu 0.013 total
./foo  0.01s user 0.00s system 97% cpu 0.013 total
./foo  0.01s user 0.00s system 97% cpu 0.011 total

المتوسط: 0.0126 s

يبدو أن PR هذا يعطي بعض التسريع على v2.3.3 في هذا الاختبار التركيبي.

إنشاء وتحميل (من seccomp_init () إلى seccomp_load ()) في وضع الحماية بالإضافة إلى بعض النفقات العامة لتهيئة آلية الحماية

libseccomp 2.3.3

Measured: 0.0052 s
Measured: 0.0040 s
Measured: 0.0046 s
Measured: 0.0042 s
Measured: 0.0038 s
Measured: 0.0038 s
Measured: 0.0039 s
Measured: 0.0036 s
Measured: 0.0042 s
Measured: 0.0044 s
Measured: 0.0036 s
Measured: 0.0037 s
Measured: 0.0044 s
Measured: 0.0035 s
Measured: 0.0035 s
Measured: 0.0035 s
Measured: 0.0040 s
Measured: 0.0037 s
Measured: 0.0043 s
Measured: 0.0042 s
Measured: 0.0035 s
Measured: 0.0034 s
Measured: 0.0038 s
Measured: 0.0035 s
Measured: 0.0035 s
Measured: 0.0037 s
Measured: 0.0038 s

المتوسط: 0.0039 s

libseccomp 2.4.2

Measured: 0.0496 s
Measured: 0.0480 s
Measured: 0.0474 s
Measured: 0.0475 s
Measured: 0.0479 s
Measured: 0.0479 s
Measured: 0.0492 s
Measured: 0.0485 s
Measured: 0.0491 s
Measured: 0.0490 s
Measured: 0.0484 s
Measured: 0.0483 s
Measured: 0.0480 s
Measured: 0.0482 s
Measured: 0.0474 s
Measured: 0.0483 s
Measured: 0.0507 s
Measured: 0.0472 s
Measured: 0.0482 s
Measured: 0.0471 s
Measured: 0.0498 s
Measured: 0.0489 s
Measured: 0.0474 s
Measured: 0.0494 s
Measured: 0.0483 s
Measured: 0.0498 s
Measured: 0.0492 s

المتوسط: 0.0466 s

PR # 180

Measured: 0.0058 s
Measured: 0.0059 s
Measured: 0.0054 s
Measured: 0.0046 s
Measured: 0.0059 s
Measured: 0.0048 s
Measured: 0.0045 s
Measured: 0.0051 s
Measured: 0.0052 s
Measured: 0.0053 s
Measured: 0.0048 s
Measured: 0.0048 s
Measured: 0.0045 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0059 s
Measured: 0.0044 s
Measured: 0.0046 s
Measured: 0.0046 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0062 s
Measured: 0.0047 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0044 s

المتوسط: 0.0049 s

على الرغم من أن العلاقات العامة في الاختبار التركيبي تعطي أوقاتًا أفضل من الإصدار 2.3.3 ، إلا أنها في العالم الحقيقي تكون أبطأ قليلاً (ربما بسبب القواعد الأكثر تعقيدًا وتشغيل seccomp_merge () لدمج مرشحين كبيرين). ومع ذلك ، فإنه لا يزال يعطي ما يقرب من عشرة أضعاف تسريع على v2.4.2.

شكرا للتحقق من الأداءvarqox! بمجرد أن يستجيب drakenclimber للجولة الأخيرة من التعليقات (وأقوم بإصلاح أي مشكلات متبقية قد يطرحها) ، سنقوم بدمج هذا الأمر.

آه ، فما باللك ، لقد لاحظت للتو أن drakenclimber وضع علامة على العلاقات العامة كما تمت الموافقة عليها. سأقوم بدمج ذلك الآن.

لقد قمت للتو بدمج PR # 180 لذا أعتقد أنه يمكننا وضع علامة مغلق ، إذا لاحظ أي شخص أي مشاكل متبقية في الأداء ، فلا تتردد في التعليق و / أو إعادة الفتح. شكرا للجميع على صبركم ومساعدتكم!

pcmoore هل تخطط للإفراج عن هذه التغييرات قريبًا؟

يعد هذا حاليًا جزءًا من معالم إصدار libseccomp v2.5 ، يمكنك تتبع تقدمنا ​​نحو إصدار v2.5 باستخدام الرابط أدناه:

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات