Scudo

‫Scudo הוא כלי דינמי להקצאת זיכרון במצב משתמש, או כלי להקצאת ערימה, שנועד להיות עמיד בפני נקודות חולשה שקשורות לערימה (כמו גלישת חוצץ מבוססת-ערימה, שימוש אחרי שחרור ושחרור כפול) תוך שמירה על הביצועים. היא מספקת את הפרימיטיבים הסטנדרטיים של הקצאה וביטול הקצאה ב-C (כמו malloc ו-free), וגם את הפרימיטיבים של C++‎ (כמו new ו-delete).

‫Scudo הוא יותר אמצעי להפחתת הסיכון מאשר כלי מלא לזיהוי שגיאות זיכרון כמו AddressSanitizer‏ (ASan).

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

‫Scudo הוא קוד פתוח וחלק מפרויקט compiler-rt של LLVM. מסמכי התיעוד זמינים בכתובת https://llvm.org/docs/ScudoHardenedAllocator.html. ה-runtime של Scudo נשלח כחלק משרשרת הכלים של Android, ותמיכה נוספה ל-Soong ול-Make כדי לאפשר הפעלה קלה של מקצה הזיכרון בקובץ בינארי.

אפשר להפעיל או להשבית אמצעי הגנה נוספים בתוך מנהל ההקצאה באמצעות האפשרויות שמתוארות בהמשך.

התאמה אישית

אפשר להגדיר חלק מהפרמטרים של מנהל ההקצאה לכל תהליך בנפרד, בכמה דרכים:

  • באופן סטטי: מגדירים פונקציה __scudo_default_options בתוכנית שמחזירה את מחרוזת האפשרויות שצריך לנתח. הפונקציה הזו צריכה להיות עם אב-טיפוס כזה: extern "C" const char *__scudo_default_options().
  • באופן דינמי: משתמשים במשתנה הסביבה SCUDO_OPTIONS שמכיל את מחרוזת האפשרויות שצריך לנתח. הגדרות שמוגדרות בדרך הזו מבטלות כל הגדרה שנעשתה דרך __scudo_default_options.

אלו האפשרויות הזמינות:

אפשרות ברירת מחדל של 64 ביט ברירת מחדל של 32 ביט תיאור
QuarantineSizeKb 256 64 הגודל (ב-KB) של ההסגר שמשמש לעיכוב של ביטול ההקצאה בפועל של נתחים. ערך נמוך יותר עשוי לצמצם את השימוש בזיכרון, אבל להפחית את יעילות ההקלה. ערך שלילי יחזור לברירות המחדל. אם מגדירים את שני הערכים האלה (ThreadLocalQuarantineSizeKb וגם הערך הזה) לאפס, ההסגר מושבת לגמרי.
QuarantineChunksUpToSize 2048 512 הגודל (בבייטים) של נתחים שאפשר להכניס להסגר.
ThreadLocalQuarantineSizeKb 64 16 הגודל (ב-KB) של השימוש במטמון לכל שרשור כדי להפחית עומס מההסגר הגלובלי. ערך נמוך יותר עשוי להקטין את השימוש בזיכרון, אבל יכול להיות שהוא יגדיל את התחרות על ההסגר הגלובלי. הגדרת שני הערכים האלה וגם QuarantineSizeKb לאפס משביתה את ההסגר לחלוטין.
DeallocationTypeMismatch false false הפעלת דיווח שגיאות ב-malloc/delete, ‏ new/free, ‏ new/delete[]
DeleteSizeMismatch true true הפעלת דיווח על שגיאות שקשורות לחוסר התאמה בין הגדלים של new ו-delete.
ZeroContents false false הגדרה שמאפשרת להקצות ולבטל הקצאה של תוכן עם אפס נתונים.
allocator_may_return_null false false מציין שהקצאת הזיכרון יכולה להחזיר ערך null כשמתרחשת שגיאה שניתן לשחזר, במקום לסיים את התהליך.
hard_rss_limit_mb 0 0 כשה-RSS של התהליך מגיע למגבלה הזו, התהליך מסתיים.
soft_rss_limit_mb 0 0 כשה-RSS של התהליך מגיע למגבלה הזו, הקצאות נוספות נכשלות או מחזירות null (בהתאם לערך של allocator_may_return_null), עד שה-RSS יורד שוב כדי לאפשר הקצאות חדשות.
allocator_release_to_os_interval_ms 5000 לא רלוונטי ההגדרה משפיעה רק על מקצה זיכרון של 64 ביט. אם הערך מוגדר, המערכת מנסה לפנות זיכרון שלא נמצא בשימוש למערכת ההפעלה, אבל לא בתדירות גבוהה יותר מהמרווח הזה (באלפיות השנייה). אם הערך שלילי, הזיכרון לא משוחרר למערכת ההפעלה.
abort_on_error true true אם היא מוגדרת, הכלי קורא ל-abort() במקום ל-_exit() אחרי הדפסת הודעת השגיאה.

אימות

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

פתרון בעיות

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

ריכזנו כאן רשימה של הודעות השגיאה הנוכחיות והסיבות האפשריות להן:

  • corrupted chunk header: אימות סכום הביקורת של כותרת המקטע נכשל. הסיבה לכך היא כנראה אחת משתי האפשרויות הבאות: הכותרת נמחקה (חלקית או מלאה), או שהמצביע שהועבר לפונקציה הוא לא נתח.
  • race on chunk header: שני שרשורים שונים מנסים לשנות את אותה כותרת באותו הזמן. בדרך כלל זה מעיד על מצב מירוץ או על חוסר נעילה כללי כשמבצעים פעולות על החלק הזה.
  • invalid chunk state: הצ'אנק לא נמצא במצב הצפוי לפעולה מסוימת. לדוגמה, הוא לא מוקצה כשמנסים לשחרר אותו, או שהוא לא בהסגר כשמנסים למחזר אותו. הסיבה הנפוצה לשגיאה הזו היא שחרור כפול של זיכרון.
  • misaligned pointer: נאכפות דרישות בסיסיות של יישור: 8 בייטים בפלטפורמות של 32 ביט ו-16 בייטים בפלטפורמות של 64 ביט. אם מצביע שמועבר לפונקציות שלנו לא מתאים לאלה, המצביע שמועבר לאחת מהפונקציות לא מיושר.
  • allocation type mismatch: כשהאפשרות הזו מופעלת, פונקציית ביטול ההקצאה שמופעלת על נתח צריכה להיות תואמת לסוג הפונקציה שהופעלה כדי להקצות אותו. חוסר התאמה כזה עלול לגרום לבעיות אבטחה.
  • invalid sized delete: כשמשתמשים באופרטור המחיקה בגודל C++14 והבדיקה האופציונלית מופעלת, יש חוסר התאמה בין הגודל שהועבר כשמבטלים את ההקצאה של מקטע לבין הגודל שהתבקש כשמבצעים הקצאה. בדרך כלל מדובר בבעיה בקומפיילר או בבלבול סוגים באובייקט שמוקצה לו זיכרון.
  • RSS limit exhausted: חרגתם מהמספר המקסימלי של פרקים בפיד ה-RSS שציינתם (אם ציינתם).

אם מנפים באגים בקריסה במערכת ההפעלה עצמה, אפשר להשתמש בגרסת build של מערכת ההפעלה HWASan. אם מנפים באגים בקריסה באפליקציה, אפשר להשתמש גם בגרסת build של אפליקציית HWASan.