لماذا تستخدم إضافة 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.