הכלי UndefinedBehaviorSanitizer (UBSan) מבצע מכשור בזמן ההידור כדי לבדוק סוגים שונים של התנהגות לא מוגדרת. UBSan יכול לזהות באגים רבים של התנהגות לא מוגדרת, אבל מערכת Android תומכת רק ב:
- יישור
- bool
- גבולות
- enum
- float-cast-overflow
- float-divide-by-zero
- integer-divide-by-zero
- nonnull-attribute
- null
- חזרה לרצף השגרה
- returns-nonnull-attribute
- shift-base
- shift-exponent
- signed-integer-overflow
- לא נגיש
- unsigned-integer-overflow
- vla-bound
גלישת מספרים שלמים לא מסומנים, למרות שהיא לא מוגדרת מבחינה טכנית כהתנהגות לא מוגדרת, נכללת ב-Sanitizer ומשמשת במודולים רבים של Android, כולל רכיבי mediaserver, כדי למנוע פגיעויות חבויות של גלישת מספרים שלמים.
הטמעה
במערכת ה-build של Android, אפשר להפעיל את UBSan באופן גלובלי או מקומי. כדי להפעיל את UBSan באופן גלובלי, מגדירים את SANITIZE_TARGET ב-Android.mk. כדי להפעיל את UBSan ברמת המודול, מגדירים את LOCAL_SANITIZE ומציינים את ההתנהגויות הלא מוגדרות שרוצים לחפש ב-Android.mk. לדוגמה:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_CFLAGS := -std=c11 -Wall -Werror -O0 LOCAL_SRC_FILES:= sanitizer-status.c LOCAL_MODULE:= sanitizer-status LOCAL_SANITIZE := alignment bounds null unreachable integer LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer include $(BUILD_EXECUTABLE)
והגדרת התצורה המקבילה (Android.bp):
cc_binary {
cflags: [
"-std=c11",
"-Wall",
"-Werror",
"-O0",
],
srcs: ["sanitizer-status.c"],
name: "sanitizer-status",
sanitize: {
misc_undefined: [
"alignment",
"bounds",
"null",
"unreachable",
"integer",
],
diag: {
misc_undefined: [
"alignment",
"bounds",
"null",
"unreachable",
"integer",
],
},
},
}
קיצורי דרך ל-UBSan
ב-Android יש גם שני קיצורי דרך, integer ו-default-ub, להפעלה של קבוצת אמצעי ניקוי בו-זמנית. integer מפעיל את integer-divide-by-zero, signed-integer-overflow ו-unsigned-integer-overflow.
default-ub מאפשר את הבדיקות שגורמות לבעיות מינימליות בביצועי הקומפיילר: bool, integer-divide-by-zero, return,
returns-nonnull-attribute, shift-exponent, unreachable and vla-bound. אפשר להשתמש במחלקת הניקוי של מספרים שלמים עם SANITIZE_TARGET ו-LOCAL_SANITIZE, אבל אפשר להשתמש ב-default-ub רק עם SANITIZE_TARGET.
דיווח משופר על שגיאות
ההטמעה של UBSan ב-Android מפעילה פונקציה שצוינה כשנתקלים בהתנהגות לא מוגדרת. כברירת מחדל, הפונקציה הזו היא abort. עם זאת, החל מאוקטובר 2016, ל-UBSan ב-Android יש ספריית זמן ריצה אופציונלית שמספקת דיווח שגיאות מפורט יותר, כולל סוג ההתנהגות הלא מוגדרת שנמצאה, ומידע על הקובץ ועל שורת קוד המקור. כדי להפעיל את הדיווח על השגיאה הזו באמצעות בדיקות של מספרים שלמים, מוסיפים את השורה הבאה לקובץ Android.mk:
LOCAL_SANITIZE:=integer LOCAL_SANITIZE_DIAG:=integer
הערך LOCAL_SANITIZE מפעיל את אמצעי הניקוי במהלך הבנייה. הפקודה LOCAL_SANITIZE_DIAG מפעילה מצב אבחון עבור אמצעי החיטוי שצוין. אפשר להגדיר ערכים שונים ל-LOCAL_SANITIZE ול-LOCAL_SANITIZE_DIAG, אבל רק הבדיקות ב-LOCAL_SANITIZE מופעלות. אם בדיקה לא מצוינת ב-LOCAL_SANITIZE, אבל מצוינת ב-LOCAL_SANITIZE_DIAG, הבדיקה לא מופעלת ולא מוצגות הודעות אבחון.
דוגמה למידע שסופק על ידי ספריית זמן הריצה של UBSan:
pixel-xl:/ # sanitizer-status ubsan sanitizer-status/sanitizer-status.c:53:6: runtime error: unsigned integer overflow: 18446744073709551615 + 1 cannot be represented in type 'size_t' (aka 'unsigned long')
ניקוי של גלישת מספר שלם
גלישות לא מכוונות של מספרים שלמים עלולות לגרום לפגמים בזיכרון או לנקודות חולשה שמאפשרות חשיפת מידע במשתנים שמשויכים לגישות לזיכרון או להקצאות זיכרון. כדי להתמודד עם הבעיה הזו, הוספנו ל-Clang את UndefinedBehaviorSanitizer (UBSan) – אמצעי לניקוי נתונים של הצפת מספרים שלמים עם סימן וללא סימן – כדי לחזק את מסגרת המדיה ב-Android 7.0. ב-Android 9, הרחבנו את UBSan כך שיכלול יותר רכיבים ושיפרנו את התמיכה בו במערכת הבנייה.
התכונה הזו נועדה להוסיף בדיקות לפעולות ולפקודות אריתמטיות, שעלולות לגרום לגלישה, כדי להפסיק בבטחה תהליך אם מתרחשת גלישה. הכלים האלה יכולים לצמצם את הסיכון לבעיות שקשורות לזיכרון ולחשיפת מידע, שנובעות מגלישת מספרים שלמים, כמו פגיעות Stagefright המקורית.
דוגמאות ומקור
החיטוי של הצפת מספרים שלמים (IntSan) מסופק על ידי הקומפיילר ומוסיף מכשור לקובץ הבינארי במהלך הקומפילציה כדי לזהות הצפות אריתמטיות. הוא מופעל כברירת מחדל ברכיבים שונים בפלטפורמה, למשל /platform/external/libnl/Android.bp.
הטמעה
IntSan משתמש ב-UBSan's signed וב-unsigned integer overflow sanitizers. ההקלה הזו מופעלת ברמת כל מודול. הוא עוזר לשמור על האבטחה של רכיבים קריטיים ב-Android, ולכן אסור להשבית אותו.
מומלץ מאוד להפעיל את התכונה Integer Overflow Sanitization (ניקוי של הצפת מספרים שלמים) לרכיבים נוספים. המועמדים האידיאליים הם קוד מקורי עם הרשאות או קוד מקורי שמנתח קלט משתמש לא מהימן. יש תקורה קטנה שקשורה לביצועים של אמצעי החיטוי, והיא תלויה בשימוש בקוד ובשכיחות של פעולות אריתמטיות. צפוי אחוז קטן של תקורה, וכדאי לבדוק אם הביצועים הם בעיה.
תמיכה ב-IntSan בקובצי makefile
כדי להפעיל את IntSan בקובץ makefile, מוסיפים את השורה:
LOCAL_SANITIZE := integer_overflow # Optional features LOCAL_SANITIZE_DIAG := integer_overflow LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
-
LOCAL_SANITIZEמקבל רשימה של אמצעי ניקוי שמופרדים בפסיקים, כאשרinteger_overflowהוא קבוצה מוכנה מראש של אפשרויות עבור אמצעי הניקוי של הצפת מספרים שלמים חתומים ולא חתומים, עם רשימת חסימה כברירת מחדל. -
LOCAL_SANITIZE_DIAGמפעיל את מצב האבחון עבור אמצעי החיטוי. מומלץ להשתמש במצב אבחון רק במהלך בדיקות, כי המצב הזה לא יבטל את הפעולה במקרה של הצפת זיכרון, ולכן לא יספק את יתרון האבטחה של אמצעי ההגנה. פרטים נוספים זמינים במאמר בנושא פתרון בעיות. -
LOCAL_SANITIZE_BLOCKLISTמאפשרת לציין קובץ BLOCKLIST כדי למנוע ניקוי של פונקציות וקובצי מקור. פרטים נוספים זמינים במאמר בנושא פתרון בעיות.
אם רוצים שליטה מפורטת יותר, מפעילים את אמצעי הניקוי בנפרד באמצעות אחד מהדגלים הבאים או שניהם:
LOCAL_SANITIZE := signed-integer-overflow, unsigned-integer-overflow LOCAL_SANITIZE_DIAG := signed-integer-overflow, unsigned-integer-overflow
תמיכה ב-IntSan בקובצי שרטוטים
כדי להפעיל ניקוי של הצפת מספרים שלמים בקובץ תוכנית, כמו
/platform/external/libnl/Android.bp,
מוסיפים:
sanitize: {
integer_overflow: true,
diag: {
integer_overflow: true,
},
BLOCKLIST: "modulename_BLOCKLIST.txt",
},
בדומה לקובצי make, המאפיין integer_overflow הוא חבילה מוכנה מראש של אפשרויות לכלים לניקוי של מספרים שלמים חתומים ולא חתומים שגולשים על גבולות הזיכרון, עם רשימת חסימה שמוגדרת כברירת מחדל.
קבוצת המאפיינים diag מאפשרת להפעיל את מצב האבחון עבור אמצעי הניקוי. מומלץ להשתמש במצב אבחון רק במהלך בדיקות. במצב אבחון, המערכת לא מפסיקה את הפעולה במקרה של הצפה, ולכן היתרון האבטחתי של אמצעי ההגנה בגרסאות למשתמשים מתבטל לחלוטין. פרטים נוספים זמינים במאמר בנושא פתרון בעיות.
המאפיין BLOCKLIST מאפשר לציין קובץ של רשימת חסימה, כדי שמפתחים יוכלו למנוע ניקוי של פונקציות וקבצי מקור. פרטים נוספים זמינים במאמר בנושא פתרון בעיות.
כדי להפעיל את אמצעי החיטוי בנפרד, משתמשים בפקודה:
sanitize: {
misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"],
diag: {
misc_undefined: ["signed-integer-overflow",
"unsigned-integer-overflow",],
},
BLOCKLIST: "modulename_BLOCKLIST.txt",
},פתרון בעיות
אם מפעילים חיטוי של הצפת מספרים שלמים ברכיבים חדשים, או מסתמכים על ספריות פלטפורמה שעברו חיטוי של הצפת מספרים שלמים, יכול להיות שתיתקלו בכמה בעיות שקשורות להצפות מספרים שלמים שגורמות להפסקת הפעולה. כדאי לבדוק רכיבים עם הפעלת ניקוי כדי לוודא שאפשר לזהות הצפות תקינות.
כדי למצוא ביטולים שנגרמו בגלל ניקוי בבניית משתמשים, מחפשים קריסות עם הודעות ביטול שמציינות הצפה שנתפסה על ידי UBSan, כמו:SIGABRT
pid: ###, tid: ###, name: Binder:### >>> /system/bin/surfaceflinger <<< signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- Abort message: 'ubsan: sub-overflow'
ה-stack trace צריך לכלול את הפונקציה שגורמת לביטול, אבל יכול להיות שלא יופיעו בו הצפות שמתרחשות בפונקציות מוטבעות.
כדי לקבוע בקלות רבה יותר את שורש הבעיה, צריך להפעיל את האבחון בספרייה שגורמת לביטול ולניסיון לשחזר את השגיאה. אם האבחון מופעל, התהליך לא יופסק אלא ימשיך לפעול. הימנעות מביטול עוזרת למקסם את מספר הצפות הזיכרון התקינות בנתיב ביצוע מסוים, בלי צורך לבצע קומפילציה מחדש אחרי תיקון כל באג. כלי האבחון מפיק הודעת שגיאה שכוללת את מספר השורה ואת קובץ המקור שגרם לביטול:
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:2188:32: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'size_t' (aka 'unsigned long')
אחרי שמאתרים את הפעולה האריתמטית הבעייתית, מוודאים שהגלישה מעבר לקיבולת היא לא מזיקה ומכוונת (למשל, אין לה השלכות על האבטחה). כדי לטפל בביטול של אמצעי הניקוי, אפשר:
- שינוי מבנה הקוד כדי למנוע את הגלישה (דוגמה)
- גלישה (overflow) באופן מפורש באמצעות הפונקציות __builtin_*_overflow של Clang (דוגמה)
- השבתת החיטוי בפונקציה על ידי ציון המאפיין
no_sanitize(דוגמה) - השבתת ניקוי של פונקציה או קובץ מקור באמצעות קובץ BLOCKLIST (דוגמה)
מומלץ להשתמש בפתרון הכי מפורט שאפשר. לדוגמה, אם יש פונקציה גדולה עם הרבה פעולות אריתמטיות ופעולה אחת שגורמת לגלישה, צריך לשנות את הפעולה הבודדת ולא להוסיף את כל הפונקציה לרשימת החסימה.
דוגמאות נפוצות לדפוסים שעלולים לגרום להצפות תקינות:
- המרות מרומזות שבהן מתרחשת הצפה לא חתומה לפני ההמרה לסוג חתום (דוגמה)
- מחיקות ברשימה מקושרת שמקטינות את אינדקס הלולאה בזמן המחיקה (דוגמה)
- הקצאת סוג לא חתום לערך -1 במקום לציין את הערך המקסימלי בפועל (דוגמה)
- לולאות שמקטינות מספר שלם לא מסומן בתנאי (דוגמה, דוגמה)
מומלץ למפתחים לוודא שבמקרים שבהם כלי הניקוי מזהה הצפה, היא אכן לא מזיקה ואין לה תופעות לוואי לא רצויות או השלכות על האבטחה, לפני שהם משביתים את הניקוי.
השבתת IntSan
אפשר להשבית את IntSan באמצעות רשימות חסימה או מאפייני פונקציה. השבתה צריכה להתבצע במשורה, ורק אם אי אפשר לבצע רפקטורינג של הקוד או אם יש תקורה בעייתית שפוגעת בביצועים.
למידע נוסף על השבתת IntSan באמצעות מאפייני פונקציה ועל הפורמט של קובץ BLOCKLIST, אפשר לעיין במסמכי התיעוד של Clang. צריך להגדיר את רשימת החסימה כך שתחול רק על אמצעי החיטוי הספציפי, באמצעות שמות של קטעים שמציינים את אמצעי החיטוי שאליו מכוונים, כדי למנוע השפעה על אמצעי חיטוי אחרים.
אימות
נכון לעכשיו, אין בדיקת CTS ספציפית לניקוי של גלישת מספרים שלמים. במקום זאת, מוודאים שהבדיקות של CTS עוברות עם או בלי IntSan מופעל כדי לוודא שהיא לא משפיעה על המכשיר.
חיטוי גבולות
BoundsSanitizer (BoundSan) מוסיף מכשור לקבצים בינאריים כדי להוסיף בדיקות של גבולות סביב גישות למערכים. הבדיקות האלה מתווספות אם הקומפיילר לא יכול להוכיח בזמן הקומפילציה שהגישה תהיה בטוחה, ואם הגודל של המערך יהיה ידוע בזמן הריצה, כדי שאפשר יהיה לבדוק אותו. Android 10 פורס את BoundSan ב-Bluetooth ובקודקים. BoundSan מסופק על ידי הקומפיילר ומופעל כברירת מחדל ברכיבים שונים בפלטפורמה.
הטמעה
BoundSan משתמש ב-UBSan bounds sanitizer. ההקלה הזו מופעלת ברמת כל מודול. הוא עוזר לשמור על האבטחה של רכיבים חשובים ב-Android, ולכן לא מומלץ להשבית אותו.
מומלץ מאוד להפעיל את BoundSan לרכיבים נוספים. המועמדים האידיאליים הם קוד מותאם עם הרשאות או קוד מותאם מורכב שמנתח קלט משתמש לא מהימן. התקורה של הביצועים שקשורה להפעלת BoundSan תלויה במספר הגישות למערכים שלא ניתן להוכיח שהן בטוחות. צפוי אחוז תקורה קטן בממוצע, וכדאי לבדוק אם הביצועים הם בעיה.
הפעלת BoundSan בקובצי תוכנית
אפשר להפעיל את BoundSan בקובצי תוכנית על ידי הוספת "bounds"
למאפיין misc_undefined sanitize במודולים של קובץ בינארי וספרייה:
sanitize: {
misc_undefined: ["bounds"],
diag: {
misc_undefined: ["bounds"],
},
BLOCKLIST: "modulename_BLOCKLIST.txt",diag
המאפיין diag מפעיל את מצב האבחון עבור אמצעי החיטוי.
מומלץ להשתמש במצב אבחון רק במהלך בדיקות. מצב האבחון לא מבטל את הפעולה במקרה של הצפת זיכרון, ולכן הוא לא מומלץ לגרסאות ייצור כי הוא לא מספק את יתרון האבטחה של ההפחתה וגורם לעומס גבוה יותר על הביצועים.
רשימת חסימה
המאפיין BLOCKLIST מאפשר לציין קובץ BLOCKLIST שמפתחים יכולים להשתמש בו כדי למנוע ניקוי של פונקציות וקבצי מקור. משתמשים במאפיין הזה רק אם הביצועים חשובים לכם והקבצים או הפונקציות שמסומנים תורמים לביצועים באופן משמעותי. צריך לבצע ביקורת ידנית של הקבצים או הפונקציות האלה כדי לוודא שהגישה למערכים בטוחה. פרטים נוספים זמינים במאמר בנושא פתרון בעיות.
הפעלת BoundSan בקובצי makefile
אפשר להפעיל את BoundSan בקובצי makefile על ידי הוספת "bounds"
למשתנה LOCAL_SANITIZE עבור מודולים בינאריים ומודולים של ספריות:
LOCAL_SANITIZE := bounds # Optional features LOCAL_SANITIZE_DIAG := bounds LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
LOCAL_SANITIZE מקבל רשימה של אמצעי חיטוי שמופרדים באמצעות פסיק.
LOCAL_SANITIZE_DIAG מפעיל את מצב האבחון. מומלץ להשתמש במצב האבחון רק במהלך בדיקות. מצב האבחון לא מבטל את הפעולה במקרה של הצפת זיכרון, ולכן הוא לא מספק את יתרון האבטחה של אמצעי ההגנה, וגם יוצר עומס גבוה יותר על הביצועים. לכן לא מומלץ להשתמש בו בגרסאות ייצור.
LOCAL_SANITIZE_BLOCKLIST מאפשר לציין קובץ BLOCKLIST
שמאפשר למפתחים למנוע ניקוי של פונקציות וקובצי מקור. משתמשים במאפיין הזה רק אם הביצועים חשובים לכם והקבצים או הפונקציות שמסומנים תורמים לביצועים באופן משמעותי. צריך לבדוק את הקבצים או הפונקציות האלה באופן ידני כדי לוודא שהגישה למערכים בטוחה. פרטים נוספים זמינים במאמר בנושא פתרון בעיות.
השבתת BoundSan
אפשר להשבית את BoundSan בפונקציות ובקובצי מקור באמצעות רשימות חסימה או תכונות של פונקציות. מומלץ להשאיר את BoundSan מופעל, ולכן כדאי להשבית אותו רק אם: הפונקציה או הקובץ יוצרים עומס גדול על הביצועים, וקוד המקור נבדק באופן ידני.
מידע נוסף על השבתת BoundSan באמצעות מאפייני פונקציה ועיצוב קובץ BLOCKLIST זמין במסמכי התיעוד של Clang LLVM. כדי למנוע השפעה על אמצעי חיטוי אחרים, צריך להגדיר את היקף הרשימה השחורה לאמצעי החיטוי הספציפי באמצעות שמות של קטעים שמציינים את אמצעי החיטוי המיועד.
אימות
אין בדיקות CTS ספציפיות ל-BoundSan. במקום זאת, מוודאים שהבדיקות של CTS עוברות עם או בלי BoundSan כדי לוודא שהיא לא משפיעה על המכשיר.
פתרון בעיות
אחרי שמפעילים את BoundSan, חשוב לבדוק היטב את הרכיבים כדי לוודא שטיפלתם בכל הגישות מחוץ לגבולות שלא זוהו קודם.
קל לזהות שגיאות של BoundSan כי הן כוללות את הודעת הביטול הבאה:
pid: ###, tid: ###, name: Binder:### >>> /system/bin/foobar <<< signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- Abort message: 'ubsan: out-of-bounds'
כשמריצים במצב אבחון, קובץ המקור, מספר השורה וערך האינדקס מודפסים ב-logcat. כברירת מחדל, במצב הזה לא מוצגת הודעת ביטול. בודקים את logcat כדי לראות אם יש שגיאות.
external/foo/bar.c:293:13: runtime error: index -1 out of bounds for type 'int [24]'