چرا MTE؟
باگهای ایمنی حافظه، که خطاهایی در مدیریت حافظه در زبانهای برنامهنویسی بومی هستند، از مشکلات رایج کدنویسی محسوب میشوند. آنها منجر به آسیبپذیریهای امنیتی و همچنین مشکلات پایداری میشوند.
Armv9 افزونهی برچسبگذاری حافظهی Arm (MTE) را معرفی کرد، یک افزونهی سختافزاری که به شما امکان میدهد اشکالات استفاده پس از آزادسازی و سرریز بافر را در کد بومی خود شناسایی کنید.
پشتیبانی را بررسی کنید
از اندروید ۱۳ به بعد، دستگاههای منتخب از MTE پشتیبانی میکنند. برای بررسی اینکه آیا دستگاه شما با MTE فعال کار میکند، دستور زیر را اجرا کنید:
adb shell grep mte /proc/cpuinfo
اگر نتیجهی Features : [...] mte باشد، دستگاه شما با MTE فعال در حال اجرا است.
برخی از دستگاهها به طور پیشفرض MTE را فعال نمیکنند، اما به توسعهدهندگان اجازه میدهند تا با فعال بودن MTE، دستگاه را مجدداً راهاندازی کنند. این یک پیکربندی آزمایشی است که برای استفاده عادی توصیه نمیشود زیرا ممکن است عملکرد یا پایداری دستگاه را کاهش دهد، اما میتواند برای توسعه برنامه مفید باشد. برای دسترسی به این حالت، در برنامه تنظیمات خود به قسمت گزینههای توسعهدهندگان > افزونه برچسبگذاری حافظه بروید. اگر این گزینه وجود ندارد، دستگاه شما از فعالسازی MTE به این روش پشتیبانی نمیکند.
دستگاههایی با پشتیبانی MTE
دستگاههای زیر از MTE پشتیبانی میکنند:
- پیکسل ۸ (شیبا)
- پیکسل ۸ پرو (هاسکی)
- پیکسل ۸a (آکیتا)
- پیکسل ۹ (توکی)
- پیکسل ۹ پرو (کایمن)
- پیکسل ۹ پرو ایکسال (کومودو)
- پیکسل ۹ پرو فولد (کامت)
- پیکسل ۹a (تگو)
حالتهای عملیاتی MTE
MTE از دو حالت پشتیبانی میکند: SYNC و ASYNC. حالت SYNC اطلاعات تشخیصی بهتری ارائه میدهد و بنابراین برای اهداف توسعه مناسبتر است، در حالی که حالت ASYNC عملکرد بالایی دارد که امکان فعالسازی آن را برای برنامههای منتشر شده فراهم میکند.
حالت همگامسازی (SYNC)
این حالت برای اولویت اشکالزدایی بر عملکرد بهینه شده است و میتواند به عنوان یک ابزار تشخیص اشکال دقیق، زمانی که سربار عملکرد بالاتر قابل قبول باشد، مورد استفاده قرار گیرد. در صورت فعال بودن، MTE SYNC همچنین به عنوان یک کاهشدهنده امنیت عمل میکند.
در صورت عدم تطابق برچسب، پردازنده فرآیند مربوط به دستورالعمل بارگذاری یا ذخیره خطا را با SIGSEGV (با si_code SEGV_MTESERR) و اطلاعات کامل در مورد دسترسی به حافظه و آدرس خطا خاتمه میدهد.
این حالت در طول تست به عنوان جایگزینی سریعتر برای HWASan که نیازی به کامپایل مجدد کد شما ندارد، یا در مرحله تولید، زمانی که برنامه شما یک سطح حمله آسیبپذیر را نشان میدهد، مفید است. علاوه بر این، هنگامی که حالت ASYNC (که در زیر توضیح داده شده است) یک اشکال پیدا کرده است، میتوان با استفاده از APIهای زمان اجرا برای تغییر اجرا به حالت SYNC، یک گزارش اشکال دقیق دریافت کرد.
علاوه بر این، هنگام اجرا در حالت SYNC، تخصیصدهنده اندروید، رد پشته هر تخصیص و آزادسازی را ثبت میکند و از آنها برای ارائه گزارشهای خطای بهتر استفاده میکند که شامل توضیح خطای حافظه، مانند استفاده پس از آزادسازی یا سرریز بافر، و رد پشته رویدادهای حافظه مربوطه است (برای جزئیات بیشتر به درک گزارشهای MTE مراجعه کنید). چنین گزارشهایی اطلاعات زمینهای بیشتری ارائه میدهند و ردیابی و رفع اشکالات را نسبت به حالت ASYNC آسانتر میکنند.
حالت ناهمزمان (ASYNC)
این حالت برای عملکرد بالاتر از دقت گزارشهای اشکال بهینه شده است و میتواند برای تشخیص با سربار کم اشکالات ایمنی حافظه مورد استفاده قرار گیرد. در صورت عدم تطابق برچسب، پردازنده اجرا را تا نزدیکترین ورودی هسته (مانند یک فراخوانی سیستمی یا وقفه تایمر) ادامه میدهد، جایی که فرآیند را با SIGSEGV (کد SEGV_MTEAERR) بدون ثبت آدرس خطا یا دسترسی به حافظه خاتمه میدهد.
این حالت برای کاهش آسیبپذیریهای ایمنی حافظه در محیط عملیاتی روی کدهای آزمایششده مفید است، جایی که تراکم اشکالات ایمنی حافظه کم است، که این امر با استفاده از حالت SYNC در حین آزمایش حاصل میشود.
فعال کردن MTE
برای یک دستگاه واحد
برای آزمایش، میتوان از تغییرات سازگاری برنامه برای تنظیم مقدار پیشفرض ویژگی memtagMode برای برنامهای که هیچ مقداری را در مانیفست مشخص نمیکند (یا "default" را مشخص میکند) استفاده کرد.
این موارد را میتوانید در منوی تنظیمات کلی، در مسیر System > Advanced > Developer options > App Compatibility Changes پیدا کنید. تنظیم 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
در گریدل
شما میتوانید 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 موجود در manifest شما را با sync for debug builds جایگزین میکند.
به عنوان یک روش جایگزین، میتوانید 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
در فایل 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
سیمیک
برای هر هدف در 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 برای 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 مراجعه کنید. همچنین میتوانید برنامه خود را با اندروید استودیو اشکالزدایی کنید و اشکالزدا در خطی که باعث دسترسی نامعتبر به حافظه میشود، متوقف میشود.
کاربران پیشرفته: استفاده از MTE در تخصیصدهنده خودتان
برای استفاده از MTE برای حافظهای که از طریق تخصیصدهندههای معمولی سیستم تخصیص داده نشده است، باید تخصیصدهنده خود را برای برچسبگذاری حافظه و اشارهگرها تغییر دهید.
صفحات مربوط به تخصیصدهنده شما باید با استفاده از PROT_MTE در پرچم prot از mmap (یا mprotect ) اختصاص داده شوند.
همه تخصیصهای برچسبگذاری شده باید ۱۶ بایتی باشند، زیرا برچسبها فقط میتوانند برای تکههای ۱۶ بایتی (که به عنوان گرانول نیز شناخته میشوند) اختصاص داده شوند.
سپس، قبل از بازگرداندن یک اشارهگر، باید از دستورالعمل IRG برای تولید یک برچسب تصادفی و ذخیره آن در اشارهگر استفاده کنید.
برای برچسبگذاری حافظهی اصلی از دستورالعملهای زیر استفاده کنید:
-
STG: یک گرانول ۱۶ بایتی را برچسبگذاری کنید -
ST2G: دو گرانول ۱۶ بایتی را برچسبگذاری کنید -
DC GVA: برچسب cacheline با همان برچسب
به عنوان یک روش جایگزین، دستورالعملهای زیر نیز حافظه را با صفر مقداردهی اولیه میکنند:
-
STZG: یک گرانول ۱۶ بایتی را برچسبگذاری و مقداردهی اولیه صفر کنید -
STZ2G: دو گرانول ۱۶ بایتی را برچسبگذاری و مقداردهی اولیه صفر کنید -
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 برای سیستم عامل اندروید که توسط Arm نوشته شده است، اطلاعات بیشتری کسب کنید.