فهم تقارير MTE

تعطل SIGSEGV بالرمز 9 (SEGV_MTESERR) أو الرمز 8 (SEGV_MTEAERR) عبارة عن أخطاء في وضع علامات على الذاكرة. ملحق وضع علامات الذاكرة (MTE) هو إحدى ميزات Armv9 المدعومة في Android 12 والإصدارات الأحدث. MTE هو تطبيق أجهزة للذاكرة الموسومة. وهو يوفر حماية دقيقة للذاكرة من أجل اكتشاف أخطاء سلامة الذاكرة والتخفيف منها.

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

يحتوي MTE على وضعين: متزامن (أو "متزامن") وغير متزامن (أو "غير متزامن"). يعمل الأول بشكل أبطأ ولكنه يوفر تشخيصًا أكثر دقة. يعمل الأخير بشكل أسرع، لكن يمكنه تقديم تفاصيل تقريبية فقط. سنقوم بتغطية كليهما بشكل منفصل، نظرًا لأن التشخيص مختلف قليلاً.

الوضع المتزامن MTE

في وضع MTE المتزامن ("المزامنة")، يتعطل 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 أيضًا تتبعات خلفية إضافية، وليس فقط تلك من نقطة الاكتشاف.

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

لا توفر أخطاء "Buffer Overflow" و"Buffer Underflow" سوى مسارًا إضافيًا "مخصصًا بواسطة" للمكدس، نظرًا لأنه بحكم التعريف لم يتم إلغاء تخصيصها بعد (أو ستظهر على أنها "استخدام بعد الإصدار المجاني"):

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...]

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

الأسباب التفصيلية تحديد الاستدلال

يجب أن يُظهر "سبب" العطل تخصيص الذاكرة الذي تم اشتقاق المؤشر الذي تم الوصول إليه في الأصل منه. لسوء الحظ، ليس لدى أجهزة MTE طريقة للترجمة من مؤشر بعلامة غير متطابقة إلى تخصيص. لشرح عطل SEGV_MTESERR، يقوم Android بتحليل البيانات التالية:

  • عنوان الخطأ (بما في ذلك علامة المؤشر).
  • قائمة بعمليات تخصيص الكومة الأخيرة مع تتبعات المكدس وعلامات الذاكرة.
  • التخصيصات الحالية (المباشرة) القريبة وعلامات الذاكرة الخاصة بها.

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

أي ذاكرة حية قريبة تتطابق فيها علامة الذاكرة مع علامة عنوان الخطأ هي سبب محتمل لـ "تجاوز سعة المخزن المؤقت" (أو "تجاوز سعة المخزن المؤقت").

فالمخصصات الأقرب إلى الخطأ – سواء في الزمان أو المكان – تعتبر أرجح من تلك البعيدة.

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

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

الوضع غير المتزامن MTE

في وضع MTE غير المتزامن ("غير المتزامن")، يتعطل SIGSEGV مع الرمز 8 (SEGV_MTEAERR).

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

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

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

مواضيع متقدمة

ضمن الغطاء، تعمل علامات الذاكرة عن طريق تعيين قيمة علامة عشوائية 4 بت (0..15) لكل تخصيص كومة. يتم تخزين هذه القيمة في منطقة بيانات التعريف الخاصة التي تتوافق مع ذاكرة الكومة المخصصة. يتم تعيين نفس القيمة للبايت الأكثر أهمية لمؤشر الكومة الذي يتم إرجاعه من وظائف مثل malloc() أو عامل التشغيل 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  ..'.}...........