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 מהספריות הסטטיות עליהן הן תלויות.