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