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

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

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

הטמעה

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

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

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

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

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

ארגון מחדש של קוד קיים

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

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

לדוגמה:

קבצים בינאריים שנוצרו על ידי המהדר של Clang לא אמורים לכלול בעיות בנתונים שמוטמעות בקוד. אם הקוד שנוצר על ידי אוסף מהדרים של GNU (GCC) הוא כלול (מספרייה סטטית), לבדוק את הקובץ הבינארי של הפלט לוודא שקבועים לא נאספו לקטעי קוד.

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

הפעלת XOM

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

השבתת XOM

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

אפשר להשבית את ה-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 ובדיקה דגלי המקטעים.