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

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

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

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

التحقّق من توفّر الدعم

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

adb shell grep mte /proc/cpuinfo

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

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

الأجهزة المتوافقة مع إضافات وضع علامات الذاكرة (MTE)

من المعروف أنّ الأجهزة التالية متوافقة مع MTE:

  • Pixel 8 (Shiba)
  • Pixel 8 Pro (Husky)
  • Pixel 8a (Akita)
  • Pixel 9 (Tokay)
  • Pixel 9 Pro (Caiman)
  • ‫Pixel 9 Pro XL (Komodo)
  • ‫Pixel 9 Pro Fold (Comet)
  • Pixel 9a (Tegu)

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

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

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

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

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

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

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

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

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

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

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

لجهاز واحد

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

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

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

CMake

لكل هدف في ملف 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)

تشغيل تطبيقك

بعد تفعيل ميزة إضافة وضع علامات الذاكرة، استخدِم تطبيقك واختبره كالمعتاد. إذا تم رصد مشكلة في أمان الذاكرة، تتعطل عملية تشغيل تطبيقك مع ظهور رسالة خطأ تشبه ما يلي (يُرجى ملاحظة استخدام SIGSEGV بدلاً من SEGV_MTESERR لرمز SYNC أو 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 في مستندات AOSP للحصول على مزيد من التفاصيل. يمكنك أيضًا تصحيح أخطاء تطبيقك باستخدام Android Studio ويتوقف مصحّح الأخطاء عند السطر الذي يتسبب في الوصول غير الصالح إلى الذاكرة.

المستخدمون المتقدّمون: استخدام MTE في أداة التوزيع الخاصة بك

لاستخدام ميزة MTE للذاكرة التي لم يتم تخصيصها من خلال أدوات تخصيص النظام العادية، عليك تعديل أداة التخصيص لتصنيف الذاكرة والمؤشرات.

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

يجب أن تكون جميع عمليات التوزيع المُشار إليها محاذيةً بحجم 16 بايت، لأنّه لا يمكن تخصيص العلامات إلا لشرائح 16 بايت (المعروفة أيضًا باسم الوحدات).

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

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

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

بدلاً من ذلك، يمكنك أيضًا استخدام التعليمات التالية لإعادة ضبط الذاكرة على القيمة 0:

  • STZG: وضع علامة على حبيبة واحدة بحجم 16 بايت وضبطها على القيمة 0
  • STZ2G: وضع علامة على حبيبتَين بحجم 16 بايت وضبطهما على القيمة 0
  • DC GZVA: وضع علامة على سطر الذاكرة المؤقتة وضبطه على القيمة 0 باستخدام العلامة نفسها

يُرجى العِلم أنّ هذه التعليمات غير متوافقة مع وحدات المعالجة المركزية القديمة، لذا عليك تنفيذها بشكل مشروط عند تفعيل ميزة 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.