Android Live-LocK Daemon(llkd)

Android 10 には、Android Live-LocK Daemon(llkd)が搭載されています。これは、カーネルのデッドロックを検出して軽減するためのものです。llkd コンポーネントは、デフォルトではスタンドアロンで実装されていますが、llkd のコードを(メインループの一部または別のスレッドとして)他のサービスに統合することもできます。

検出シナリオ

llkd の検出シナリオには、「永続的 D または Z 状態」、「永続的スタック署名」の 2 つがあります。

永続的 D または Z 状態

スレッドが D(割り込み不可スリープ)または Z(ゾンビ)状態のまま ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms より長い時間前進しない場合、llkd はそのプロセス(または親プロセス)を強制終了します。次のスキャン時にまだそのプロセスが存在している場合、llkd はライブロック状態を確認し、その状態が最も詳細にバグレポートに出力される方法でカーネル パニックを発生させます。

llkd には、llkd がロック状態になった場合にアラームを出すセルフ ウォッチドッグが含まれています。ウォッチドッグ タイマーはメインループ全体の予想処理時間の 2 倍であり、サンプリングは ro.llk_sample_ms ごとです。

永続的スタック署名

userdebug リリースの場合、llkd は永続的スタック署名チェックを使用してカーネルのライブロックを検出できます。Z 以外のいずれかの状態のスレッドに、ro.llk.timeout_ms または ro.llk.stack.timeout_ms よりも長い間 ro.llk.stack カーネル シンボルが永続的にリストされている場合、llkd はそのプロセスを(フォワード スケジューリングが進行している場合でも)強制終了します。次のスキャン時にまだそのプロセスが存在している場合、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 全体にわたって 1 回のサンプリングで 1 度のみです(サンプリングは ro.llk.check_ms ごとに行われます)。ABA を回避する機能がないため、これが誤ったトリガーを防ぐ唯一の方法です。シンボル関数は、競合する可能性のあるロックを呼び出す関数の下にある必要があります。ロックがシンボル関数の下または中で起きた場合、シンボルはロックを引き起こしたプロセスだけでなく、影響を受けるすべてのプロセスに現れます。

カバレッジ

llkd は、デフォルトの実装では init[kthreadd][kthreadd] から生成されたスレッドを監視しません。llkd[kthreadd] から生成されたスレッドをカバーするには、次の条件を満たす必要があります。

  • ドライバが永続的 D 状態にとどまらないこと。

または

  • ドライバが、外部から強制終了されたスレッドを回復するメカニズムを備えていること(たとえば、wait_event() ではなく wait_event_interruptible() を使用しているなど)。

上記いずれかの条件を満たしていれば、llkd 拒否リストを調整して、カーネル コンポーネントをカバーできます。スタック シンボルのチェックでは、ptrace オペレーションをブロックするサービスでの sepolicy 違反を防ぐため、別のプロセス拒否リストも考慮されます。

Android プロパティ

llkd は、以下に挙げる Android プロパティの影響を受けます。

  • prop_ms という名前のプロパティの単位はミリ秒です。
  • リストにカンマ(,)区切り文字を使用するプロパティでは、先頭に区切り文字を置くことでデフォルトのエントリを保持し、オプションで接頭辞のプラス(+)、マイナス(-)を使ってエントリを追加、削除できます。このリストにおいて、文字列 "false" は空のリストと同義です。また、空白または欠落しているエントリには、指定したデフォルト値が使用されます。

ro.config.low_ram

デバイスのメモリが限られています。

ro.debuggable

デバイスが userdebug ビルドまたは eng ビルド用に設定されています。

ro.llk.sysrq_t

プロパティが "eng" の場合、デフォルトは ro.config.low_ram または ro.debuggable にはなりません。 true の場合、すべてのスレッドをダンプ(sysrq t)します。

ro.llk.enable

ライブロック デーモンを有効化できるようにします。デフォルトは false です。

llk.enable

eng ビルドかどうかの検査に使います。デフォルトは ro.llk.enable です。

ro.khungtask.enable

[khungtask] デーモンを有効化できるようにします。デフォルトは false です。

khungtask.enable

eng ビルドかどうかの検査に使います。デフォルトは ro.khungtask.enable です。

ro.llk.mlockall

mlockall() の呼び出しを有効にします。デフォルトは false です。

ro.khungtask.timeout

[khungtask] の制限時間の上限。デフォルトは 12 分です。

ro.llk.timeout_ms

D または Z の制限時間の上限。デフォルトは 10 分です。この値を 2 倍にして、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 のスレッドのサンプリング周期。デフォルトは 2 分です。

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 を行いません。そのため、スタック シンボルが現れるのは非常にまれであり、存在するとしても短時間です(スタックのすべてのサンプルにおいて、シンボルが永続的に存在することはほとんどありません)。スタックの拡張範囲で " symbol+0x" または " symbol.cfi+0x" との一致があるかどうかをチェックします。userdebug ビルドまたは eng ビルドでのみ使用できます。user ビルドでは、セキュリティ上の問題により権限が制限されるため、このチェックを行えません。

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](ゾンビ setsid の場合のみ kernel[kthreadd]adbd)です。アンパサンド(&)区切り文字は、ターゲットの子プロセスとの組み合わせでのみ親を無視する場合に使用します。アンパサンドを使用するのは、プロセス名の一部に使用されることがないためです。シェルの setprop では、アンパサンドはエスケープするか引用符で囲む必要がありますが、通常これが指定されている init rc ファイルでは問題は生じません。親プロセスまたはターゲット プロセスは、commcmdlinepid 参照にできます。

ro.llk.blacklist.uid

llkd は、ここに指定した uid に一致するプロセスを監視しません。 uid の番号または名前のカンマ区切りリストです。デフォルトは空または 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 を呼び出す必要があります。この関数は、このハンドラが次に呼び出されるまでの予想時間を返します。