כלי ניקוי כתובות

AddressSanitizer‏ (ASan) הוא כלי מהיר מבוסס-מְהַמֵר לזיהוי באגים בזיכרון בקוד מקורי.

ASan מזהה:

  • זליגה/חוסר מקום במאגרים של סטאק ושל אשכול
  • שימוש בערימה אחרי בחינם
  • שימוש ב-Stack מחוץ להיקף
  • Double free/wild free

ASan פועלת גם ב-ARM של 32 ביט וגם ב-ARM של 64 ביט, וגם ב-x86 וב-x86-64. תקורת ה-CPU של ASan היא כפולה בערך, תקורת גודל הקוד היא בין 50% ל-2x ותקורת זיכרון גדולה (תלוי בדפוסי ההקצאה שלכם, אבל בסדר גודל של 2x).

Android 10 וההסתעפות הראשית של AOSP ב-AArch64 תומכים ב-Hardware AssistSanitizer (HWASan), כלי דומה עם תקורת RAM נמוכה יותר ומגוון רחב יותר של באגים שזוהו. HWASan מזהה שימוש בסטאק אחרי החזרה, בנוסף לבאגים שזוהו על ידי ASan.

ל-HWASan יש עלות ריצה דומה של מעבד וקוד, אבל עלות ריצה קטנה בהרבה של זיכרון RAM (15%). בדיקת HWASan היא לא ודאית. יש רק 256 ערכים אפשריים לתג, כך שיש סיכוי של 0.4% בלבד לפספס באג. ל-HWAsan אין את האזורים האדומים בגודל מוגבל של ASan לזיהוי זליגות זיכרון, ואין לו את המרחב המוגבל בבידוד לזיהוי שימוש לאחר שחרור (UAF). לכן, לא משנה ל-HWAsan כמה גדול הזליג או כמה זמן חלף מאז שהזיכרון הוקצה מחדש. לכן, HWASan טוב יותר מ-ASan. מידע נוסף על העיצוב של HWASan ועל השימוש ב-HWASan ב-Android

ASan מזהה זליגות זיכרון בסטאק או ברמת המערכת, בנוסף לזליגות זיכרון ב-heap, והוא מהיר עם תקורה מינימלית של זיכרון.

במסמך הזה נסביר איך ליצור ולהריץ חלקים או את כל Android באמצעות Asan. אם אתם מפתחים אפליקציית SDK או NDK עם ASan, תוכלו לעיין במקום זאת במאמר ניקוי כתובות (Address Sanitizer).

חיטוי קובצי הפעלה ספציפיים באמצעות ASan

מוסיפים את LOCAL_SANITIZE:=address או את sanitize: { address: true } לכלל ה-build של קובץ ההפעלה. אפשר לחפש בקוד דוגמאות קיימות או למצוא את שאר הכלי לניקוי נתונים שזמינים.

כשמזוהה באג, ASan מדפיסה דוח מפורט גם לפלט הסטנדרטי וגם ל-logcat, ואז קורסת את התהליך.

ניטרול ספריות משותפות באמצעות ASan

בגלל אופן הפעולה של ASan, אפשר להשתמש בספרייה שנוצרה באמצעות ASan רק בקובץ הפעלה שנוצר באמצעות ASan.

כדי לנקות ספרייה משותפת שמשמשת בכמה קובצי הפעלה, שחלקם לא נוצרו באמצעות ASan, צריך שני עותקים של הספרייה. הדרך המומלצת לעשות זאת היא להוסיף את הקוד הבא ל-Android.mk בשביל המודול הרלוונטי:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

כך הספרייה תופיע ב-/system/lib/asan במקום ב-/system/lib. לאחר מכן מריצים את קובץ ההפעלה באמצעות:

LD_LIBRARY_PATH=/system/lib/asan

לדימוני מערכת, צריך להוסיף את הקטע הבא לקטע המתאים של /init.rc או /init.$device$.rc.

setenv LD_LIBRARY_PATH /system/lib/asan

בודקים אם התהליך משתמש בספריות מ-/system/lib/asan כשהן נמצאות, על ידי קריאת /proc/$PID/maps. אם היא לא מותקנת, ייתכן שתצטרכו להשבית את SELinux:

adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.

מעקבי ערימה משופרים

ASan משתמשת בתוכנת Unwinder מהירה שמבוססת על פריים כדי לתעד דוח קריסות לכל אירוע של הקצאת זיכרון והקצאת נתונים בתוכנית. רוב מערכות Android מבוססות על בלי סמן מסגרות. כתוצאה מכך, בדרך כלל מתקבלים רק פריים אחד או שניים משמעותיים. כדי לפתור את הבעיה, צריך ליצור מחדש את הספרייה עם ASan (מומלץ!) או עם:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

לחלופין, אפשר להגדיר את ASAN_OPTIONS=fast_unwind_on_malloc=0 בסביבת התהליך. האפשרות השנייה עשויה להיות מאוד דורשת משאבי מעבד (CPU), בהתאם לעומס.

סימבוליזציה

בשלב הראשון, דוחות ASan מכילים הפניות לשינויים בקבצים הבינאריים ובספריות המשותפות. יש שתי דרכים לקבל מידע על קובץ המקור והשורה:

  • מוודאים שהקובץ הבינארי llvm-symbolizer נמצא ב-/system/bin. llvm-symbolizer נוצר ממקורות ב-third_party/llvm/tools/llvm-symbolizer.
  • מסננים את הדוח באמצעות הסקריפט external/compiler-rt/lib/asan/scripts/symbolize.py.

הגישה השנייה יכולה לספק יותר נתונים (כלומר, file:line מיקומים) בגלל הזמינות של ספריות מסומנות במארח.

ASan באפליקציות

ל-ASan אין אפשרות לראות את קוד Java, אבל היא יכולה לזהות באגים בספריות JNI. לשם כך, צריך ליצור את קובץ ההפעלה באמצעות ASan, שהוא /system/bin/app_process(32|64) במקרה הזה. כך אפשר להפעיל את ASan בכל האפליקציות במכשיר בו-זמנית, וזה עומס כבד, אבל מכשיר עם 2GB של RAM אמור להתמודד עם זה.

מוסיפים את LOCAL_SANITIZE:=address לכלל ה-build app_process ב-frameworks/base/cmds/app_process. בינתיים אפשר להתעלם מהיעד app_process__asan באותו קובץ (אם הוא עדיין שם כשקוראים את הטקסט).

עורכים את הקטע service zygote בקובץ system/core/rootdir/init.zygote(32|64).rc המתאים, ומוסיפים את השורות הבאות לבלוק השורות עם הפסקה שמכיל את class main, עם אותה כמות הפסקה:

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

פיתוח, סנכרון adb, אתחול מהיר ב-fastboot ואתחול מחדש.

שימוש במאפיין wrap

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

כדי לעשות זאת, מפעילים את האפליקציה עם המאפיין wrap.. בדוגמה הבאה מריצים את אפליקציית Gmail ב-ASAN:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

בהקשר הזה, asanwrapper משכתב את /system/bin/app_process ל-/system/bin/asan/app_process, שנוצר באמצעות ASan. הוא גם מוסיף את הערך /system/lib/asan בתחילת נתיב החיפוש של הספרייה הדינמית. כך ספריות עם ASan מ-/system/lib/asan מועדפות על פני ספריות רגילות ב-/system/lib כשהן פועלות עם asanwrapper.

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

SANITIZE_TARGET

ב-Android 7.0 ואילך יש תמיכה ביצירת כל פלטפורמת Android באמצעות ASan בבת אחת. (אם אתם מפתחים גרסה חדשה יותר מ-Android 9, עדיף להשתמש ב-HWASan).

מריצים את הפקודות הבאות באותו עץ build.

make -j42
SANITIZE_TARGET=address make -j42

במצב הזה, userdata.img מכיל ספריות נוספות וגם צריך לשדרג אותן למכשיר. משתמשים בשורת הפקודה הבאה:

fastboot flash userdata && fastboot flashall

הפקודה הזו יוצרת שתי קבוצות של ספריות משותפות: ספרייה רגילה ב-/system/lib (הקריאה הראשונה של make) וספרייה עם ASan ב-/data/asan/lib (הקריאה השנייה של make). קובצי ההפעלה מה-build השני מחליפים את קובצי ההפעלה מה-build הראשון. קובצי הפעלה עם ASan מקבלים נתיב חיפוש ספרייה שונה שכולל את /data/asan/lib לפני /system/lib באמצעות השימוש ב-/system/bin/linker_asan ב-PT_INTERP.

מערכת ה-build מוחקת את ספריות האובייקטים של הביניים כשהערך של $SANITIZE_TARGET משתנה. הפקודה הזו מאלצת בנייה מחדש של כל היעדים תוך שמירה על קובצי ה-binary המותקנים ב-/system/lib.

לא ניתן ליצור יעדים מסוימים באמצעות ASan:

  • קובצי הפעלה מקושרים באופן סטטי
  • יעדים של LOCAL_CLANG:=false
  • LOCAL_SANITIZE:=false לא אומתו על ידי ASan עבור SANITIZE_TARGET=address

קובצי הפעלה כאלה מושמטים מה-build של SANITIZE_TARGET, והגרסה מהקריאה הראשונה של make נשארת ב-/system/bin.

ספריות כאלה נוצרות ללא ASan. הן יכולות להכיל קוד ASan מהספריות הסטטיות שהן תלויות בהן.

מסמכים תומכים