AddressSanitizer

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

ASan מזהה:

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

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

ב-Android 10 ובהסתעפות הראשית של AOSP ב-AArch64 יש תמיכה ב-Hardware-assisted AddressSanitizer‏ (HWASan), כלי דומה עם פחות עומס על זיכרון ה-RAM ומגוון רחב יותר של באגים שזוהו. ‏HWAsan מזהה שימוש ב-stack אחרי חזרה, בנוסף לבאגים ש-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 ואילך יש תמיכה ב-ASan ליצירת כל פלטפורמת Android בבת אחת. (אם אתם מפתחים גרסה חדשה יותר מ-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 מהספריות הסטטיות עליהן הן תלויות.

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