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 הזה.