Android 即時鎖定 Daemon (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 會確認活鎖狀態,並以可提供該狀態最詳細錯誤報告的方式讓核心進入恐慌狀態。

llkd 包含自我監控犬,在 llkd 鎖定時鬧鐘會發出鬧鐘;監控計時器的預期時間會兩倍,且每 ro.llk_sample_ms 取樣一次。

持續性堆疊簽章

針對使用者偵錯版本,llkd 可以使用永久堆疊簽章檢查功能偵測核心即時鎖定。如果 Z 以外狀態的執行緒含有持續列出的 ro.llk.stack 核心符號,且回報時間長於 ro.llk.timeout_msro.llk.stack.timeout_msllkd 就會終止程序 (即使有向前排程進度)。如果後續掃描顯示相同程序仍然存在,llkd 會確認即時鎖定條件和恐慌,進而為條件提供最詳細的錯誤報告。

當存在即時鎖定條件時,lldk 檢查會持續進行,並在 Linux 的 /proc/pid/stack 檔案中尋找組合字串 symbol+0xsymbol.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] 產生的執行緒:

  • 驅動程式不得處於永久的 D 狀態。

  • 驅動程式必須具備機制,以便在執行緒遭到外部終止時復原。例如,使用 wait_event_interruptible() 而非 wait_event()

如果符合上述任一條件,llkd 拒絕清單即可調整為涵蓋核心元件。堆疊符號檢查會涉及額外的程序拒絕清單,以防止服務封鎖 ptrace 作業時違反安全政策。

Android 屬性

llkd 會回應多個 Android 屬性 (如下所列)。

  • 名為 prop_ms 的屬性以毫秒為單位。
  • 使用半形逗號 (,) 分隔清單的屬性會使用開頭分隔符來保留預設項目,然後分別使用可選加號 (+) 和減號 (-) 前置字串來新增或減去項目。對於這些清單,字串 false 與空清單同義,空白或缺少的項目會採用指定的預設值。

ro.config.low_ram

裝置的記憶體配置有限。

ro.debuggable

裝置已針對 userdebug 或 eng 版本進行設定。

ro.llk.sysrq_t

如果屬性是 eng,則預設值為 ro.config.low_ramro.debuggable。如果是 true,則會傾印所有執行緒 (sysrq t)。

ro.llk.enable

允許啟用即時鎖定守護程序。預設值為 false

llk.enable

針對工程師版本進行評估。預設值為 ro.llk.enable

ro.khungtask.enable

允許啟用 [khungtask] Daemon。預設值為 false

khungtask.enable

針對工程師版本進行評估。預設值為 ro.khungtask.enable

ro.llk.mlockall

啟用呼叫 mlockall()。預設值為 false

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 版本中啟用

ro.llk.check_ms

D 或 Z 的執行緒範例。預設值為兩分鐘。

ro.llk.stack

檢查核心堆疊符號,如果持續存在,可能表示子系統已鎖定。預設為以逗號分隔的核心符號清單。cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable檢查作業不會執行 ABA 的順向排程,除非在 ro.llk.stack.timeout_ms 期間每隔 ro.llk_check_ms 執行一次輪詢,因此堆疊符號應極為罕見且短暫 (符號不太可能會持續顯示在堆疊的所有樣本中)。檢查堆疊展開中是否有 symbol+0xsymbol.cfi+0x 的配對項目。僅適用於 userdebug 或 eng 版本;使用者版本的安全性問題會導致權限受限,無法進行這項檢查。

ro.llk.blacklist.process

llkd 不會監控指定的程序。預設值為 0,1,2 (kernelinit[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)。And (&) 分隔符指定只在與目標子程序搭配使用時才忽略父項。之所以選擇 AND 符號,是因為它從不是程序名稱的一部分,所以已選取了,不過殼層中的 setprop 需要逸出或加上引號,但通常指定的 init rc 檔案不會有這個問題。父項或目標程序可以是 commcmdlinepid 參照。

ro.llk.blacklist.uid

llkd 不會監控符合指定 UID 的程序。以半形逗號分隔的 UIS 編號或名稱清單。預設值為空白或 false

ro.llk.blacklist.process.stack

llkd 不會監控指定的程序子集,以便取得即時鎖定堆疊簽章。預設為處理程序名稱 init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd。防止與封鎖 ptrace 的程序相關的 sepolicy 違規 (因為無法檢查這些程序)。僅在 userdebug 和 eng 版本中啟用。如要進一步瞭解建構類型,請參閱「建構 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。這個函式會傳回下次預期呼叫此處理常式前的時間長度。