זיכרון להפעלה בלבד (XOM) לקובצי בינארי של AArch64

קטעי קוד הפעלה של קבצים בינאריים של מערכת AArch64 מסומנים כברירת מחדל כ'לפעולה בלבד' (לא לקריאה) כחלק מהקשחת האבטחה נגד התקפות של שימוש חוזר בקוד בזמן אמת. קוד שמערבב נתונים וקוד יחד וקוד שבודק בכוונה את החלקים האלה (בלי למפות מחדש קודם את פלחי הזיכרון כקריא) לא פועל יותר. אפליקציות עם SDK יעד של 10 (רמת API 29 ואילך) מושפעות מהשינוי אם האפליקציה מנסה לקרוא בזיכרון קטעי קוד של ספריות מערכת שפועלות בזיכרון לצורכי ביצוע בלבד (XOM) בלי לסמן קודם את הקטע כקריא.

כדי ליהנות באופן מלא מההפחתה הזו, נדרשת תמיכה גם בחומרה וגם בליבה. ללא התמיכה הזו, יכול להיות שההפחתה תהיה חלקית בלבד. ליבת Android 4.9 המשותפת מכילה את התיקונים המתאימים כדי לספק תמיכה מלאה בכך במכשירי ARMv8.2.

הטמעה

קבצים בינאריים של AArch64 שנוצרים על ידי המהדר מבוססים על ההנחה שהקוד והנתונים לא מעורבבים. הפעלת התכונה הזו לא משפיעה לרעה על הביצועים של המכשיר.

בקוד שצריך לבצע בדיקה פנימית מכוונת של הזיכרון בקטעי הקוד שניתן להריץ, מומלץ להפעיל את mprotect בקטעי הקוד שצריך לבדוק כדי לאפשר קריאה שלהם, ואז להסיר את האפשרות לקריאה כשהבדיקה מסתיימת.
הטמעה הזו גורמת לקריאות בקטעי זיכרון שמסומנים כ'לקריאה בלבד', וכתוצאה מכך מתרחשת שגיאת חלוקה (SEGFAULT). היא עלולה להתרחש כתוצאה מבאג, מנקודת חולשה, מנתונים מעורבים עם קוד (אוסף של מחרוזות מילוליות) או מניתוח פנימי מכוון של זיכרון.

תמיכה במכשירים והשפעה עליהם

יכול להיות שמכשירים עם חומרה או ליבות ישנים יותר (פחות מ-4.9) בלי התיקונים הנדרשים לא יתמכו בתכונה הזו באופן מלא או לא ייהנו מהיתרונות שלה. במכשירים ללא תמיכה בליבה, יכול להיות שלא תהיה אכיפה של גישה של משתמשים לזיכרון לצורכי ביצוע בלבד. עם זאת, קוד ליבה שבודק באופן מפורש אם דף ניתן לקריאה עדיין יכול לאכוף את המאפיין הזה, כמו process_vm_readv().

צריך להגדיר את הדגל CONFIG_ARM64_UAO בליבה כדי לוודא שהיא תתייחס כראוי לדפים של מרחב המשתמש שסומנו כ'לצורכי הפעלה בלבד'. יכול להיות שמכשירי ARMv8 ישנים יותר או מכשירי ARMv8.2 עם השבתת User Access Override‏ (UAO) לא ייהנו מכך במלואה, ויכול להיות שעדיין יהיה אפשר לקרוא דפים לצורך ביצוע בלבד באמצעות syscalls.

שינוי מבנה של קוד קיים

קוד שהועתק מ-AArch32 עשוי להכיל נתונים וקוד מעורבים, וכתוצאה מכך עלולות להיווצר בעיות. במקרים רבים, אפשר לפתור את הבעיות האלה פשוט על ידי העברת הקבועים לקטע .data בקובץ האסיפה.

יכול להיות שיהיה צורך לבצע עיבוד מחדש של קוד שנכתב ביד כדי להפריד בין קבועים שנצברים באופן מקומי.

לדוגמה:

בקבצים הבינאריים שנוצרו על ידי המהדר Clang לא אמורות להיות בעיות של נתונים שמעורבים בקוד. אם קוד שנוצר על ידי GNU compiler collection‏ (GCC) נכלל (מספרייה סטטית), צריך לבדוק את הפלט הבינארי כדי לוודא שהקבועים לא אוחדו לקטעים של קוד.

אם צריך לבצע בדיקה עצמית של קוד בקטעי קוד שניתן להריץ, קודם צריך לבצע קריאה ל-mprotect כדי לסמן את הקוד כקריא. לאחר השלמת הפעולה, צריך להפעיל שוב את mprotect כדי לסמן שהיא לא ניתנת לקריאה.

הפעלת XOM

כברירת מחדל, הרשאת ההפעלה בלבד מופעלת לכל קובצי הבינארי של 64 ביט במערכת ה-build.

השבתת XOM

אפשר להשבית את ההרשאה 'הפעלה בלבד' ברמת המודול, ברמת עץ תיקיות המשנה כולו או ברמה הגלובלית של גרסה בנויה שלמה.

אפשר להשבית את XOM במודולים נפרדים שלא ניתן לבצע בהם רפאקציה, או שצריך לקרוא את הקוד ההפעלה שלהם, על ידי הגדרת המשתנים LOCAL_XOM ו-xom לערך false.

// Android.mk
LOCAL_XOM := false

// Android.bp
cc_binary { // or other module types
   ...
   xom: false,
}

אם זיכרון לצורכי ביצוע בלבד מושבת בספרייה סטטית, מערכת ה-build מחילה את ההגדרה הזו על כל המודולים התלויים בספרייה הסטטית הזו. אפשר לשנות את ההגדרה הזו באמצעות xom: true,.

כדי להשבית זיכרון לצורכי הפעלה בלבד בספריית משנה מסוימת (לדוגמה, foo/bar/‎), מעבירים את הערך אל XOM_EXCLUDE_PATHS.

make -j XOM_EXCLUDE_PATHS=foo/bar

לחלופין, אפשר להגדיר את המשתנה PRODUCT_XOM_EXCLUDE_PATHS בתצורת המוצר.

כדי להשבית קובצי בינארי להפעלה בלבד באופן גלובלי, מעבירים את הערך ENABLE_XOM=false לפקודה make.

make -j ENABLE_XOM=false

אימות

אין בדיקות CTS או בדיקות אימות זמינות לזיכרון לצורכי ביצוע בלבד. אפשר לאמת קובצי בינארי באופן ידני באמצעות readelf ובדיקת דגלים של פלחים.