Android Live-Lock 守護進程 (llkd)

Android 10 包含 Android Live-Lock 守護程序 ( llkd ),旨在捕獲和緩解內核死鎖。 llkd組件提供默認的獨立實現,但您也可以將llkd代碼集成到另一個服務中,作為主循環的一部分或作為單獨的線程。

檢測場景

llkd有兩種檢測場景:持久 D 或 Z 狀態,以及持久堆棧簽名。

持續 D 或 Z 狀態

如果線程處於 D(不間斷睡眠)或 Z(殭屍)狀態且沒有前進的時間超過ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms ,則llkd將終止進程(或父進程)。如果隨後的掃描顯示相同的進程繼續存在,則llkd確認活鎖條件並以提供最詳細錯誤報告的方式使內核恐慌。

llkd包括一個自我看門狗,當llkd鎖定時會發出警報;看門狗是流過主循環的預期時間的兩倍,並且採樣是每個ro.llk_sample_ms

持久堆棧簽名

對於 userdebug 版本, llkd可以使用持久堆棧簽名檢查來檢測內核活鎖。如果處於除 Z 之外的任何狀態的線程有一個持續列出的ro.llk.stack內核符號報告的時間超過ro.llk.timeout_msro.llk.stack.timeout_ms ,則llkd將終止該進程(即使存在 forward調度進度)。如果隨後的掃描顯示相同的進程繼續存在,則llkd確認活鎖條件並以提供最詳細錯誤報告的方式使內核恐慌。

當活鎖條件存在時, lldk檢查會持續持續,並在 Linux 上的/proc/pid/stack文件中查找組合字符串" symbol+0x"" symbol.cfi+0x" 。符號列表在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] -spawned 線程:

  • 驅動程序不得保持在持續的 D 狀態,

要么

  • 如果線程被外部終止,驅動程序必須具有恢復線程的機制。例如,使用wait_event_interruptible()而不是wait_event()

如果滿足上述條件之一,可以調整llkd黑名單以覆蓋內核組件。堆棧符號檢查涉及一個額外的進程黑名單,以防止阻止ptrace操作的服務違反 sepolicy。

安卓屬性

llkd響應幾個 Android 屬性(如下所列)。

  • 名為prop_ms的屬性以毫秒為單位。
  • 對列表使用逗號 (,) 分隔符的屬性使用前導分隔符來保留默認條目,然後分別添加或減去帶有可選加 (+) 和減 (-) 前綴的條目。對於這些列表,字符串“false”是空列表的同義詞,空白或缺失的條目採用指定的默認值。

ro.config.low_ram

設備配置了有限的內存。

ro.debuggable

設備配置為 userdebug 或 eng build。

ro.llk.sysrq_t

如果屬性是“eng”,則默認不是ro.config.low_ramro.debuggable 。如果為真,則轉儲所有線程( sysrq t )。

ro.llk.enable

允許啟用活鎖守護程序。默認為假。

llk.enable

評估為 eng 構建。默認為ro.llk.enable

ro.khungtask.enable

允許啟用[khungtask]守護程序。默認為假。

khungtask.enable

評估為 eng 構建。默認為ro.khungtask.enable

ro.llk.mlockall

啟用對mlockall()的調用。默認為假。

ro.khungtask.timeout

[khungtask]最大時間限制。默認值為 12 分鐘。

ro.llk.timeout_ms

D 或 Z 最大時間限制。默認值為 10 分鐘。將該值加倍以設置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僅在 userdebug 或 eng build 上有效

ro.llk.check_ms

D 或 Z 的線程樣本。默認為兩分鐘。

ro.llk.stack

檢查內核堆棧符號,如果這些符號持續存在,則表明子系統已鎖定。默認為cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable逗號分隔的內核符號列表。除了在 r ro.llk.stack.timeout_ms期間輪詢每個ro.llk_check_ms之外,該檢查不會執行前向調度 ABA,因此堆棧符號應該非常罕見且短暫(符號不太可能持續出現堆棧的樣本)。在堆棧擴展中檢查" symbol+0x"" symbol.cfi+0x"匹配。僅在 userdebug 或 eng build 上可用;用戶構建的安全問題會導致權限受限,從而阻止此檢查。

ro.llk.blacklist.process

llkd不監視指定的進程。默認值為0,1,2kernelinit[kthreadd] )加上進程名稱init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1] 。進程可以是commcmdlinepid引用。自動默認值可以大於當前最大屬性大小 92。

ro.llk.blacklist.parent

llkd不監視具有指定父進程的進程。默認值為0,2,adbd&[setsid]kernel[kthreadd]adbd僅適用於殭屍setsid )。與號 (&) 分隔符指定僅在與目標子進程組合時才忽略父進程。選擇 & 是因為它從來不是進程名稱的一部分;但是,shell 中的setprop需要轉義或引用 & 符號,儘管通常指定它的init rc文件沒有這個問題。父進程或目標進程可以是commcmdlinepid引用。

ro.llk.blacklist.uid

llkd不監視與指定 uid 匹配的進程。以逗號分隔的 uid 編號或名稱列表。默認為空或假。

ro.llk.blacklist.process.stack

llkd不監視指定的進程子集的活鎖堆棧簽名。默認是進程名稱init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd 。防止與阻止ptrace的進程相關的 sepolicy 違規(因為這些無法檢查)。僅在 userdebug 和 eng build 上有效。有關構建類型的詳細信息,請參閱構建 Android

架構問題

  • 屬性限制為 92 個字符(但是,對於源代碼的include/llkd.h文件中定義的默認值,這將被忽略)。
  • 內置的[khungtask]守護進程過於通用,並且會在過多處於 D 狀態的驅動程序代碼上絆倒。切換到 S 將使任務可以終止(如果需要,可以由驅動程序恢復)。

庫接口(可選)

您可以選擇使用libllkd組件中的以下 C 接口將llkd合併到另一個特權守護程序中:

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

如果提供了線程名,則會自動生成一個線程,否則調用者必須在其主循環中調用llkCheckMilliseconds 。該函數返回下一次預期調用此處理程序之前的時間段。