تتمثل عملية استكشاف الأخطاء وإصلاحها المعتادة في مثل هذه الحالات في فحص دورة حياة الكائن المتأثر بعناية: انظر كيف يتم تخصيص الذاكرة لها ، وكيف يتم تحريرها ، وكيف يتم أخذ عدادات المرجع وإصدارها بشكل صحيح ، مع إيلاء اهتمام خاص لمسارات الخطأ. ومع ذلك ، في حالتنا ، تم تحطيم كائنات مختلفة ، ولم يعثر فحص دورة حياتها على أخطاء.
تحظى ذاكرة التخزين المؤقت kmalloc-192 بشعبية كبيرة في النواة ، فهي تجمع بين عدة عشرات من الكائنات المختلفة. الخطأ في دورة حياة أحدهم هو السبب الأكثر ترجيحًا لهذا النوع من الأخطاء. حتى مجرد سرد كل هذه الأشياء يمثل مشكلة كبيرة ، وليس هناك شك في التحقق منها جميعًا. استمرت تقارير الأخطاء في الوصول ، لكننا لم نتمكن من العثور على سببها من خلال التحقيق المباشر. مطلوب تلميح.
من جانبنا ، تم التحقيق في هذه الأخطاء من قبل Andrey Ryabinin ، متخصص في إدارة الذاكرة ، والمعروف على نطاق واسع في الدوائر الضيقة لمطوري النواة كمطور KASAN ، وهي تقنية رائعة لاكتشاف أخطاء الوصول إلى الذاكرة. في الواقع ، كان KASAN هو الأنسب لاكتشاف أسباب الخطأ لدينا. لم يتم تضمين KASAN في نواة RHEL7 الأصلية ، لكن أندريه نقل التصحيحات الضرورية إلينا في OpenVz. لم نقم بتضمين KASAN في إصدار الإنتاج من kernel الخاص بنا ، ولكنه موجود في إصدار تصحيح الأخطاء من kernel ويساعدنا بشكل فعال في ضمان الجودة في العثور على الأخطاء.
بالإضافة إلى KASAN ، تتضمن نواة التصحيح العديد من ميزات التصحيح الأخرى التي ورثناها من Red Hat. نتيجة التصحيح ، تبين أن النواة بطيئة نوعًا ما. يقول QA أن نفس الاختبارات على نواة التصحيح تستغرق 4 مرات أكثر. بالنسبة لنا ، هذا ليس أمرًا أساسيًا ، فنحن لا نقيس الأداء هناك ، ولكن نبحث عن الأخطاء. ومع ذلك ، كان مثل هذا التباطؤ غير مقبول للعملاء ، وتم رفض طلباتنا لوضع نواة تصحيح الأخطاء في الإنتاج بشكل ثابت.
كبديل لـ KASAN ، طُلب من العملاء تمكين slub_debug على العقد المتأثرة... تسمح هذه التقنية أيضًا باكتشاف تلف الذاكرة. باستخدام المنطقة الحمراء وتسمم الذاكرة لكل كائن ، يتحقق مخصص الذاكرة لمعرفة ما إذا كان كل شيء على ما يرام في كل مرة يخصص فيها الذاكرة ويحررها. إذا حدث خطأ ما ، فإنه يصدر رسالة خطأ ، ويصحح الضرر المكتشف إن أمكن ، ويسمح للنواة بمواصلة العمل. بالإضافة إلى ذلك ، يتم تخزين المعلومات حول آخر من قام بتخصيص كائن وتحريره ، بحيث في حالة اكتشاف تلف الذاكرة بعد الوقائع ، يمكن فهم "من" كان هذا الكائن في "الحياة الماضية". يمكن تمكين Slub_debug في سطر أوامر kernel على نواة إنتاج ، ولكن هذه الفحوصات تستهلك أيضًا موارد الذاكرة ووحدة المعالجة المركزية. لا بأس بالتطوير وتصحيح الأخطاء في ضمان الجودة ، لكن عملاء الإنتاج يستخدمونه دون الكثير من الحماس.
مرت ستة أشهر ، اقترب العام الجديد. الاختبارات المحلية على نواة التصحيح باستخدام KASAN لم تكتشف المشكلة ، ولم نتلق أي تقارير أخطاء من العقد مع تمكين slub_debug ، ولم نتمكن من العثور على أي شيء في المواد الخام ولم نعثر على المشكلة. تم تحميل Andrey بمهام أخرى ، على العكس من ذلك ، حصلت على فجوة وتم توجيهي لتحليل تقرير الخطأ التالي.
بعد تحليل تفريغ التصادم ، اكتشفت قريبًا الكائن kmalloc-192 الإشكالي: كانت ذاكرته ممتلئة بنوع من القمامة ، وهي معلومات تنتمي إلى نوع آخر من الأشياء. لقد كان مشابهًا جدًا لعواقب الاستخدام بعد الاستخدام المجاني ، ولكن بعد فحص دورة حياة الكائن التالف في المواد الخام بعناية ، لم أجد أي شيء مريبًا أيضًا.
نظرت في تقارير الأخطاء القديمة ، وحاولت العثور على بعض الأدلة هناك ، ولكن دون جدوى أيضًا.
في النهاية عدت إلى الخطأ الخاص بي وبدأت في النظر إلى الكائن السابق. اتضح أيضًا أنه قيد الاستخدام ، ولكن من محتواه كان من غير المفهوم تمامًا ما كان عليه - لم يكن هناك ثوابت أو إشارات إلى وظائف أو كائنات أخرى. بعد تعقب عدة أجيال من الإشارات إلى هذا الكائن ، اكتشفت في النهاية أنها كانت صورة نقطية متقلصة. كان هذا الكائن جزءًا من تقنية التحسين لتحرير ذاكرة الحاوية. تم تطوير هذه التقنية في الأصل من أجل حباتنا ، وفي وقت لاحق قام مؤلفها كيريل تكاي بتخصيصها لخط لينكس الرئيسي
"تظهر النتائج أن الأداء يرتفع على الأقل في 548 مرة."
تكمل عدة آلاف من هذه الرقع نواة RHEL7 الأصلية المستقرة من الصخور ، مما يجعل نواة Virtuozzo ملائمة قدر الإمكان للمضيفين. كلما كان ذلك ممكنًا ، نحاول إرسال تطوراتنا إلى الخط الرئيسي ، لأن هذا يجعل من السهل الحفاظ على الكود في حالة جيدة.
بعد اتباع الروابط ، وجدت بنية تصف الصورة النقطية الخاصة بي. يعتقد الواصف أن حجم الصورة النقطية يجب أن يكون 240 بايت ، وهذا لا يمكن أن يكون صحيحًا بأي شكل من الأشكال ، لأنه في الواقع تم تخصيص الكائن من ذاكرة التخزين المؤقت kmalloc-192.
بنغو!
اتضح أن الوظائف التي تعمل مع ذاكرة الوصول إلى الصورة النقطية تتجاوز الحد الأعلى ويمكن أن تغير محتويات الكائن التالي. في حالتي ، كان هناك refcount في بداية الكائن ، وعندما أبطلت الصورة النقطية ذلك ، نتج عن الوضع اللاحق التحرير المفاجئ للكائن. في وقت لاحق ، تم تخصيص الذاكرة من جديد لكائن جديد ، تم اعتبار التهيئة الخاصة به على أنها قمامة من خلال رمز الكائن القديم ، والذي أدى عاجلاً أم آجلاً إلى انهيار العقدة.
من الجيد أن تستشير مؤلف الكود!
بالنظر إلى الكود الخاص به مع كيريل ، سرعان ما وجدنا السبب الجذري للتناقض المكتشف. مع زيادة عدد الحاويات ، كان من المفترض أن تزداد الصورة النقطية ، لكننا تركنا إحدى الحالات ، ونتيجة لذلك ، تخطينا أحيانًا الصورة النقطية لتغيير حجمها. في اختباراتنا المحلية ، لم يتم العثور على هذا الموقف ، وفي إصدار التصحيح الذي أرسله كيريل إلى الخط الرئيسي ، تمت إعادة صياغة الكود ، ولم يكن هناك خطأ هناك.
مع 4 محاولات ، عملت أنا و Kirill معًا لإنشاء مثل هذا التصحيح ، لمدة شهر قمنا بتشغيله في الاختبارات المحلية وفي نهاية فبراير أصدرنا تحديثًا بنواة ثابتة. لقد فحصنا بشكل انتقائي مقالب الأعطال الأخرى ، ووجدنا أيضًا الصورة النقطية الخاطئة في الحي ، واحتفلنا بالنصر وشطبنا الأخطاء القديمة على الماكرة.
ومع ذلك ، استمرت النساء المسنات في السقوط والسقوط. لقد تقلص عدد هذه الأنواع من تقارير الأخطاء ، لكنه لم يجف تمامًا.
بشكل عام ، كان هذا متوقعًا. عملاؤنا هم مضيفون. إنهم يكرهون بشدة إعادة تشغيل عقدهم ، لأن إعادة التشغيل == وقت التوقف == خسر المال. كما أننا لا نحب إصدار نواة بشكل متكرر. يعد الإصدار الرسمي للتحديث إجراءً شاقًا إلى حد ما يتطلب إجراء مجموعة من الاختبارات المختلفة. لذلك ، يتم إصدار نواة مستقرة جديدة كل ثلاثة أشهر تقريبًا.
لضمان التسليم الفوري لإصلاحات الأخطاء لعقد إنتاج العميل ، نستخدم تصحيحات ReadyKernel الحية. في رأيي ، لا أحد يفعل هذا سوانا. يستخدم Virtuozzo 7 إستراتيجية غير معتادة لاستخدام الفطائر الحية.
عادة ، lifepatch هو الأمن فقط. في بلدنا ، 3/4 من الإصلاحات عبارة عن إصلاحات للأخطاء. إصلاحات للأخطاء التي عثر عليها عملاؤنا بالفعل أو قد يتعثرون بسهولة في المستقبل. على نحو فعال ، لا يمكن تنفيذ مثل هذه الأشياء إلا لمجموعة أدوات التوزيع الخاصة بك: بدون تعليقات من المستخدمين ، من المستحيل فهم ما هو مهم بالنسبة لهم وما هو غير مهم.
الترقيع الحي ليس حلاً سحريًا بالتأكيد. من المستحيل عمومًا تصحيح كل شيء على التوالي - التكنولوجيا لا تسمح بذلك. لا يتم إضافة وظائف جديدة بهذه الطريقة أيضًا. ومع ذلك ، يتم إصلاح جزء كبير من الأخطاء بأبسط تصحيحات من سطر واحد ، والتي تعد ممتازة لتصحيح الحياة. في الحالات الأكثر تعقيدًا ، يجب "تعديل التصحيح الأصلي بشكل إبداعي باستخدام ملف" ، وفي بعض الأحيان تكون آلية الترقيع المباشر هي عربات التي تجرها الدواب ، ولكن الساحر Zhenya Shatokhin المصحح للحياة يعرف وظيفته تمامًا. في الآونة الأخيرة ، على سبيل المثال ، اكتشفخطأ ساحر في kpatch ، والذي ، لأسباب وجيهة ، يستحق كتابة أوبرا منفصلة.
مع تراكم إصلاحات الأخطاء المناسبة ، عادةً مرة كل أسبوع إلى أسبوعين ، تطلق Zhenya سلسلة أخرى من بقع ReadyKernel الحية. بعد الإصدار ، ينتشرون على الفور في عقد العميل ويمنعون الهجوم على أشعل النار الذي نعرفه بالفعل. وكل هذا دون إعادة تشغيل عقد العميل. وتحرر الألباب بشكل متكرر دون داع. الفوائد المستمرة.
ومع ذلك ، غالبًا ما يصل التصحيح المباشر إلى العملاء بعد فوات الأوان: المشكلة التي تم إغلاقها حدثت بالفعل ، لكن العقدة ، مع ذلك ، لم تتعطل بعد.
هذا هو السبب في أن ظهور تقارير أخطاء جديدة بالمشكلة التي تم إصلاحها بالفعل لم يكن غير متوقع بالنسبة لنا. أظهر تحليلها مرارًا وتكرارًا أعراضًا مألوفة: نواة قديمة ، قمامة في kmalloc-192 ، صورة نقطية "خاطئة" أمامها ، ورقعة حية غير محملة أو محملة مؤخرًا مع إصلاح.
إحدى هذه الحالات كانت OVZ-7188 من FastVPS ، والتي وصلت إلينا في نهاية شهر فبراير. "شكرًا جزيلاً على تقرير الخطأ. تعازينا. على الفور تشبه إلى حد كبير المشكلة المعروفة. إنه لأمر مؤسف أنه لا توجد بقع حية في OpenVZ. انتظر إصدار نواة مستقر ، أو انتقل إلى Virtuozzo أو استخدم حبات غير مستقرة مع خطأ. "
تعد تقارير الأخطاء من أهم الأشياء التي يقدمها لنا OpenVZ. يمنحنا البحث عنها فرصة لاكتشاف المشكلات الخطيرة قبل أن يتدخل أي من العملاء البدينين. لذلك ، على الرغم من المشكلة المعروفة ، طلبت مع ذلك ملء مقالب التصادم بالنسبة لنا.
تحليل أولهما أحبطني إلى حد ما: لم يتم العثور على الصورة النقطية "الخاطئة" أمام كائن kmalloc-192 "الملتوي".
بعد ذلك بقليل ، ظهرت المشكلة على النواة الجديدة. ثم آخر وآخر وآخر.
وجه الفتاة!
كيف ذلك؟ غير ثابت؟ لقد راجعت مرة أخرى المواد الخام - كل شيء على ما يرام ، التصحيح في مكانه ، ولا يتم فقد أي شيء.
مرة أخرى الفساد؟ في نفس المكان؟
كان علي أن أكتشفها مرة أخرى.
(ما هذا؟ انظر هنا )
في كل من مقالب التصادم الجديدة ، اصطدم التحقيق مرة أخرى بجسم kmalloc-192. بشكل عام ، بدا مثل هذا الكائن طبيعيًا تمامًا ، ولكن في بداية الكائن ، تم العثور على العنوان الخطأ في كل مرة. بتتبع علاقة الكائن ، وجدت أنه تم إلغاء اثنين من البايتات الداخلية في العنوان.
in all cases corrupted pointer contains nulls in 2 middle bytes: (mask 0xffffffff0000ffff)
0xffff9e2400003d80
0xffff969b00005b40
0xffff919100007000
0xffff90f30000ccc0
في الحالات الأولى المدرجة ، بدلاً من العنوان "الخطأ" 0xffff9e2400003d80 ، كان يجب أن يكون العنوان "الصحيح" 0xffff9e24740a3d80. تم العثور على حالة مماثلة في حالات أخرى.
اتضح أن بعض الرموز الدخيلة أبطلت كائننا بـ 2 بايت. السيناريو الأكثر احتمالا هو الاستخدام بعد الخالي ، عندما يقوم الكائن ، بعد تحريره ، بإلغاء بعض الحقول في وحدات البايت الأولى. تحققت من العناصر الأكثر استخدامًا ، ولكن لم يتم العثور على أي شيء مريب. مرة أخرى طريق مسدود.
FastVPSبناءً على طلبنا ، قمت بتشغيل نواة التصحيح مع KASAN لمدة أسبوع ، لكنها لم تساعد ، ولم يتم تكرار المشكلة مطلقًا. لقد طلبنا تسجيل slub_debug ، لكن هذا يتطلب إعادة تشغيل ، واستغرقت العملية وقتًا طويلاً. في شهري مارس وأبريل ، تعطلت العقد عدة مرات ، ولكن تم إيقاف slub_debug ، وهذا لم يعطنا معلومات جديدة.
ثم ساد الهدوء ، وتوقفت المشكلة عن التكاثر. انتهى أبريل ، ومار مايو - لم تكن هناك شلالات جديدة.
انتهى الانتظار في 7 يونيو - أخيرًا وصلت المشكلة إلى جوهرها مع تمكين slub_debug. أثناء التحقق من المنطقة الحمراء عند تحرير كائن slub_debug ، وجدت 2 بايت خارج الحد الأعلى. بعبارة أخرى ، اتضح أنه لم يكن خاليًا من الاستخدام ، كان الكائن السابق هو الجاني مرة أخرى. كان هناك هيكل طبيعي المظهر nf_ct_ext. تشير هذه البنية إلى تعقب الاتصال ، ووصف اتصال الشبكة الذي يستخدمه جدار الحماية.
ومع ذلك ، لم يتضح بعد سبب حدوث ذلك.
بدأت في النظر إلى conntrack: في إحدى الحاويات ، طرق شخص ما على المنفذ المفتوح 1720 باستخدام IPv6. حسب المنفذ والبروتوكول ، وجدت nf_conntrack_helper المقابل.
static struct nf_conntrack_helper nf_conntrack_helper_q931[] __read_mostly = {
{
.name = "Q.931",
.me = THIS_MODULE,
.data_len = sizeof(struct nf_ct_h323_master),
.tuple.src.l3num = AF_INET, <<<<<<<< IPv4
.tuple.src.u.tcp.port = cpu_to_be16(Q931_PORT),
.tuple.dst.protonum = IPPROTO_TCP,
.help = q931_help,
.expect_policy = &q931_exp_policy,
},
{
.name = "Q.931",
.me = THIS_MODULE,
.tuple.src.l3num = AF_INET6, <<<<<<<< IPv6
.tuple.src.u.tcp.port = cpu_to_be16(Q931_PORT),
.tuple.dst.protonum = IPPROTO_TCP,
.help = q931_help,
.expect_policy = &q931_exp_policy,
},
};
بمقارنة الهياكل ، لاحظت أن المساعد ipv6 لم يحدد data_len. دخلت في بوابة لمعرفة من أين أتت ، اكتشفت تصحيحًا لعام 2012.
الالتزام 1afc56794e03229fa53cfa3c5012704d226e1dec
المؤلف: بابلو نيرا أيوسو <pablo@netfilter.org>
التاريخ: الخميس 7 يونيو 12:11:50 2012 +0200 netfilter
: nf_ct_helper: تنفيذ البيانات الخاصة المساعد متغير الطول
يستخدم هذا التصحيح ملحقات conntrack ذات الطول المتغير الجديد.
بدلاً من استخدام union nf_conntrack_help الذي يحتوي على جميع
معلومات البيانات الخاصة المساعدة ، نخصص
مساحة متغيرة الطول لتخزين بيانات المساعد الخاص.
يتضمن هذا التصحيح تعديل جميع المساعدين الحاليين.
يتضمن أيضًا اثنين من رأس التضمين لتجنب التجميع
تحذيرات.
أضاف التصحيح حقلاً جديدًا .data_len إلى المساعد ، والذي يشير إلى مقدار الذاكرة الذي يحتاجه معالج اتصال الشبكة المقابل. كان من المفترض أن يحدد التصحيح .data_len لجميع nf_conntrack_helpers المتاحة في ذلك الوقت ، لكنه غاب عن البنية التي وجدتها.
نتيجة لذلك ، اتضح أن الاتصال عبر ipv6 بالمنفذ المفتوح 1720 أطلق وظيفة q931_help () ، فقد كتب إلى بنية لم يخصص لها أحد ذاكرة. أدى فحص المنفذ البسيط إلى إبطال بضع بايتات ، كما أدى إرسال رسالة بروتوكول عادية إلى ملء الهيكل بمعلومات أكثر أهمية ، ولكن على أي حال ، فإن ذاكرة شخص آخر قد تلاشت وعاجلاً أم آجلاً أدى ذلك إلى انهيار العقدة. أعاد
فلوريان ويستفال تصميم الكود مرة أخرى في عام 2017وأزلت .data_len ، وذهبت المشكلة التي اكتشفتها دون أن يلاحظها أحد.
على الرغم من حقيقة أن الخطأ لم يعد موجودًا في الخط الرئيسي الحالي لـ linux kernel ، فإن المشكلة موروثة عن طريق مجموعة من توزيعات Linux ، بما في ذلك RHEL7 / CentOS7 و SLES 11 & 12 و Oracle Unbreakable Enterprise Kernel 3 & 4 و Debian 8 & 9 و نظام التشغيل Ubuntu 14.04 و 16.04 LTS.
تم إعادة إنتاج الخطأ بشكل تافه على عقدة الاختبار ، سواء في جوهرنا أو على RHEL7 الأصلي. الأمان الصريح: تلف الذاكرة المُدارة عن بُعد. حيث يكون منفذ 1720 ipv6 مفتوحًا - عمليا ping of death.
في 9 حزيران (يونيو) ، قمت بعمل تصحيح من سطر واحد مع وصف غامض وأرسلته إلى الخط الرئيسي. لقد أرسلت الوصف التفصيلي إلى Red Hat Bugzilla وكتبته بشكل منفصل إلى Red Hat Security.
تطورت أحداث أخرى بدون مشاركتي.
في 15 يونيو ، أصدرت Zhenya Shatokhin التصحيح المباشر لـ ReadyKernel لنواةنا القديمة.
https://readykernel.com/patch/Virtuozzo-7/readykernel-patch-131.10-108.0-1.vl7/
أصدرنا في 18 يونيو نواة مستقرة جديدة في Virtuozzo و OpenVz.
https://virtuozzosupport.force.com/s/article/VZA-2020-043
في 24 يونيو ، عيّنت Red Hat Security معرف CVE للخلل
https://access.redhat.com/security/cve/CVE-2020-14305
المشكلة حصلت على تأثير معتدل مع درجة عالية غير عادية من CVSS v3 8.1 ، وعلى مدار الأيام القليلة التالية ، استجابت توزيعات
SUSE الأخرى لخلل القبعة العامة https://bugzilla.suse.com/show_bug.cgi؟id=CVE-2020-14305
Debian https: / /security-tracker.debian.org/tracker/CVE-2020-14305
Ubuntuhttps://people.canonical.com/~ubuntu-security/cve/2020/CVE-2020-14305.html
في 6 يوليو ، أصدرت KernelCare نسخة حية عن التوزيعات المتأثرة.
https://blog.kernelcare.com/new-kernel-vulnerability-found-by-virtuozzo-live-patched-by-kernelcare
في 9 يوليو تم إصلاح المشكلة في نواة Linux المستقرة 4.9.230 و 4.4.230.
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/؟h=linux-4.9.y&id=396ba2fc4f27ef6c44bbc0098bfddf4da76dc4c9
التوزيعات ، ومع ذلك ، لم تغلق الفجوة ...
قلت لشريكي كوستيا خورنكو: "انظر ، كوستيا ، ضربت قذيفة لدينا الحفرة نفسها مرتين! أنا وواحد وصول إلى ما بعد نهاية الكائن في المرة الأخيرة التي قابلت فيها nepoymi ، وزارنا هنا مرتين على التوالي. قل لي ، هل هو احتمالية مربعة؟ أم لا مربع؟
- الاحتمالية مربعة ، نعم. ولكن هنا عليك أن تنظر - ما هو الاحتمال؟ الاحتمال التربيعي لحدث حدوث أخطاء غير عادية مرتين متتاليتين بالضبط. إنه على التوالي.
حسنًا ، كوستيا ذكي ، إنه يعرف أفضل.