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

لماذا تستخدم إضافة MTE؟

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

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

التحقق من الدعم

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

adb shell grep mte /proc/cpuinfo

إذا كانت النتيجة Features : [...] mte، يعني ذلك أنّ جهازك يعمل بميزة MTE.

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

أوضاع التشغيل الخاصة بـ MTE

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

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

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

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

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

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

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

تم تحسين هذا الوضع لتحقيق أداء أعلى من دقة تقارير الأخطاء، ويمكن استخدامه لرصد أخطاء "أمان الذاكرة" بدون أي مخاطرة. في حال عدم تطابق العلامة، يستمر المعالج في التنفيذ إلى أن يتم إدخال أقرب إدخال للنواة (مثل syscall أو مقاطعة الموقّت)، حيث ينهي العملية باستخدام 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) لجميع إصدارات نوع إنشاء مخصّص. لإجراء ذلك، أنشِئ نوع التصميم الخاص بك وأضِف 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 للتطبيق، يجب إعادة إنشاء الرمز باستخدام قياس حالة التطبيق. سيتم تشغيل التطبيق الناتج على الأجهزة المتوافقة مع MTE فقط.

لإنشاء الرمز الأصلي (JNI) لتطبيقك باستخدام ميزة MTE، عليك اتّباع الخطوات التالية:

لعبة ndk-build

في ملف Application.mk:

APP_CFLAGS := -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag
APP_LDFLAGS := -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag

إنشاء فيديوهات Shorts

لكل هدف في CMakeLists.txt:

target_compile_options(${TARGET} PUBLIC -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag)
target_link_options(${TARGET} PUBLIC -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag)

تشغيل تطبيقك

بعد تفعيل إضافة وضع علامات الذاكرة (MTE)، يمكنك استخدام تطبيقك واختباره كالمعتاد. إذا تم رصد مشكلة في أمان الذاكرة، يتعطّل تطبيقك ويظهر رمز مميز مشابه لما يلي (يُرجى ملاحظة أنّ SIGSEGV مع SEGV_MTESERR for SYNC أو SEGV_MTEAERR for 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 في مستندات 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;
}

يمكنك الاستفادة من تنفيذ scudo كمرجع.

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

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