إنّ الأعطال في SIGSEGV التي تظهر مع الرمز 9 (SEGV_MTESERR) أو الرمز 8 (SEGV_MTEAERR) هي أخطاء وضع العلامات على الذاكرة. إضافة وضع علامات الذاكرة (MTE) هي إحدى ميزات Armv9 المتوافقة مع الإصدار 12 من نظام التشغيل Android والإصدارات الأحدث. إنّ ميزة "إضافة وضع علامات الذاكرة" (MTE) هي عملية تنفيذ للذاكرة المُصنَّفة على مستوى الأجهزة. ويقدّم هذا الإصدار حماية دقيقة للذاكرة لرصد الأخطاء المتعلقة بأمان الذاكرة والحدّ منها.
في C/C++، لا يمكن استخدام المؤشر الذي يتم إرجاعه من استدعاء malloc() أو عامل التشغيل new() أو الدوال المشابهة إلا للوصول إلى الذاكرة ضمن حدود هذا التخصيص، وخلال فترة التخصيص فقط (بدون تحريره أو حذفه). تُستخدَم أداة MTE في Android لرصد مخالفات هذه القاعدة، والتي يُشار إليها في تقارير الأعطال باسم "تجاوز المخزن المؤقت"/"انخفاض مستوى المخزن المؤقت" و مشاكل "استخدام الذاكرة بعد تفريغها".
يتضمّن MTE وضعَين: الوضع المتزامن (أو "المزامنة") والوضع غير المتزامن (أو "غير المتزامن"). يعمل الخيار الأول ببطء أكبر ولكنه يقدّم بيانات تشخيص أكثر دقة. يعمل هذا الأخير بشكل أسرع، ولكن لا يمكنه تقديم سوى تفاصيل تقريبية. سنتناول كلاهما بشكل منفصل، لأنّ بيانات التشخيص تختلف قليلاً.
وضع إضافة علامات الذاكرة المتزامن
في وضع MTE المتزامن ("sync")، يتعطّل SIGSEGV بالرمز 9 (SEGV_MTESERR).
pid: 13935, tid: 13935, name: sanitizer-statu >>> sanitizer-status <<< uid: 0 tagged_addr_ctrl: 000000000007fff3 signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0 Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0 x0 0000007cd94227cc x1 0000007cd94227cc x2 ffffffffffffffd0 x3 0000007fe81919c0 x4 0000007fe8191a10 x5 0000000000000004 x6 0000005400000051 x7 0000008700000021 x8 0800007ae92853a0 x9 0000000000000000 x10 0000007ae9285000 x11 0000000000000030 x12 000000000000000d x13 0000007cd941c858 x14 0000000000000054 x15 0000000000000000 x16 0000007cd940c0c8 x17 0000007cd93a1030 x18 0000007cdcac6000 x19 0000007fe8191c78 x20 0000005800eee5c4 x21 0000007fe8191c90 x22 0000000000000002 x23 0000000000000000 x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000 x28 0000000000000000 x29 0000007fe8191b70 lr 0000005800eee0bc sp 0000007fe8191b60 pc 0000005800eee0c0 pst 0000000060001000 backtrace: #00 pc 00000000000010c0 /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30) #01 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30) #02 pc 00000000000019cc /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30) #03 pc 00000000000487d8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331) deallocated by thread 13935: #00 pc 000000000004643c /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331) #01 pc 00000000000421e4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331) #02 pc 00000000000010b8 /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30) #03 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30) allocated by thread 13935: #00 pc 0000000000042020 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331) #01 pc 0000000000042394 /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331) #02 pc 000000000003cc9c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331) #03 pc 00000000000010ac /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30) #04 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
تحتوي جميع تقارير الأعطال في MTE على سجلّ التسجيل المعتاد والتتبّع العكسي للنقطة التي تم فيها رصد المشكلة. سيحتوي سطر "السبب:" لأي خطأ رصدته أداة MTE على "[MTE]" كما هو موضّح في المثال أعلاه، بالإضافة إلى مزيد من التفاصيل. في هذه الحالة، كان النوع المحدّد من الخطأ الذي تم رصده هو الاستخدام بعد تحرير الذاكرة، ويُعلمنا الرمز "0 بايت في عملية تخصيص 32 بايت في 0x7ae92853a0" بحجم عملية التخصيص وعنوانه، وكذلك القيمة المرجعية في عملية التخصيص التي حاولنا الوصول إليها.
تتضمّن تقارير الأعطال في MTE أيضًا عمليات تتبُّع إضافية، وليس فقط عملية التتبُّع من نقطة رصد العُطل.
تُضيف أخطاء "الاستخدام بعد تحرير الذاكرة" قسمَي "تم إلغاء تخصيصها بواسطة" و"تم تخصيصها بواسطة" إلى ملف "تجميع الأعطال"، ما يعرض عمليات تتبُّع تسلسل استدعاء الدوالّ في وقت إلغاء تخصيص هذه الذاكرة (قبل استخدامها) والوقت الذي تم تخصيصها فيه سابقًا. وتُعلمك هذه الأرقام أيضًا بخيط المعالجة الذي تم فيه تخصيص الذاكرة أو إلغاء تخصيصها. في هذا المثال البسيط، يكون كلّ من مؤشر التتبّع ومؤشر تخصيص الذاكرة ومؤشر تحرير الذاكرة متطابقًا، ولكن في الحالات الأكثر تعقيدًا في الحياة الواقعية، قد لا يكون ذلك صحيحًا بالضرورة، ويمكن أن يكون معرفة اختلافها دليلاً مهمًا في العثور على خطأ مرتبط بالتوازي.
لا تقدّم أخطاء "تدفّق بيانات زائد في المخزن المؤقت" و"تدفّق بيانات غير كافٍ في المخزن المؤقت" سوى تتبع إضافي "للمُخصّص من خلال" تتبُّع تسلسل استدعاء الدوال البرمجية، لأنّه لم يتمّ إلغاء تخصيصها بعد (أو ستظهر على أنّها "استخدام بعد تحرير الذاكرة"):
Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0 [...] backtrace: [...] allocated by thread 13949:
يُرجى ملاحظة استخدام كلمة "يمين" هنا: هذا يعني أنّنا نخبرك بعدد وحدات البايت التي تجاوزت نهاية عملية التخصيص التي حدث فيها الوصول غير الصحيح. أمّا في حالة حدوث تدفّق بيانات غير صحيح، فسيتم استخدام كلمة "يسار"، وسيكون عددًا من وحدات البايت قبل بدء عملية التخصيص.
أسباب محتملة متعدّدة
تحتوي تقارير SEGV_MTESERR أحيانًا على السطر التالي:
Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.
يحدث ذلك عندما يكون هناك عدة مصادر محتملة للخطأ، ولا يمكننا تحديد السبب الفعلي. نطبع ما يصل إلى 3 من هذه الكلمات المرشحة بترتيب تقريبي حسب الاحتمالية، ونترك التحليل للمستخدم.
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x400007b43063db5 backtrace: [stack...] Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability. Cause: [MTE]: Use After Free, 5 bytes into a 10-byte allocation at 0x7b43063db0 deallocated by thread 6663: [stack...] allocated by thread 6663: [stack...] Cause: [MTE]: Use After Free, 5 bytes into a 6-byte allocation at 0x7b43063db0 deallocated by thread 6663: [stack...] allocated by thread 6663: [stack...]
في المثال أعلاه، رصدنا عمليتين حديثتَين لتحديد مساحة في عنوان الذاكرة نفسه الذي يمكن أن يكون الهدف المقصود للوصول غير الصالح إلى الذاكرة. يمكن أن يحدث ذلك عند إعادة استخدام عمليات التخصيص للذاكرة الفارغة، على سبيل المثال، إذا كان لديك تسلسل مثل new, free, new, free, new, free, access. تتم طباعة عملية التوزيع الأحدث أولاً.
الإعدادات الإرشادية التفصيلية لتحديد السبب
من المفترض أن يعرض "سبب" الأعطال عملية تخصيص الذاكرة التي تم اشتقاق المؤشر الذي تم الوصول إليه منها في الأصل. لا تتوفّر في أجهزة MTE طريقة للترجمة من مؤشر يتضمّن علامة غير متطابقة إلى عملية تخصيص. لتفسير تعطُّل SEGV_MTESERR، يحلِّل Android البيانات التالية:
- عنوان الخطأ (بما في ذلك علامة المؤشر)
- قائمة بعمليات تخصيص الذاكرة في الشريحة الأخيرة مع عمليات تتبُّع تسلسل استدعاء الدوال البرمجية وعلامات الذاكرة
- عمليات التخصيص الحالية (الحية) القريبة وعلامات الذاكرة المرتبطة بها
إنّ أي ذاكرة تم إلغاء تخصيصها مؤخرًا في عنوان الخطأ حيث تتطابق علامة الذاكرة مع علامة عنوان الخطأ هي سبب محتمل "لاستخدام الذاكرة بعد تحريرها".
إنّ أي ذاكرة نشطة قريبة تتطابق فيها علامة الذاكرة مع علامة عنوان الخطأ هي سبب محتمل "للفيض في المخزن المؤقت" (أو "النقص في المخزن المؤقت").
ويُعتبَر أنّ عمليات التوزيع الأقرب إلى الخطأ - سواء في الوقت أو في المساحة - أكثر احتمالًا من عمليات التوزيع البعيدة.
بما أنّه يتم إعادة استخدام الذاكرة التي تم إلغاء تخصيصها غالبًا، وعدد قيم العلامات المختلفة صغير (أقل من 16)، من الشائع العثور على عدة مرشّحين محتمَلين، ولا تتوفّر طريقة للعثور تلقائيًا على السبب الحقيقي. لهذا السبب، تُدرِج تقارير MTE أحيانًا عدة أسباب محتملة.
ننصح مطوّر التطبيق بالاطّلاع على الأسباب المحتمَلة بدءًا من السبب الأكثر ترجيحًا. غالبًا ما يكون من السهل فلترة الأسباب غير ذات الصلة استنادًا إلى تتبع تسلسل استدعاء الدوال البرمجية.
وضع MTE غير المتزامن
في الوضع غير المتزامن ("async") لواجهة MTE، يتعطّل SIGSEGV بالرمز 8 (SEGV_MTEAERR).
لا تحدث أخطاء SEGV_MTEAERR على الفور عندما ينفِّذ برنامج عملية وصول غير صالحة إلى الذاكرة. يتم رصد المشكلة بعد فترة وجيزة من حدوث الحدث، ويتم إنهاء البرنامج في تلك المرحلة بدلاً من ذلك. وعادةً ما تكون هذه النقطة هي طلب النظام التالي، ولكن يمكن أن تكون أيضًا مقاطعة للموقّت، أي أي انتقال من مساحة المستخدم إلى النواة.
لا تحافظ أخطاء SEGV_MTEAERR على عنوان الذاكرة (يظهر دائمًا على النحو التالي: "-------"). يتوافق التتبّع العكسي مع لحظة رصد الشرط (أي في طلب النظام التالي أو تبديل سياق آخر)، وليس عند تنفيذ الوصول غير الصالح.
وهذا يعني أنّ تتبع تسلسل استدعاء الدوال البرمجية "الرئيسي" في الأعطال غير المتزامنة لـ MTE يكون عادةً غير ذي صلة. وبالتالي، من الصعب جدًا تصحيح أخطاء الوضع غير المتزامن مقارنةً بأخطاء الوضع المتزامن. ويمكن فهمها على أنّها تشير إلى وجود خطأ في الذاكرة في الرمز البرمجي المجاور في سلسلة المحادثات المحدّدة. قد توفّر السجلات في أسفل ملف السجلّ المرجعي لمعلومات عن ما حدث فعليًا. بخلاف ذلك، فإنّ الإجراء المقترَح هو إعادة إنتاج الخطأ في وضع المزامنة واستخدام بيانات التشخيص الأفضل التي يوفّرها وضع المزامنة.
مواضيع متقدّمة
في الأساس، تعمل ميزة وضع العلامات على الذاكرة من خلال تعيين قيمة علامة عشوائية بسعة 4 بت (0..15) لكل عملية تخصيص للمساحة في الذاكرة. ويتم تخزين هذه القيمة في منطقة بيانات وصفية خاصة تتوافق مع ذاكرة الشريحة المخصّصة. يتم تعيين القيمة نفسها إلى البايت الأكثر أهمية في مؤشر الحِزمة الذي يتم إرجاعه من دوال مثل malloc() أو operator new().
عند تفعيل فحص العلامة في العملية، تقارن وحدة المعالجة المركزية تلقائيًا أعلى بايت من المؤشر بعلامة الذاكرة عند كل عملية وصول إلى الذاكرة. في حال عدم تطابق العلامات، تُرسِل وحدة المعالجة المركزية إشارة خطأ تؤدي إلى حدوث عطل.
ونظرًا للعدد المحدود لقيم العلامات المحتملة، يكون هذا النهج احتماليًا. من المحتمل أن يكون لأيّ موقع ذاكرة لا يمكن الوصول إليه باستخدام مؤشر معيّن قيمة علامة مختلفة، ما قد يؤدي إلى حدوث عطل، مثل المواقع خارج الحدود أو بعد إلغاء التخصيص ("المؤشر المعلّق"). هناك احتمالية بنسبة% 7 تقريبًا لعدم رصد أيّ حالة فردية من الأخطاء. ولأنّه يتمّ منح قيم العلامات بشكل عشوائي، هناك احتمالية مستقلة تبلغ% 93 تقريبًا لرصد الخطأ في المرة التالية التي يحدث فيها.
يمكن الاطّلاع على قيم العلامات في حقل عنوان الخطأ وكذلك في تفريغ السجلّ، كما هو موضّح أدناه. يمكن استخدام هذا القسم للتحقّق من ضبط العلامات بطريقة سليمة، بالإضافة إلى الاطّلاع على عمليات تخصيص الذاكرة الأخرى المجاورة التي تحمل قيمة العلامة نفسها، لأنّها قد تكون أسبابًا محتملة للخطأ غير تلك المُدرَجة في التقرير. نتوقع أن يكون هذا الإجراء مفيدًا بشكل أساسي للأشخاص الذين يعملون على تنفيذ MTE نفسه أو مكوّنات النظام الأخرى ذات المستوى المنخفض، بدلاً من المطوّرين.
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0800007ae92853a0 Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0 x0 0000007cd94227cc x1 0000007cd94227cc x2 ffffffffffffffd0 x3 0000007fe81919c0 x4 0000007fe8191a10 x5 0000000000000004 x6 0000005400000051 x7 0000008700000021 x8 0800007ae92853a0 x9 0000000000000000 x10 0000007ae9285000 x11 0000000000000030 x12 000000000000000d x13 0000007cd941c858 x14 0000000000000054 x15 0000000000000000 x16 0000007cd940c0c8 x17 0000007cd93a1030 x18 0000007cdcac6000 x19 0000007fe8191c78 x20 0000005800eee5c4 x21 0000007fe8191c90 x22 0000000000000002 x23 0000000000000000 x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000 x28 0000000000000000 x29 0000007fe8191b70 lr 0000005800eee0bc sp 0000007fe8191b60 pc 0000005800eee0c0 pst 0000000060001000
يظهر أيضًا قسم خاص بعنوان "علامات الذاكرة" في تقرير الأعطال الذي يعرض علامات الذاكرة حول عنوان الخطأ. في المثال أدناه، لم تتطابق علامة المؤشر "4" مع علامة الذاكرة "a".
Memory tags around the fault address (0x0400007b43063db5), one tag per 16 bytes: 0x7b43063500: 0 f 0 2 0 f 0 a 0 7 0 8 0 7 0 e 0x7b43063600: 0 9 0 8 0 5 0 e 0 f 0 c 0 f 0 4 0x7b43063700: 0 b 0 c 0 b 0 2 0 1 0 4 0 7 0 8 0x7b43063800: 0 b 0 c 0 3 0 a 0 3 0 6 0 b 0 a 0x7b43063900: 0 3 0 4 0 f 0 c 0 3 0 e 0 0 0 c 0x7b43063a00: 0 3 0 2 0 1 0 8 0 9 0 4 0 3 0 4 0x7b43063b00: 0 5 0 2 0 5 0 a 0 d 0 6 0 d 0 2 0x7b43063c00: 0 3 0 e 0 f 0 a 0 0 0 0 0 0 0 4 =>0x7b43063d00: 0 0 0 a 0 0 0 e 0 d 0 [a] 0 f 0 e 0x7b43063e00: 0 7 0 c 0 9 0 a 0 d 0 2 0 0 0 c 0x7b43063f00: 0 0 0 6 0 b 0 8 0 3 0 0 0 5 0 e 0x7b43064000: 0 d 0 2 0 7 0 a 0 7 0 a 0 d 0 8 0x7b43064100: 0 b 0 2 0 b 0 4 0 1 0 6 0 d 0 4 0x7b43064200: 0 1 0 6 0 f 0 2 0 f 0 6 0 5 0 c 0x7b43064300: 0 1 0 4 0 d 0 6 0 f 0 e 0 1 0 8 0x7b43064400: 0 f 0 4 0 3 0 2 0 1 0 2 0 5 0 6
تعرض أقسام السجلّ الدائم التي تعرض محتوى الذاكرة حول جميع قيم السجلّ أيضًا قيم علاماتها.
memory near x10 ([anon:scudo:primary]): 0000007b4304a000 7e82000000008101 000003e9ce8b53a0 .......~.S...... 0700007b4304a010 0000200000006001 0000000000000000 .`... .......... 0000007b4304a020 7c03000000010101 000003e97c61071e .......|..a|.... 0200007b4304a030 0c00007b4304a270 0000007ddc4fedf8 p..C{.....O.}... 0000007b4304a040 84e6000000008101 000003e906f7a9da ................ 0300007b4304a050 ffffffff00000042 0000000000000000 B............... 0000007b4304a060 8667000000010101 000003e9ea858f9e ......g......... 0400007b4304a070 0000000100000001 0000000200000002 ................ 0000007b4304a080 f5f8000000010101 000003e98a13108b ................ 0300007b4304a090 0000007dd327c420 0600007b4304a2b0 .'.}......C{... 0000007b4304a0a0 88ca000000010101 000003e93e5e5ac5 .........Z^>.... 0a00007b4304a0b0 0000007dcc4bc500 0300007b7304cb10 ..K.}......s{... 0000007b4304a0c0 0f9c000000010101 000003e9e1602280 ........."`..... 0900007b4304a0d0 0000007dd327c780 0700007b7304e2d0 ..'.}......s{... 0000007b4304a0e0 0d1d000000008101 000003e906083603 .........6...... 0a00007b4304a0f0 0000007dd327c3b8 0000000000000000 ..'.}...........