إضافة وضع علامات الذاكرة (MTE)

أسباب إضافة وضع علامات الذاكرة (MTE)؟

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

قدَّم Armv9 إضافة وضع علامات الذاكرة (MTE)، وهي إضافة للأجهزة تتيح لك اكتشاف أخطاء "الاستخدام بعد تفريغها" و"تجاوز المخزن المؤقت" في رمزك الأصلي.

الحصول على الدعم

بدءًا من نظام التشغيل Android 13، تتوافق أجهزة محدَّدة مع إضافة وضع علامات الذاكرة (MTE). للتحقّق مما إذا كان جهازك يعمل مع تفعيل إضافة وضع علامات الذاكرة (MTE)، عليك تنفيذ الأمر التالي:

adb shell grep mte /proc/cpuinfo

إذا كانت النتيجة Features : [...] mte، يعني ذلك أنّ جهازك يعمل مع تفعيل إضافة وضع علامات الذاكرة (MTE).

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

أوضاع تشغيل إضافة وضع علامات الذاكرة (MTE)

توفِّر إضافة وضع علامات الذاكرة (MTE) وضعَين: "المزامنة" و"ASYNC". ويوفّر وضع المزامنة معلومات تشخيصية أفضل، وبالتالي فهو أكثر ملاءمة لأغراض التطوير، في حين يتميّز وضع ASYNC بأداء عالي يتيح لك تفعيله للتطبيقات التي تم إصدارها.

الوضع المتزامن (مزامنة)

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

في حالة عدم تطابق العلامة، ينهي المعالج العملية عند التحميل المسيء أو يخزّن التعليمات باستخدام SIGSEGV (باستخدام si_code SEGV_MTESERR) ومعلومات كاملة حول الوصول إلى الذاكرة وعنوان الخطأ.

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

علاوةً على ذلك، عند التشغيل في وضع "المزامنة"، يسجّل تخصيص Android تتبُّع تسلسل استدعاء الدوال البرمجية لكل عملية تخصيص ومساحة عرض ويستخدمها لتقديم تقارير أخطاء أفضل تتضمن توضيحًا لخطأ في الذاكرة، مثل عمليات الاستخدام بعد تفريغها أو تجاوز المخزن المؤقت، وعمليات تتبُّع تسلسل استدعاء الدوال البرمجية لأحداث الذاكرة ذات الصلة (راجِع التعرُّف على تقارير إضافة وضع علامات الذاكرة (MTE) للحصول على مزيد من التفاصيل). توفِّر هذه التقارير مزيدًا من المعلومات السياقية وتسهّل تتبُّع الأخطاء وإصلاحها مقارنةً بوضع ASYNC.

الوضع غير المتزامن (ASYNC)

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

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

تفعيل إضافة وضع علامات الذاكرة (MTE)

لجهاز واحد

لإجراء تجارب، يمكن استخدام تغييرات توافق التطبيق لضبط القيمة التلقائية للسمة memtagMode لتطبيق لا يحدّد أي قيمة في البيان (أو يحدد "default").

ويمكن العثور على هذه الإعدادات ضمن النظام > الإعدادات المتقدّمة > خيارات المطوّرين > التغييرات في التوافق مع التطبيقات في قائمة الإعدادات العامة. ويؤدي ضبط NATIVE_MEMTAG_ASYNC أو NATIVE_MEMTAG_SYNC إلى تفعيل إضافة وضع علامات الذاكرة (MTE) لتطبيق معيّن.

بدلاً من ذلك، يمكن ضبطها باستخدام الأمر am على النحو التالي:

  • في وضع المزامنة: $ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
  • بالنسبة إلى وضع ASYNC: $ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name

في Gradle

يمكنك تفعيل إضافة وضع علامات الذاكرة (MTE) لجميع إصدارات تصحيح الأخطاء لمشروع Gradle من خلال

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application android:memtagMode="sync" tools:replace="android:memtagMode"/>
</manifest>

إلى app/src/debug/AndroidManifest.xml. سيؤدي هذا الإجراء إلى إلغاء memtagMode في ملف البيان وذلك باستخدام المزامنة لإصدارات تصحيح الأخطاء.

يمكنك بدلاً من ذلك تفعيل إضافة وضع علامات الذاكرة (MTE) لجميع إصدارات نوع الإصدار المخصّص. لإجراء ذلك، يجب إنشاء BuildType الخاص بك ووضع XML إلى app/src/<name of buildType>/AndroidManifest.xml.

لملف APK على أي جهاز متوافق

تكون إضافة وضع علامات الذاكرة (MTE) غير مفعَّلة تلقائيًا. ويمكن للتطبيقات التي تريد استخدام إضافة وضع علامات الذاكرة (MTE) تنفيذ ذلك من خلال ضبط android:memtagMode ضمن العلامة <application> أو <process> في AndroidManifest.xml.

android:memtagMode=(off|default|sync|async)

عند ضبط السمة على علامة <application>، تؤثر السمة في جميع العمليات التي يستخدمها التطبيق، ويمكن إلغاء هذه السمة للعمليات الفردية من خلال ضبط العلامة <process>.

تشغيل تطبيقك

بعد تفعيل إضافة وضع علامات الذاكرة (MTE)، يمكنك استخدام تطبيقك واختباره كالمعتاد. إذا تم رصد مشكلة تتعلّق بأمان الذاكرة، يتعطّل تطبيقك مع عرض ضريح يشبه ما يلي (ملاحظة: SIGSEGV التي تتضمّن SEGV_MTESERR للمزامنة أو SEGV_MTEAERR لنظام ASYNC):

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)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report

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

المستخدمون المتقدمون: استخدام إضافة وضع علامات الذاكرة (MTE) في التخصيص الخاص بك

لاستخدام إضافة وضع علامات الذاكرة (MTE) للذاكرة غير المخصّصة من خلال تخصيصات النظام العادية، تحتاج إلى تعديل التخصيص لوضع علامة على الذاكرة والمؤشرات.

يجب تخصيص صفحات التخصيص باستخدام PROT_MTE في علامة prot mmap (أو mprotect).

يجب محاذاة جميع التوزيعات ذات العلامات مع 16 بايت، حيث لا يمكن تعيين العلامات إلا للأجزاء التي يبلغ حجمها 16 بايت (المعروفة أيضًا باسم الحبيبات).

بعد ذلك، قبل عرض مؤشر، عليك استخدام تعليمات IRG لإنشاء علامة عشوائية وتخزينها في المؤشر.

اتّبِع التعليمات التالية لوضع علامة على الذاكرة الأساسية:

  • STG: وضع علامة على حبيبة واحدة بحجم 16 بايت
  • ST2G: ضَع علامة على حبيبات بحجم 16 بايت.
  • DC GVA: سطر ذاكرة التخزين المؤقت للعلامة نفسه

بدلاً من ذلك، تؤدي التعليمات التالية أيضًا إلى عدم إعداد الذاكرة:

  • STZG: علامة والإعداد بحبيبات واحدة بحجم 16 بايت
  • STZ2G: علامة وبدء إعداد حبيبين بحجم 16 بايت
  • DC GZVA: العلامة وذاكرة التخزين المؤقت لإعدادها باستخدام العلامة نفسها

تجدر الإشارة إلى أنّ هذه التعليمات غير متاحة على وحدات المعالجة المركزية القديمة، لذا يجب تشغيلها بشكل مشروط عند تفعيل إضافة وضع علامات الذاكرة (MTE). يمكنك التحقق مما إذا كانت إضافة وضع علامات الذاكرة (MTE) مفعّلة لهذه العملية من عدمه:

#include <sys/prctl.h>

bool runningWithMte() {
      int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
      return mode != -1 && mode & PR_MTE_TCF_MASK;
}

قد تجد أنّ عملية تنفيذ ملف تعريف الارتباط مفيدة كمرجع.

مزيد من المعلومات

يمكنك الاطّلاع على مزيد من المعلومات في دليل مستخدم MTE لنظام التشغيل Android الذي كتبه Arm.