בקרה על תקינות זרימת הנתונים

נכון לשנת 2016, כ-86% מכל נקודות החולשה ב-Android קשורות לבטיחות הזיכרון. רוב נקודות החולשה נפרצות על ידי תוקפים שמשתנים את תהליך הבקרה הרגיל של האפליקציה כדי לבצע פעולות זדוניות שרירותיות עם כל ההרשאות של האפליקציה שנפרצה. תקינות תהליך הבקרה (CFI) היא מנגנון אבטחה שאוסר על שינויים בתרשים המקורי של תהליך הבקרה של קובץ בינארי שנוצר על ידי הידור, וכך מקשה מאוד לבצע התקפות כאלה.

ב-Android 8.1 הפעלנו את ההטמעה של LLVM ל-CFI ב-media stack. ב-Android 9 הפעלנו את CFI ברכיבים נוספים וגם בליבה. CFI של המערכת מופעל כברירת מחדל, אבל צריך להפעיל את CFI של הליבה.

כדי להשתמש ב-CFI של LLVM, צריך לבצע הידור באמצעות אופטימיזציה בזמן קישור (LTO). LTO שומר את ייצוג הביטקוד של LLVM בקובצי האובייקט עד לזמן הקישור, וכך מאפשר למהדר להבין טוב יותר אילו אופטימיזציות אפשר לבצע. הפעלת LTO מקטינה את הגודל של קובץ הבינארי הסופי ומשפרת את הביצועים, אבל מאריכה את זמן הידור הקוד. בבדיקות ב-Android, השילוב של LTO ו-CFI גורם לעומס יתר זניח על גודל הקוד ועל הביצועים. בחלק מהמקרים, גם הגודל וגם הביצועים השתפרו.

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

דוגמאות ומקור

ה-CFI מסופק על ידי המהדר ומוסיף מכשירי מדידה לקובץ הבינארי במהלך זמן הידור. אנחנו תומכים ב-CFI בכלי הפיתוח של Clang ובמערכת ה-build של Android ב-AOSP.

CFI מופעל כברירת מחדל במכשירי Arm64 עבור קבוצת הרכיבים שמפורטים ב-/platform/build/target/product/cfi-common.mk. הוא מופעל גם ישירות בקבוצה של קובצי makefile או קובצי תוכנית של רכיבי מדיה, כמו /platform/frameworks/av/media/libmedia/Android.bp ו-/platform/frameworks/av/cmds/stagefright/Android.mk.

הטמעת CFI במערכת

CFI מופעל כברירת מחדל אם אתם משתמשים ב-Clang ובמערכת ה-build של Android. CFI עוזר לשמור על הבטיחות של משתמשי Android, לכן לא מומלץ להשבית אותו.

למעשה, מומלץ מאוד להפעיל את CFI לרכיבים נוספים. המועמדים האידיאליים הם קוד מקורי בעל הרשאות או קוד מקורי שמעבד קלט לא מהימן של משתמשים. אם אתם משתמשים ב-clang ובמערכת ה-build של Android, תוכלו להפעיל את CFI ברכיבים חדשים על ידי הוספת כמה שורות לקובצי ה-makefile או לקובצי ה-blueprint.

תמיכה ב-CFI בקובצי makefile

כדי להפעיל את CFI בקובץ make, כמו /platform/frameworks/av/cmds/stagefright/Android.mk, מוסיפים את השורה הבאה:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE מציין את CFI ככלי הסינון במהלך ה-build.
  • LOCAL_SANITIZE_DIAG מפעיל את מצב האבחון של CFI. במצב אבחון, במהלך קריסות מודפסים ב-logcat פרטי ניפוי באגים נוספים. המידע הזה שימושי במהלך הפיתוח ובדיקה של גרסאות build. עם זאת, חשוב להסיר את מצב האבחון בגרסאות build בסביבת הייצור.
  • LOCAL_SANITIZE_BLACKLIST מאפשר לרכיבים להשבית באופן סלקטיבי את המדידה של CFI לפונקציות או לקובצי מקור ספציפיים. אפשר להשתמש ברשימת שחור כתחליף אחרון כדי לפתור בעיות שעשויות להופיע אצל המשתמשים. לפרטים נוספים, ראו השבתה של CFI.

תמיכה ב-CFI בקובצי שרטוטים

כדי להפעיל את CFI בקובץ תוכנית, כמו /platform/frameworks/av/media/libmedia/Android.bp, צריך להוסיף:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

פתרון בעיות

אם מפעילים את CFI ברכיבים חדשים, יכול להיות שתתקלו בכמה בעיות שקשורות לשגיאות של אי-התאמה לסוג הפונקציה ולשגיאות של אי-התאמה לסוג קוד האסיפה.

שגיאות של אי-התאמה בסוג הפונקציה מתרחשות כי CFI מגביל קריאות עקיפות רק לקפיצה לפונקציות שיש להן את אותו סוג דינמי כמו הסוג הסטטי ששימש בקריאה. CFI מגביל את הקריאות לפונקציות החברות הווירטואליות והלא וירטואליות כך שיאפשרו לקפוץ רק לאובייקטים שהם סוג נגזר מהסוג הסטטי של האובייקט ששימש לביצוע הקריאה. כלומר, אם יש לכם קוד שמפר אחת מההנחות האלה, הכלי למדידת הביצועים ש-CFI מוסיף יבוטל. לדוגמה, ב-stack trace מופיע SIGABRT וב-logcat מופיעה שורה על אי-התאמה בשלמות של תהליך בקרת הזרימה.

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

בעיה אפשרית נוספת היא ניסיון להפעיל CFI בקוד שמכיל קריאות עקיפות לאסיפה. מאחר שקוד האסיפה לא מוקלד, התוצאה היא חוסר התאמה בסוג.

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

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

השבתת CFI

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

מערכת ה-build של Android מספקת תמיכה ברשימות שחורות לכל רכיב (שמאפשרות לבחור קובצי מקור או פונקציות ספציפיות שלא יקבלו מכשירי CFI) גם ב-Make וגם ב-Soong. פרטים נוספים על הפורמט של קובץ רשימת ההחרגות זמינים במסמכי העזרה של Clang.

אימות

נכון לעכשיו, אין בדיקת CTS ספציפית ל-CFI. במקום זאת, צריך לוודא שבדיקות CTS עוברות גם עם CFI מופעל וגם בלי שהוא מופעל, כדי לוודא ש-CFI לא משפיע על המכשיר.