پسوند برچسب گذاری حافظه بازو (MTE)

چرا 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 بیشتر بیاموزید.