Scudo

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

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

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

‫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 ו-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: כשמשתמשים באופרטור delete בגודל C++14 והבדיקה האופציונלית מופעלת, יש חוסר התאמה בין הגודל שהועבר כשמבטלים הקצאה של מקטע לבין הגודל שהתבקש כשמבצעים הקצאה שלו. בדרך כלל מדובר בבעיה בקומפיילר או בבלבול סוגים באובייקט שמוקצה לו זיכרון.
  • RSS limit exhausted: חרגתם מהערך המקסימלי של RSS שצוין.

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