דימון (daemon) של Android Live-lock (llkd)

Android 10 כולל את הדימון של Android לנעילה דינמית (llkd), שנועד לזהות ולצמצם נעילות לולאה של הליבה. הרכיב llkd מספק הטמעה עצמאית שמוגדרת כברירת מחדל, אבל אפשר גם לשלב את הקוד של llkd בשירות אחר, כחלק מהמחזור הראשי או כחוט נפרד.

תרחישים של זיהוי

ל-llkd יש שני תרחישים לזיהוי: מצב D או Z מתמיד וחתימה מתמידה על סטאק.

מצב D או Z מתמיד

אם חוט נמצא בסטטוס D (שינה ללא אפשרות התעוררות) או בסטטוס Z (זומבי) בלי התקדמות במשך יותר מ-ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms, הפונקציה llkd תהרוג את התהליך (או את תהליך ההורה). אם בסריקה הבאה יתברר שאותו תהליך עדיין קיים, llkd יאשר את התנאי של נעילת חיים ויגרום לסטטוס חרום (panic) בליבה, כך שיוצג דוח הבאג המפורט ביותר לגבי התנאי.

llkd כולל טיימר מפקח (watchdog) ששולח התראות אם llkd ננעל. הטיימר המפקח (watchdog) כפול מזמן הזמן הצפוי לזרום דרך הלולאה הראשית, והדגימה מתבצעת כל ro.llk_sample_ms.

חתימת סטאק קבועה

במהדורות userdebug, ה-llkd יכול לזהות נעילות ליבה פעילות באמצעות בדיקת חתימות על סטאק מתמיד. אם בשרשור בכל מצב חוץ מ-Z יש סמל ליבה (kernel) קבוע של ro.llk.stack שדווח למשך יותר מ-ro.llk.timeout_ms או ro.llk.stack.timeout_ms, ה-llkd יסיים את התהליך (גם אם יש התקדמות בתזמון העברה). אם בסריקה הבאה יתברר שאותו תהליך עדיין קיים, ה-llkd יאשר את התנאי של נעילת חיים (live-lock) ויגרום לסטטוס חרום (panic) בליבה, כך שיוצג דוח הבאג המפורט ביותר לגבי התנאי.

הבדיקה של lldk מתבצעת באופן קבוע כשהתנאי של הנעילה הפעילה קיים, ומחפשת את המחרוזות המורכבות symbol+0x או symbol.cfi+0x בקובץ /proc/pid/stack ב-Linux. רשימת הסמלים נמצאת ב-ro.llk.stack והיא מוגדרת כברירת מחדל כרשימה מופרדת בפסיקים של cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable.

הסמלים צריכים להיות נדירים וקצרים טווח מספיק, כך שבמערכת רגילה הפונקציה תופיע רק פעם אחת במדגם במהלך פרק הזמן של ro.llk.stack.timeout_ms (המדגמים מתרחשים כל ro.llk.check_ms). בגלל היעדר הגנה ABA, זו הדרך היחידה למנוע הפעלה שגויה. פונקציית הסמל חייבת להופיע מתחת לפונקציה שמפעילה את הנעילה שיכולה להכיל את הרצף. אם המנעול נמצא מתחת לפונקציה של הסמל או בתוכה, הסמל מופיע בכל התהליכים המושפעים, ולא רק בזה שגרם לנעילת המערכת.

כיסוי

ההטמעה שמוגדרת כברירת מחדל של llkd לא עוקבת אחרי יצירת תהליכים של init,‏ [kthreadd] או [kthreadd]. כדי שהllkd יכסה שרשורים שנוצרו על ידי [kthreadd]:

  • הנהגים לא יכולים להישאר במצב D קבוע,

או

  • לדרייברים חייבים להיות מנגנונים לשחזור של השרשור במקרה שהוא יופסק באופן חיצוני. לדוגמה, צריך להשתמש ב-wait_event_interruptible() במקום ב-wait_event().

אם אחד מהתנאים שלמעלה מתקיים, אפשר להתאים את רשימת הישויות שנחסמו llkd כך שתכסה את רכיבי הליבה. בדיקת הסמל ב-Stack כוללת רשימת דחייה נוספת של תהליכים כדי למנוע הפרות של מדיניות האבטחה בשירותים שחוסמים פעולות ptrace.

נכסי Android

השדה llkd מגיב למספר מאפיינים של Android (המפורטים בהמשך).

  • המאפיינים שנקראים prop_ms הם באלפיות שנייה.
  • במאפיינים שמשתמשים בפסיק (,) כמפריד ברשימות, נעשה שימוש במפריד מוביל כדי לשמור על הערך שמוגדר כברירת מחדל, ולאחר מכן מוסיפים או מחסירים ערכים באמצעות הקידומות האופציונליות 'פלוס' (+) ו'מינוס' (-) בהתאמה. ברשימות האלה, המחרוזת false היא שם נרדף לרשימה ריקה, ורשומות ריקות או חסרות יקבלו את ערך ברירת המחדל שצוין.

ro.config.low_ram

המכשיר מוגדר עם נפח זיכרון מוגבל.

ro.debuggable

המכשיר מוגדר ל-userdebug או ל-eng build.

ro.llk.sysrq_t

אם המאפיין הוא eng, ברירת המחדל היא לא ro.config.low_ram או ro.debuggable. אם true, פורקים את כל השרשורים (sysrq t).

ro.llk.enable

אישור להפעלת דימון (daemon) של נעילה בשידור חי. ברירת המחדל היא false.

llk.enable

הערכה של גרסאות build של מהנדסי תוכנה. ברירת המחדל היא ro.llk.enable.

ro.khungtask.enable

מאפשרים להפעיל את הדימון [khungtask]. ברירת המחדל היא false.

khungtask.enable

בוצעה הערכה לפיתוח גרסאות build לעידוד השימוש באפליקציה. ברירת המחדל היא ro.khungtask.enable.

ro.llk.mlockall

מפעילים את האפשרות 'שיחה אל' mlockall(). ברירת המחדל היא false.

ro.khungtask.timeout

מגבלת זמן של [khungtask] לכל היותר. ברירת המחדל היא 12 דקות.

ro.llk.timeout_ms

מגבלת זמן מקסימלית של D או Z. ברירת המחדל היא 10 דקות. כפול הערך הזה כדי להגדיר את הטיימר המפקח (watchdog) של ההתראה llkd.

ro.llk.D.timeout_ms

מגבלת הזמן המקסימלית של D. ברירת המחדל היא ro.llk.timeout_ms.

ro.llk.Z.timeout_ms

מגבלת הזמן המקסימלית של Z. ברירת המחדל היא ro.llk.timeout_ms.

ro.llk.stack.timeout_ms

בדיקה של מגבלת הזמן המקסימלית לסמלי סטאק עקביים. ברירת המחדל היא ro.llk.timeout_ms. האפשרות הזו פעילה רק בגרסאות build מסוג userdebug או eng.

ro.llk.check_ms

דוגמאות לחוטים של D או Z. ברירת המחדל היא שתי דקות.

ro.llk.stack

חיפוש סמלים של ערימת ליבה (kernel), שאם הם קיימים באופן קבוע עלולים להעיד על כך שמערכת המשנה נעולה. ברירת המחדל היא cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable, רשימה של סמלי הליבה מופרדים בפסיקים. הבדיקה לא מבצעת תזמון קדימה של ABA, אלא רק באמצעות סקרים כל ro.llk_check_ms במהלך התקופה ro.llk.stack.timeout_ms, כך שסמלי ה-stack אמורים להיות נדירים מאוד וחולפים (סביר מאוד שסמל לא יופיע באופן עקבי בכל הדוגמאות של ה-stack). הבדיקה מחפשת התאמה ל-symbol+0x או ל-symbol.cfi+0x בהרחבת ה-stack. הבדיקה זמינה רק בגרסאות build של userdebug או של eng. בעיות אבטחה בגרסאות build של משתמשים גורמות להרשאות מוגבלות שמונעות את הבדיקה הזו.

ro.llk.black.process

llkd לא עוקב אחרי התהליכים שצוינו. ברירת המחדל היא 0,1,2 (kernel, init ו-[kthreadd]) בתוספת שמות התהליכים init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]. תהליך יכול להיות הפניה מסוג comm,‏ cmdline או pid. ברירת המחדל האוטומטית יכולה להיות גדולה יותר מהגודל המקסימלי הנוכחי של נכס, שהוא 92.

ro.llk.blacklist.parent

ה-llkd לא עוקב אחרי תהליכים שיש להם את ההורים שצוינו. ברירת המחדל היא 0,2,adbd&[setsid] (kernel,‏ [kthreadd] ו-adbd רק לזומבי setsid). התו האמפרסנד (&) מפריד ומציין שההורה יתעלם רק בשילוב עם תהליך הצאצא היעד. אמפרסנד נבחר כי הוא אף פעם לא חלק משם תהליך. עם זאת, setprop במעטפת מחייב את האמפרסנד צריך לסמן בתו בריחה (escape) או מירכאות, אבל אין את הבעיה הזו בקובץ init rc שבו מצוין בדרך כלל. תהליך הורה או יעד יכול להיות הפניה ל-comm, ל-cmdline או ל-pid.

ro.llk.blacklist.uid

ה-llkd לא עוקב אחרי תהליכים שתואמים למזהי ה-UID שצוינו. רשימה מופרדת בפסיקים של שמות או מספרים של UIS. ברירת המחדל היא ריקה או false.

ro.llk.black.process.stack

הפונקציה llkd לא עוקבת אחרי חתימה של סטאק נעול פעיל בקבוצת המשנה שצוינה של התהליכים. ברירת המחדל היא שמות תהליכים init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd. מניעת הפרת מדיניות האבטחה שמשויכת לתהליכים החוסמים את ptrace (כי אי אפשר לבדוק אותם). האפשרות הזו פעילה רק בגרסאות build של userdebug ו-eng. למידע נוסף על סוגי ה-build, אפשר לקרוא את המאמר פיתוח Android.

בעיות ארכיטקטוניות

  • האורך המקסימלי של המאפיינים הוא 92 תווים (עם זאת, המערכת מתעלמת מהמגבלה הזו לגבי ברירת המחדל שמוגדרת בקובץ include/llkd.h במקורות).
  • הדימון המובנה ב-[khungtask] הוא כללי מדי ולכן נסיעות עם קוד הנהג שנמצאות במצב D יותר מדי. אם תעברו ל-S, המשימות תמיד יהיו בלתי נשכחות (והנהגים יוכלו לשחזר אותן במקרה הצורך).

ממשק הספרייה (אופציונלי)

אפשר לשלב את llkd בדימון אחר בעל הרשאות באמצעות ממשק ה-C הבא מהרכיב libllkd:

#include "llkd.h"
bool llkInit(const char* threadname) /* return true if enabled */
unsigned llkCheckMillseconds(void)   /* ms to sleep for next check */

אם מציינים שם של שרשור, נוצר השרשור באופן אוטומטי. אחרת, מבצע הקריאה החוזרת יצטרך לקרוא ל-llkCheckMilliseconds בלופ הראשי שלו. הפונקציה מחזירה את פרק הזמן לפני הקריאה הצפויה הבאה ל-handler הזה.