چرا MTE؟
اشکالات ایمنی حافظه، که خطاهایی در مدیریت حافظه در زبان های برنامه نویسی بومی هستند، مشکلات رایج کد هستند. آنها منجر به آسیب پذیری های امنیتی و همچنین مشکلات ثبات می شوند.
Armv9 افزونه برچسبگذاری حافظه Arm (MTE) را معرفی کرد، یک افزونه سختافزاری که به شما امکان میدهد باگهای بدون استفاده و سرریز بافر را در کد اصلی خود مشاهده کنید.
پشتیبانی را بررسی کنید
از Android 13، دستگاههای منتخبی از MTE پشتیبانی میکنند. برای بررسی اینکه آیا دستگاه شما با MTE فعال است یا نه، دستور زیر را اجرا کنید:
adb shell grep mte /proc/cpuinfo
اگر نتیجه Features : [...] mte
، دستگاه شما با فعال کردن MTE در حال اجرا است.
برخی از دستگاهها بهطور پیشفرض MTE را فعال نمیکنند، اما به توسعهدهندگان اجازه راهاندازی مجدد با فعال کردن MTE را میدهند. این یک پیکربندی آزمایشی است که برای استفاده معمولی توصیه نمیشود زیرا ممکن است عملکرد یا پایداری دستگاه را کاهش دهد، اما میتواند برای توسعه برنامه مفید باشد. برای دسترسی به این حالت، به Developer Options > Memory Tagging Extension در برنامه تنظیمات خود بروید. اگر این گزینه وجود ندارد، دستگاه شما از فعال کردن MTE به این روش پشتیبانی نمی کند.
حالت های عملیاتی MTE
MTE از دو حالت SYNC و ASYNC پشتیبانی می کند. حالت SYNC اطلاعات تشخیصی بهتری را ارائه می دهد و بنابراین برای اهداف توسعه مناسب تر است، در حالی که حالت ASYNC عملکرد بالایی دارد که امکان فعال کردن آن را برای برنامه های منتشر شده فراهم می کند.
حالت همزمان (SYNC)
این حالت برای اشکال زدایی بیش از عملکرد بهینه شده است و می تواند به عنوان یک ابزار دقیق تشخیص اشکال استفاده شود، زمانی که سربار عملکرد بالاتر قابل قبول باشد. هنگامی که فعال باشد، MTE SYNC همچنین به عنوان یک کاهش امنیتی عمل می کند.
در صورت عدم تطابق تگ، پردازشگر فرآیند بارگذاری متخلف یا دستورالعمل ذخیره را با SIGSEGV (با si_code SEGV_MTESERR) و اطلاعات کامل در مورد دسترسی به حافظه و آدرس خطا خاتمه میدهد.
این حالت در هنگام آزمایش به عنوان جایگزین سریعتری برای HWASan مفید است که نیازی به کامپایل مجدد کدتان ندارد، یا در مرحله تولید، زمانی که برنامه شما یک سطح حمله آسیبپذیر را نشان میدهد. علاوه بر این، زمانی که حالت ASYNC (در زیر توضیح داده شده است) یک اشکال پیدا کرد، می توان با استفاده از APIهای زمان اجرا برای تغییر اجرا به حالت SYNC، گزارش اشکال دقیقی را به دست آورد.
علاوه بر این، هنگام اجرا در حالت SYNC، تخصیصدهنده اندروید ردیابی پشته هر تخصیص و تخصیص را ضبط میکند و از آنها برای ارائه گزارشهای خطای بهتری استفاده میکند که شامل توضیح خطای حافظه، مانند استفاده پس از آزاد شدن یا سرریز بافر، و ردپای رویدادهای حافظه مربوطه را روی هم قرار دهید (برای جزئیات بیشتر به درک گزارش های MTE مراجعه کنید). چنین گزارشهایی اطلاعات متنی بیشتری را ارائه میکنند و ردیابی و رفع اشکالها را نسبت به حالت ASYNC آسانتر میکنند.
حالت ناهمزمان (ASYNC)
این حالت برای عملکرد بیش از دقت گزارشهای اشکال بهینه شده است و میتواند برای تشخیص باگهای ایمنی حافظه کم هزینه استفاده شود. در صورت عدم تطابق تگ، پردازنده تا نزدیکترین ورودی هسته (مانند وقفه syscall یا تایمر) به اجرا ادامه میدهد، جایی که فرآیند را با SIGSEGV (کد SEGV_MTEAERR) بدون ثبت آدرس خطا یا دسترسی به حافظه خاتمه میدهد.
این حالت برای کاهش آسیبپذیریهای ایمنی حافظه در تولید در پایگاههای کد به خوبی آزمایششده، جایی که چگالی اشکالات ایمنی حافظه کم است، مفید است، که با استفاده از حالت SYNC در طول آزمایش به دست میآید.
MTE را فعال کنید
برای یک دستگاه
برای آزمایش، میتوان از تغییرات سازگاری برنامه برای تنظیم مقدار پیشفرض ویژگی memtagMode
برای برنامهای استفاده کرد که هیچ مقداری را در مانیفست تعیین نمیکند (یا "default"
را مشخص میکند).
اینها را میتوانید در قسمت System > Advanced > Developer options > App Compatibility Changes در منوی تنظیمات جهانی پیدا کنید. تنظیم NATIVE_MEMTAG_ASYNC
یا NATIVE_MEMTAG_SYNC
MTE را برای یک برنامه خاص فعال می کند.
همچنین، این را می توان با استفاده از دستور am
به صورت زیر تنظیم کرد:
- برای حالت SYNC:
$ 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)
برنامه خود را اجرا کنید
با فعال کردن 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 مراجعه کنید. همچنین می توانید برنامه خود را با Android Studio اشکال زدایی کنید و اشکال زدا در خطی که باعث دسترسی نامعتبر به حافظه می شود متوقف می شود.
کاربران پیشرفته: استفاده از MTE در تخصیص دهنده خود
برای استفاده از MTE برای حافظهای که از طریق تخصیصدهندههای معمولی سیستم تخصیص داده نشده است، باید تخصیصدهنده خود را تغییر دهید تا حافظه و اشارهگرها را تگ کند.
صفحات اختصاص دهنده شما باید با استفاده از PROT_MTE
در پرچم prot
mmap
(یا mprotect
) تخصیص داده شوند.
همه تخصیص های برچسب گذاری شده باید 16 بایت تراز شوند، زیرا برچسب ها را فقط می توان برای تکه های 16 بایتی (که به عنوان گرانول نیز شناخته می شود) اختصاص داد.
سپس، قبل از برگرداندن یک اشاره گر، باید از دستورالعمل IRG
برای تولید یک تگ تصادفی و ذخیره آن در اشاره گر استفاده کنید.
از دستورالعمل های زیر برای برچسب گذاری حافظه زیرین استفاده کنید:
-
STG
: یک گرانول 16 بایتی را برچسب گذاری کنید -
ST2G
: دو گرانول 16 بایتی را برچسب گذاری کنید -
DC GVA
: خط کش را با همان تگ تگ کنید
از طرف دیگر، دستورالعمل های زیر نیز حافظه را صفر می کنند:
-
STZG
: یک گرانول 16 بایتی را تگ کرده و صفر کنید -
STZ2G
: دو گرانول 16 بایتی را تگ کرده و صفر کنید -
DC GZVA
: تگ کنید و cacheline را با همان تگ صفر مقداردهی کنید
توجه داشته باشید که این دستورالعمل ها در CPU های قدیمی پشتیبانی نمی شوند، بنابراین باید زمانی که 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;
}
ممکن است پیاده سازی اسکودو را به عنوان مرجع مفید بیابید.
بیشتر بدانید
می توانید در راهنمای کاربری MTE برای سیستم عامل اندروید نوشته شده توسط Arm بیشتر بیاموزید.