لماذا يجب استخدام إضافة وضع علامات الذاكرة؟
تُعدّ أخطاء أمان الذاكرة، وهي أخطاء في معالجة الذاكرة في لغات البرمجة الأصلية، من المشاكل الشائعة في الرموز البرمجية. وتؤدي إلى ثغرات أمنية ومشاكل في الاستقرار.
قدّمت بنية Armv9 إضافة Memory Tagging Extension (MTE) من Arm، وهي إضافة على مستوى الأجهزة تتيح لك رصد الأخطاء المرتبطة باستخدام ذاكرة معيّنة بعد تفريغها وتجاوز المخزن المؤقت في الرموز البرمجية الأصلية.
التحقّق من توفّر الدعم
بدءًا من Android 13، تتوافق أجهزة محدَّدة مع تقنية MTE. للتحقّق مما إذا كان جهازك يعمل مع تفعيل MTE، شغِّل الأمر التالي:
adb shell grep mte /proc/cpuinfo
إذا كانت النتيجة Features : [...] mte، يعني ذلك أنّ جهازك يعمل مع تفعيل MTE.
لا تفعّل بعض الأجهزة ميزة "وضع علامات الذاكرة" (MTE) تلقائيًا، ولكنها تسمح للمطوّرين بإعادة التشغيل مع تفعيل هذه الميزة. هذا الإعداد تجريبي ولا يُنصح باستخدامه في الحالات العادية لأنّه قد يؤدي إلى انخفاض أداء الجهاز أو استقراره، ولكن قد يكون مفيدًا في تطوير التطبيقات. للوصول إلى هذا الوضع، انتقِل إلى خيارات المطوّرين > Memory Tagging Extension في تطبيق "الإعدادات". إذا لم يكن هذا الخيار متاحًا، يعني ذلك أنّ جهازك لا يتيح تفعيل 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
يتيح MTE وضعَين: SYNC وASYNC. يوفّر وضع SYNC معلومات أفضل عن التشخيص، وبالتالي فهو أكثر ملاءمةً لأغراض التطوير، بينما يتميّز وضع ASYNC بأداء عالٍ يتيح تفعيله للتطبيقات التي تم إصدارها.
الوضع المتزامن (SYNC)
تم تحسين هذا الوضع لتسهيل تصحيح الأخطاء أكثر من تحسين الأداء، ويمكن استخدامه كأداة دقيقة لرصد الأخطاء عندما يكون من المقبول زيادة أعباء الأداء. عند تفعيل ميزة "المزامنة مع إضافة وضع علامات الذاكرة"، تعمل هذه الميزة أيضًا كإجراء أمني للحدّ من المخاطر.
في حال عدم تطابق العلامة، ينهي المعالج العملية عند التحميل أو التخزين المخالف باستخدام SIGSEGV (مع si_code SEGV_MTESERR) ومعلومات كاملة حول الوصول إلى الذاكرة والعنوان الذي حدث فيه الخطأ.
ويكون هذا الوضع مفيدًا أثناء الاختبار كبديل أسرع لـ HWASan لا يتطلّب إعادة تجميع الرمز، أو في مرحلة الإنتاج عندما يمثّل تطبيقك سطح هجوم معرَّضًا للخطر. بالإضافة إلى ذلك، عندما يرصد الوضع ASYNC (الموضّح أدناه) خطأً، يمكن الحصول على تقرير دقيق عن الخطأ باستخدام واجهات برمجة التطبيقات لوقت التشغيل للتبديل إلى الوضع SYNC.
بالإضافة إلى ذلك، عند التشغيل في وضع SYNC، يسجّل برنامج تخصيص الذاكرة في Android تقرير تتبُّع تسلسل استدعاء الدوال البرمجية لكل عملية تخصيص وإلغاء تخصيص، ويستخدمها لتقديم تقارير أفضل عن الأخطاء تتضمّن شرحًا لخطأ في الذاكرة، مثل استخدام ذاكرة معيّنة بعد تفريغها أو تجاوز المخزن المؤقت للذاكرة الديناميكية، وتقارير تتبُّع تسلسل استدعاء الدوال البرمجية لأحداث الذاكرة ذات الصلة (راجِع التعرّف على تقارير MTE لمزيد من التفاصيل). توفّر هذه التقارير معلومات سياقية أكثر وتسهّل تتبُّع الأخطاء وإصلاحها مقارنةً بالوضع ASYNC.
الوضع غير المتزامن (ASYNC)
تم تحسين هذا الوضع من أجل الأداء على حساب دقة تقارير الأخطاء، ويمكن استخدامه لرصد أخطاء أمان الذاكرة بأقل قدر من الأعباء. في حال عدم تطابق العلامة، يواصل المعالج التنفيذ إلى أن يصل إلى أقرب نقطة دخول إلى النواة (مثل استدعاء النظام أو مقاطعة الموقّت)، حيث ينهي العملية باستخدام SIGSEGV (الرمز SEGV_MTEAERR) بدون تسجيل عنوان الخطأ أو الوصول إلى الذاكرة.
ويكون هذا الوضع مفيدًا للحدّ من الثغرات الأمنية المتعلّقة بسلامة الذاكرة في الإصدارات المتاحة للجميع على قواعد الرموز التي تم اختبارها جيدًا والتي يُعرف أنّ كثافة أخطاء سلامة الذاكرة فيها منخفضة، ويتم تحقيق ذلك باستخدام وضع SYNC أثناء الاختبار.
تفعيل إضافة وضع علامات الذاكرة (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 مخصّص. لإجراء ذلك، أنشئ buildType خاصًا بك وضَع ملف XML في app/src/<name of buildType>/AndroidManifest.xml.
حزمة APK على أي جهاز متوافق
تكون ميزة "الترجمة الآلية للرسائل" غير مفعَّلة تلقائيًا. يمكن للتطبيقات التي تريد استخدام 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)
تشغيل تطبيقك
بعد تفعيل ميزة "إضافة وضع علامات الذاكرة"، استخدِم تطبيقك واختبِره كالمعتاد. في حال رصد مشكلة تتعلّق بأمان الذاكرة، سيتعطّل تطبيقك مع ظهور ملف Tombstone يشبه ما يلي (يُرجى ملاحظة SIGSEGV مع SEGV_MTESERR لعملية المزامنة أو SEGV_MTEAERR لعملية عدم المزامنة):
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: وضع علامة على سطر التخزين المؤقت بالعلامة نفسها
بدلاً من ذلك، تعمل التعليمات التالية أيضًا على تهيئة الذاكرة إلى القيمة صفر:
-
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.