ב-Arm v9 מוצג תוסף לתיוג זיכרון (MTE) של Arm, שהוא הטמעה בחומרה של זיכרון מתויג.
ברמה גבוהה, MTE מתייג כל הקצאת זיכרון או ביטול הקצאת זיכרון במטא-נתונים נוספים. הוא מקצה תג למיקום בזיכרון, שאחר כך אפשר לשייך לו מצביעים שמפנים למיקום הזה בזיכרון. בזמן הריצה, ה-CPU בודק שכל טעינה ואחסון תואמים בין המצביע לבין תגי המטא-נתונים.
ב-Android 12, אפשר להוסיף מטא-נתונים לכל הקצאה של זיכרון בערימה של מרחב המשתמש ושל ליבת מערכת ההפעלה. השימוש ב-MTE עוזר לזהות באגים מסוג use-after-free (שימוש אחרי שחרור) ובאגים מסוג buffer-overflow (גלישת חוצץ), שהם המקור הנפוץ ביותר לבאגים שקשורים לבטיחות הזיכרון בבסיסי הקוד שלנו.
מצבי הפעולה של MTE
ל-MTE יש שלושה מצבי פעולה:
- מצב סינכרוני (SYNC)
- מצב אסינכרוני (ASYNC)
- מצב אסימטרי (ASYMM)
מצב סינכרוני (SYNC)
המצב הזה מותאם לזיהוי מדויק של באגים, ולא לביצועים. אפשר להשתמש בו ככלי מדויק לזיהוי באגים, אם תקורה גבוהה יותר של ביצועים מקובלת. כשמפעילים את MTE SYNC, הוא פועל כאמצעי להפחתת סיכוני אבטחה.
אם יש אי התאמה בתג, המעבד מפסיק את ההפעלה באופן מיידי ומסיים את התהליך עם SIGSEGV
(קוד SEGV_MTESERR
) ומידע מלא על הגישה לזיכרון ועל כתובת השגיאה.
מומלץ להשתמש במצב הזה במהלך הבדיקות כחלופה ל-HWASan/KASAN או בסביבת ייצור כשתהליך היעד מייצג משטח תקיפה פגיע. בנוסף, אם מצב ASYNC מציין שיש באג, אפשר להשתמש בממשקי ה-API של זמן הריצה כדי לעבור למצב SYNC ולקבל דוח באגים מדויק.
כשמפעילים את הקצאת הזיכרון ב-Android במצב SYNC, המערכת מתעדת את עקבות המחסנית של כל ההקצאות והביטולים, ומשתמשת בהם כדי לספק דוחות שגיאות טובים יותר שכוללים הסבר על שגיאת זיכרון, כמו use-after-free או buffer-overflow, ועקבות המחסנית של אירועי הזיכרון הרלוונטיים. דוחות כאלה מספקים מידע נוסף על ההקשר, ומקלים על איתור באגים ותיקונם.
מצב אסינכרוני (ASYNC)
המצב הזה מותאם לביצועים על פני דיוק של דוחות באגים, ואפשר להשתמש בו כשיטה לאיתור באגים שקשורים לבטיחות הזיכרון עם תקורה נמוכה.
במקרה של אי התאמה בתג, המעבד ממשיך את הביצוע עד לכניסה הקרובה ביותר לליבת המערכת (לדוגמה, קריאת מערכת או הפרעה של טיימר), שם הוא מסיים את התהליך עם SIGSEGV
(קוד SEGV_MTEAERR
) בלי לתעד את כתובת השגיאה או את הגישה לזיכרון.
מומלץ להשתמש במצב הזה בסביבת ייצור במאגרי קוד שנבדקו היטב, שבהם צפיפות הבאגים שקשורים לבטיחות הזיכרון נמוכה. אפשר להשיג את זה באמצעות מצב SYNC במהלך הבדיקה.
מצב אסימטרי (ASYMM)
תכונה נוספת ב-Arm v8.7-A, מצב MTE אסימטרי, מספקת בדיקה סינכרונית של קריאות זיכרון ובדיקה אסינכרונית של כתיבות זיכרון, עם ביצועים דומים לאלה של מצב ASYNC. ברוב המקרים, המצב הזה עדיף על מצב ASYNC, ולכן מומלץ להשתמש בו במקום במצב ASYNC בכל פעם שהוא זמין.
לכן, באף אחד מממשקי ה-API שמתוארים בהמשך לא מוזכר מצב Asymmetric. במקום זאת, אפשר להגדיר את מערכת ההפעלה כך שתמיד ישתמשו במצב אסימטרי כשמתבקש מצב אסינכרוני. מידע נוסף זמין בקטע בנושא הגדרת רמת MTE מועדפת ספציפית למעבד.
MTE במרחב המשתמש
בקטעים הבאים מוסבר איך להפעיל את MTE לתהליכי מערכת ולאפליקציות. התכונה MTE מושבתת כברירת מחדל, אלא אם אחת מהאפשרויות שבהמשך מוגדרת לתהליך מסוים (כאן מפורטים הרכיבים שבהם התכונה MTE מופעלת).
הפעלת MTE באמצעות מערכת ה-build
כמאפיין ברמת התהליך, MTE נשלט על ידי ההגדרה של זמן הבנייה של קובץ ההפעלה הראשי. האפשרויות הבאות מאפשרות לשנות את ההגדרה הזו עבור קובצי הפעלה ספציפיים, או עבור ספריות משנה שלמות בעץ המקור. המערכת מתעלמת מההגדרה בספריות או בכל יעד שהוא לא קובץ הפעלה או בדיקה.
1. הפעלת MTE ב-Android.bp
(דוגמה), לפרויקט מסוים:
מצב MTE | הגדרה |
---|---|
MTE אסינכרוני | sanitize: { memtag_heap: true, } |
MTE סינכרוני | sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, } |
או בAndroid.mk:
מצב MTE | הגדרה |
---|---|
Asynchronous MTE |
LOCAL_SANITIZE := memtag_heap |
Synchronous MTE |
LOCAL_SANITIZE := memtag_heap LOCAL_SANITIZE_DIAG := memtag_heap |
2. הפעלת MTE בספריית משנה בעץ המקור באמצעות משתנה מוצר:
מצב MTE | רשימת המיקומים שנכללו | רשימת החרגות |
---|---|---|
async | PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
סנכרון | PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS
MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
או
מצב MTE | הגדרה |
---|---|
MTE אסינכרוני | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
MTE סינכרוני | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
או על ידי ציון נתיב ההחרגה של קובץ הפעלה:
מצב MTE | הגדרה |
---|---|
MTE אסינכרוני | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
MTE סינכרוני |
דוגמה (שימוש דומה ל-PRODUCT_CFI_INCLUDE_PATHS
)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor) PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \ vendor/$(vendor)/projectB
הפעלת MTE באמצעות מאפייני מערכת
אפשר לשנות את הגדרות הבנייה שלמעלה בזמן הריצה על ידי הגדרת מאפיין המערכת הבא:
arm64.memtag.process.<basename> = (off|sync|async)
כאשר basename
מייצג את שם הבסיס של קובץ ההפעלה.
לדוגמה, כדי להגדיר את /system/bin/ping
או את /data/local/tmp/ping
לשימוש ב-MTE אסינכרוני, משתמשים ב-adb shell setprop arm64.memtag.process.ping async
.
הפעלת MTE באמצעות משתנה סביבה
דרך נוספת לשנות את הגדרת ה-build של תהליכים מקוריים (לא אפליקציות) היא באמצעות הגדרת משתנה הסביבה: MEMTAG_OPTIONS=(off|sync|async)
. אם גם משתנה הסביבה וגם מאפיין המערכת מוגדרים, המשתנה מקבל עדיפות.
הפעלת MTE באפליקציות
אם לא מציינים את ההגדרה הזו, MTE מושבת כברירת מחדל, אבל אפליקציות שרוצות להשתמש ב-MTE יכולות לעשות זאת על ידי הגדרת android:memtagMode
בתוך התג <application>
או <process>
ב-AndroidManifest.xml
.
android:memtagMode=(off|default|sync|async)
כשמגדירים את התג <application>
, המאפיין משפיע על כל התהליכים שבהם האפליקציה משתמשת, ואפשר לבטל את ההגדרה שלו לתהליכים ספציפיים על ידי הגדרת התג <process>
.
לצורך ניסויים, אפשר להשתמש בשינויים בתאימותכדי להגדיר את ערך ברירת המחדל של מאפיין memtagMode
באפליקציה שלא מצוין בה ערך במניפסט (או שמצוין בה default
).
אפשר למצוא את השינויים האלה בקטע System > Advanced > Developer options
> App Compatibility Changes
בתפריט ההגדרות הכלליות. הגדרת NATIVE_MEMTAG_ASYNC
או NATIVE_MEMTAG_SYNC
מפעילה את MTE לאפליקציה מסוימת.
לחלופין, אפשר להגדיר את זה באמצעות הפקודה am
באופן הבא:
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
יצירת קובץ אימג' של מערכת MTE
מומלץ מאוד להפעיל MTE בכל הקבצים הבינאריים המקוריים במהלך הפיתוח וההפעלה. ההגדרה הזו עוזרת לזהות באגים שקשורים לבטיחות הזיכרון בשלב מוקדם, ומספקת כיסוי ריאליסטי של המשתמשים אם היא מופעלת בגרסאות בדיקה.
מומלץ מאוד להפעיל MTE במצב סינכרוני בכל הקבצים הבינאריים המקוריים במהלך הפיתוח
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
כמו כל משתנה במערכת ה-build, אפשר להשתמש ב-SANITIZE_TARGET
כמשתנה סביבה או כהגדרה של make
(לדוגמה, בקובץ product.mk
).
שימו לב: הפעולה הזו מפעילה את MTE לכל התהליכים המקוריים, אבל לא לאפליקציות (שהן הסתעפויות מ-zygote64
). כדי להפעיל את MTE באפליקציות, צריך לפעול לפי ההוראות למעלה.
הגדרת רמת MTE מועדפת ספציפית למעבד
במעבדים מסוימים, הביצועים של MTE במצבי ASYMM או אפילו SYNC עשויים להיות דומים לביצועים של ASYNC. לכן כדאי להפעיל בדיקות מחמירות יותר במעבדים האלה כשמתבקש מצב בדיקה פחות מחמיר, כדי ליהנות מהיתרונות של זיהוי שגיאות בבדיקות המחמירות יותר בלי לפגוע בביצועים.
כברירת מחדל, תהליכים שמוגדרים להפעלה במצב ASYNC יפעלו במצב ASYNC
בכל המעבדים. כדי להגדיר את ליבת המערכת להפעלת התהליכים האלה במצב SYNC במעבדים ספציפיים, צריך לכתוב את הערך sync לרשומה sysfs
/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
בזמן האתחול. אפשר לעשות את זה באמצעות סקריפט הפעלה. לדוגמה, כדי להגדיר את מעבדי ה-CPU 0-1 להרצת תהליכים במצב ASYNC במצב SYNC, ואת מעבדי ה-CPU 2-3 להרצה במצב ASYMM, אפשר להוסיף את ההגדרה הבאה לסעיף init של סקריפט init של ספק:
write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm
אבני דרך (Tombstones) מתהליכים במצב ASYNC שפועלים במצב SYNC יכללו מעקב מדויק אחר המיקום של שגיאת הזיכרון. עם זאת, הם לא יכללו הקצאה או ביטול הקצאה של מעקב אחר מחסנית. העקבות האלה זמינים רק אם התהליך מוגדר לפעול במצב SYNC.
int mallopt(M_THREAD_DISABLE_MEM_INIT, level)
כאשר level
הוא 0 או 1.
ההגדרה הזו משביתה את האתחול של הזיכרון ב-malloc, ומונעת שינוי של תגי זיכרון
אלא אם יש צורך בכך כדי שהתוצאה תהיה נכונה.
int mallopt(M_MEMTAG_TUNING, level)
כאשר level
הוא:
M_MEMTAG_TUNING_BUFFER_OVERFLOW
M_MEMTAG_TUNING_UAF
בחירה של שיטת הקצאת תגים.
- הגדרת ברירת המחדל היא
M_MEMTAG_TUNING_BUFFER_OVERFLOW
. -
M_MEMTAG_TUNING_BUFFER_OVERFLOW
– מאפשר זיהוי דטרמיניסטי של באגים של הצפת חוצץ לינארית ושל תת-זרימה על ידי הקצאת ערכי תג שונים להקצאות סמוכות. במצב הזה יש סיכוי קטן יותר לזיהוי באגים מסוג use-after-free, כי רק מחצית מערכי התגים האפשריים זמינים לכל מיקום בזיכרון. חשוב לזכור ש-MTE לא יכול לזהות הצפה בתוך גרגר תג זהה (חלק בגודל 16 בייט), ויכול לפספס הצפות קטנות גם במצב הזה. גלישה כזו לא יכולה לגרום להשחתת הזיכרון, כי הזיכרון בגרנול אחד אף פעם לא משמש להקצאות מרובות. -
M_MEMTAG_TUNING_UAF
- מאפשר תגים אקראיים באופן עצמאי להסתברות אחידה של 93% לזיהוי באגים מרחביים (גלישת חוצץ) וזמניים (שימוש אחרי שחרור).
בנוסף לממשקי ה-API שמתוארים למעלה, משתמשים מנוסים יכולים להשתמש גם בממשקי ה-API הבאים:
- הגדרת
PSTATE.TCO
רישום החומרה יכולה להשבית זמנית את בדיקת התגים (דוגמה). לדוגמה, כשמעתיקים טווח של זיכרון עם תוכן תג לא ידוע, או כשמטפלים בצוואר בקבוק של ביצועים בלולאה פעילה. - כשמשתמשים ב-
M_HEAP_TAGGING_LEVEL_SYNC
, המערכת לטיפול בקריסות מספקת מידע נוסף, כמו הקצאה וביטול הקצאה של דוחות מעקב אחר מחסנית. כדי להשתמש בפונקציונליות הזו צריך גישה לביטים של התג, והיא מופעלת על ידי העברת הדגלSA_EXPOSE_TAGBITS
כשמגדירים את ה-signal handler. מומלץ שכל תוכנה שמגדירה מטפל משלה באותות ומקצה קריסות לא ידועות למערכת תעשה את אותו הדבר.
MTE בליבה
כדי להפעיל את KASAN עם MTE מואץ עבור הליבה, צריך להגדיר את הליבה באמצעות CONFIG_KASAN=y
, CONFIG_KASAN_HW_TAGS=y
. ההגדרות האלה מופעלות כברירת מחדל בקרנלים של GKI, החל מ-Android
12-5.10
.
אפשר לשלוט בזה בזמן האתחול באמצעות ארגומנטים של שורת הפקודה:
-
kasan=[on|off]
– הפעלה או השבתה של KASAN (ברירת מחדל:on
) -
kasan.mode=[sync|async]
- בחירה בין מצב סינכרוני למצב אסינכרוני (ברירת מחדל:sync
) -
kasan.stacktrace=[on|off]
– האם לאסוף עקבות מחסנית (ברירת מחדל:on
)- איסוף של נתוני stack trace דורש גם
stack_depot_disable=off
.
- איסוף של נתוני stack trace דורש גם
-
kasan.fault=[report|panic]
– האם להדפיס רק את הדוח או גם להפעיל את מצב החירום של ליבת המערכת (ברירת מחדל:report
). בלי קשר לאפשרות הזו, בדיקת התגים מושבתת אחרי השגיאה הראשונה שמדווחת.
שימוש מומלץ
מומלץ מאוד להשתמש במצב SYNC במהלך ההפעלה, הפיתוח והבדיקה. צריך להפעיל את האפשרות הזו באופן גלובלי לכל התהליכים באמצעות משתנה הסביבה או מערכת ה-build. במצב הזה, באגים מזוהים בשלב מוקדם בתהליך הפיתוח, בסיס הקוד מתייצב מהר יותר ונמנעים עלויות של זיהוי באגים בשלב מאוחר יותר בייצור.
מומלץ מאוד להשתמש במצב ASYNC בסביבת ייצור. הכלי הזה מספק תקורה נמוכה לזיהוי באגים של בטיחות זיכרון בתהליך, וגם הגנה נוספת לעומק. אחרי שמזהים באג, המפתח יכול להשתמש בממשקי ה-API של זמן הריצה כדי לעבור למצב SYNC ולקבל מעקב מדויק אחר מחסנית הקריאות (stack trace) ממערך משתמשים שנבחר לדגימה.
מומלץ מאוד להגדיר את רמת ה-MTE המועדפת הספציפית למעבד עבור ה-SoC. בדרך כלל, למצב Asymm יש מאפייני ביצועים זהים למצב ASYNC, וכמעט תמיד עדיף להשתמש בו. ליבות קטנות לפי סדר ההוראות לרוב מציגות ביצועים דומים בכל שלושת המצבים, ואפשר להגדיר אותן כך שיעדיפו SYNC.
מפתחים צריכים לבדוק אם יש קריסות על ידי בדיקה של /data/tombstones
, logcat
או על ידי מעקב אחר צינורות DropboxManager
של הספק כדי לזהות באגים אצל משתמשי הקצה. מידע נוסף על ניפוי באגים בקוד מקורי של Android זמין כאן.
רכיבי פלטפורמה עם MTE מופעל
ב-Android 12, מספר רכיבי מערכת קריטיים לאבטחה משתמשים ב-MTE ASYNC כדי לזהות קריסות של משתמשי קצה ולפעול כשכבת הגנה נוספת. הרכיבים האלה הם:
- שירותי רשתות וכלי עזר (למעט
netd
) - Bluetooth, SecureElement, NFC HALs ואפליקציות מערכת
statsd
דימוןsystem_server
-
zygote64
(כדי לאפשר לאפליקציות להביע הסכמה לשימוש ב-MTE)
היעדים האלה נבחרו על סמך הקריטריונים הבאים:
- תהליך עם הרשאות (מוגדר כתהליך שיש לו גישה למשהו שלא קיים בדומיין unprivileged_app SELinux)
- עיבוד קלט לא מהימן (כלל השניים)
- האטה מקובלת בביצועים (ההאטה לא יוצרת חביון שגלוי למשתמש)
אנחנו ממליצים לספקים להפעיל MTE בסביבת ייצור עבור רכיבים נוספים, בהתאם לקריטריונים שצוינו למעלה. במהלך הפיתוח, מומלץ לבדוק את הרכיבים האלה באמצעות מצב SYNC, כדי לזהות באגים שאפשר לתקן בקלות, ולהעריך את ההשפעה של ASYNC על הביצועים שלהם.
בעתיד, מערכת Android מתכננת להרחיב את רשימת רכיבי המערכת שבהם מופעלת MTE, בהתאם למאפייני הביצועים של עיצובי חומרה עתידיים.