Android では OEM に対し、SELinux の実装を徹底してテストすることを強くおすすめしています。SELinux を実装するメーカーは、まずデバイスのテストプールに新しいポリシーを適用する必要があります。
新しいポリシーを適用したら、getenforce
コマンドを実行して、SELinux が正しいモードで実行されていることを確認します。
このコマンドにより、Enforcing モードか Permissive モードのどちらかのグローバル SELinux モードが出力されます。各ドメインの SELinux モードを確認するには、対応するファイルを調べるか、/platform/system/sepolicy/tools/
の適切なフラグ(-p
)を使用して最新バージョンの sepolicy-analyze
を実行する必要があります。
拒否の読み取り
エラーを確認します。エラーはイベントログとして dmesg
と logcat
にルーティングされ、デバイス上でローカルで確認できます。メーカーは、dmesg
への SELinux の出力をデバイス上で調べ、公開リリース前に permissive モードで設定を調整して、最終的に enforcing モードに切り替える必要があります。SELinux ログメッセージには avc:
が含まれているため、grep
で簡単に見つけることができます。cat /proc/kmsg
を実行して、残っている拒否ログをキャプチャしたり、cat /sys/fs/pstore/console-ramoops
を実行して前回のブートから拒否ログをキャプチャしたりできます。
ログの消去を回避するため、SELinux エラー メッセージは起動完了後にレート制限されます。関連するメッセージをすべて表示するには、adb shell auditctl -r 0
を実行してレート制限を無効にします。
メーカーはこの出力を確認することで、システム ユーザーまたはシステム コンポーネントによる SELinux ポリシー違反を簡単に特定できます。特定した不正な動作を修正するには、ソフトウェアと SELinux ポリシーのいずれかまたは両方に変更を加えます。
具体的には、このログメッセージには、どのプロセスが enforcing モードで違反となるか、そしてその違反の理由が示されます。次に例を示します。
avc: denied { connectto } for pid=2671 comm="ping" path="/dev/socket/dnsproxyd" scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket
この出力は次のように解釈できます。
- 上記の
{ connectto }
は、実行されたアクションを表します。末尾のtclass
(unix_stream_socket
)と合わせて確認することで、何に対して何が実行されたかが大まかにわかります。このケースでは、何かが unix ストリーム ソケットへの接続を試みていました。 -
scontext (u:r:shell:s0)
は、どのコンテキストでこのアクションが開始されたかを示します。このケースでは、シェルとして実行された何かがアクションを実行しています。 -
tcontext (u:r:netd:s0)
は、アクションのターゲットのコンテキストを示します。このケースでは、netd
がオーナーである unix_stream_socket がコンテキストになっています。 - 上の行の
comm="ping"
を確認すると、拒否が生成された時点で何が実行されていたかについて、さらにヒントが得られます。このケースでは、有益なヒントが示されています。
別の例を示します。
adb shell su root dmesg | grep 'avc: '
出力:
<5> type=1400 audit: avc: denied { read write } for pid=177 comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0 tcontext=u:object_r:kmem_device:s0 tclass=chr_file
この拒否で重要となる要素を以下に示します。
- アクション - 試行されたアクションがかっこ内に示されています(
read write
またはsetenforce
)。 - 実行元 -
scontext
(ソース コンテキスト)のエントリは、実行元(このケースではrmt_storage
デーモン)を表します。 - オブジェクト -
tcontext
(ターゲット コンテキスト)のエントリは、アクションの対象となったオブジェクトを表します。このケースでは kmem です。 - 結果 -
tclass
(ターゲット クラス)のエントリは、アクションの対象となったオブジェクトのタイプを示します。このケースではchr_file
(キャラクター デバイス)です。
ユーザー スタックとカーネル スタックをダンプする
イベントログに含まれる情報だけでは拒否の原因を特定できない場合もあります。多くの場合、カーネルやユーザー空間を含めた呼び出しチェーンを収集すると、拒否の原因を詳しく把握できます。
最近のカーネルでは、avc:selinux_audited
というトレースポイントが定義されています。Android simpleperf
を使用して、このトレースポイントを有効にし、呼び出しチェーンをキャプチャします。
サポートされている構成
- Linux カーネル 5.10 以降、具体的には Android 共通カーネル ブランチの mainline と android12-5.10 がサポートされています。android12-5.4 ブランチもサポートされています。
simpleperf
を使用すると、デバイスでトレースポイントが定義されているかどうかを判断できます(adb root && adb shell simpleperf list | grep avc:selinux_audited
)。他のカーネル バージョンの場合は、dd81662 と 30969bc の commit を選択できます。 - デバッグしているイベントを再現できる必要があります。起動時のイベントは simpleperf ではサポートされていませんが、サービスを再起動することでイベントをトリガーできる場合があります。
呼び出しチェーンをキャプチャする
まず、simpleperf record
を使用してイベントを記録します。
adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"
その後、拒否の原因となったイベントをトリガーし、記録を停止します。この例では、Ctrl-c
を使用してサンプルをキャプチャしています。
^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.
最後に、simpleperf report
を使用すると、キャプチャしたスタック トレースを検査できます。次に例を示します。
adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph" [...] Children Self Command Pid Tid Shared Object Symbol 100.00% 0.00% dmesg 3318 3318 /apex/com.android.runtime/lib64/bionic/libc.so __libc_init | -- __libc_init | -- main toybox_main toy_exec_which dmesg_main klogctl entry_SYSCALL_64_after_hwframe do_syscall_64 __x64_sys_syslog do_syslog selinux_syslog slow_avc_audit common_lsm_audit avc_audit_post_callback avc_audit_post_callback
この呼び出しチェーンは、カーネルとユーザー空間の呼び出しチェーンを統合したものです。ユーザー空間から拒否が発生するカーネルに至るまで、トレースを開始することでコードフローを理解しやすくなります。simpleperf
の詳細については、Simpleperf の実行可能コマンド リファレンスをご覧ください。
permissive への切り替え
SELinux の適用は、userdebug ビルドまたは eng ビルドで adb を使用して無効にできます。それには、まず adb root
を実行して ADB を root に切り替えます。次に、以下のコマンドを実行して SELinux の適用を無効にします。
adb shell setenforce 0
または、(デバイス起動時の初期に)カーネル コマンドラインで次のようにします。
androidboot.selinux=permissive
androidboot.selinux=enforcing
あるいは、Android 12 の bootconfig を使用して次のようにします。
androidboot.selinux=permissive
androidboot.selinux=enforcing
audit2allow の使用
audit2allow
ツールは、dmesg
の拒否を対応する SELinux ポリシー ステートメントに変換します。そのため、SELinux の開発が大幅に効率化されます。
これを使用するには、次のコマンドを実行します。
adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy
その場合も、権限を追加する際にはその権限が過度なものではないか注意する必要があります。たとえば、先ほど示した rmt_storage
拒否を audit2allow
にフィードすると、次のような SELinux ポリシー ステートメントが結果として示されます。
#============= shell ============== allow shell kernel:security setenforce; #============= rmt ============== allow rmt kmem_device:chr_file { read write };
これでは、カーネルメモリに書き込む権限が rmt
に付与され、大きなセキュリティ ホールが生まれてしまいます。多くの場合、audit2allow
ステートメントは出発点にすぎません。これらのステートメントを導入した場合は、必要に応じてソースドメインとターゲットのラベルを変更し、適切なマクロを利用して、適切なポリシーを完成させます。調査した拒否によっては、ポリシーの変更ではなく、不適切なアプリの変更が必要になることもあります。