カーネル イベントでユーザー スタックとカーネル スタックをダンプする

カーネル内の特定のコードパスが実行されたときにネイティブ カーネルとユーザー空間のスタックをダンプすると、ログで見つかったエラーなどの特定の動作をデバッグする際に、コードフローをよりよく理解できます。そのようなケースの 1 つとして、ログにある SELinux の拒否メッセージに気づいたが、そのトリガーとなったパスを識別し、発生原因を把握したい場合があります。

この記事では、カーネルのインストルメンテーションと BPF コンパイラ コレクション(BCC)を使用して、Android システムでカーネル イベントが発生したときにユーザー スタックとカーネル スタックの両方をダンプする方法を説明します。BCC は、効率的なカーネル トレースを作成するためのツールキットです。

adeb をインストールする

adeb プロジェクトは、Android デバイスに chroot 環境をインストールします。adeb は、この記事の後半のステップで使用します。

adeb README の手順に沿って adeb をインストールします。

対象の Android デバイスに adeb をインストールするには、次のコマンドを実行します。

adeb prepare --full
adeb には 事前にパッケージ化された BCC が付属しているので、後のステップで必要になる BCC の trace ユーティリティも前のステップでインストールされます。

例: SELinux の拒否をトリガーしたパスを把握する

カーネルにトレースポイントを追加する

以下の diff は、SELinux の拒否がカーネルに記録されている場所にトレースポイントを追加します。この記事の後半では、BCC でこのトレースポイントを使用する必要があります。カーネルソースに diff を適用すると、SELinux の拒否のトレースポイントを追加できます。 diff が完全に適用されない場合は、diff を参考にして手動で適用してください。

diff --git a/include/trace/events/selinux.h b/include/trace/events/selinux.h
new file mode 100644
index 000000000000..dac185062634
--- /dev/null
+++ b/include/trace/events/selinux.h
@@ -0,0 +1,34 @@
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM selinux
+
+#if !defined(_TRACE_SELINUX_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SELINUX_H
+
+#include <linux/ktime.h>
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(selinux_denied,
+
+   TP_PROTO(int cls, int av),
+
+   TP_ARGS(cls, av),
+
+   TP_STRUCT__entry(
+       __field(    int,        cls )
+       __field(    int,        av  )
+   ),
+
+   TP_fast_assign(
+       __entry->cls = cls;
+       __entry->av = av;
+   ),
+
+   TP_printk("denied %d %d",
+       __entry->cls,
+       __entry->av)
+);
+
+#endif /* _TRACE_SELINUX_H */
+
+/* This part ust be outside protection */
+#include <trace/define_trace.h>
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index 84d9a2e2bbaf..ab04b7c2dd01 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -34,6 +34,9 @@
 #include "avc_ss.h"
 #include "classmap.h"

+#define CREATE_TRACE_POINTS
+#include <trace/events/selinux.h>
+
 #define AVC_CACHE_SLOTS            512
 #define AVC_DEF_CACHE_THRESHOLD        512
 #define AVC_CACHE_RECLAIM      16
@@ -713,6 +716,12 @@ static void avc_audit_pre_callback(struct audit_buffer *ab, void *a)
    struct common_audit_data *ad = a;
    audit_log_format(ab, "avc:  %s ",
             ad->selinux_audit_data->denied ? "denied" : "granted");
+
+   if (ad->selinux_audit_data->denied) {
+       trace_selinux_denied(ad->selinux_audit_data->tclass,
+                    ad->selinux_audit_data->audited);
+   }
+
    avc_dump_av(ab, ad->selinux_audit_data->tclass,
            ad->selinux_audit_data->audited);
    audit_log_format(ab, " for ");

ユーザー スタックとカーネル スタックをトレースする

SELinux の拒否のトレースポイントが見つかったときにスタックをトレースするには、次のコマンドを実行します。

adeb shell
trace -K -U 't:selinux:selinux_denied'

拒否がトリガーされると、次のように表示されます。

2286    2434    Binder:2286_4   selinux_denied
        avc_audit_pre_callback+0xd8 [kernel]
        avc_audit_pre_callback+0xd8 [kernel]
        common_lsm_audit+0x64 [kernel]
        slow_avc_audit+0x74 [kernel]
        avc_has_perm+0xb8 [kernel]
        selinux_binder_transfer_file+0x158 [kernel]
        security_binder_transfer_file+0x50 [kernel]
        binder_translate_fd+0xcc [kernel]
        binder_transaction+0x1b64 [kernel]
        binder_ioctl+0xadc [kernel]
        do_vfs_ioctl+0x5c8 [kernel]
        sys_ioctl+0x88 [kernel]
        __sys_trace_return+0x0 [kernel]
        __ioctl+0x8 [libc.so]
        android::IPCThreadState::talkWithDriver(bool)+0x104 [libbinder.so]
        android::IPCThreadState::waitForResponse(android::Parcel, int)+0x40
                                                            [libbinder.so]
        android::IPCThreadState::executeCommand(int)+0x460 [libbinder.so]
        android::IPCThreadState::getAndExecuteCommand()+0xa0 [libbinder.so]
        android::IPCThreadState::joinThreadPool(bool)+0x40 [libbinder.so]
        [unknown] [libbinder.so]
        android::Thread::_threadLoop(void)+0x12c [libutils.so]
        android::AndroidRuntime::javaThreadShell(void)+0x90 [libandroid_runtime.so]
        __pthread_start(void*)+0x28 [libc.so]
        __start_thread+0x48 [libc.so]

上記のコールチェーンは、統合されたカーネルとユーザーのネイティブ コールチェーンです。これを見ると、ユーザー空間から拒否が発生したカーネルまでのコードフローの全行程がよくわかります。上記のコールチェーンの例では、ファイル記述子を渡す処理に関与しているユーザー空間からバインダー トランザクションが開始されています。ファイル記述子が必要な SELinux ポリシー設定を持っていなかったため、SELinux がこれを拒否してバインダー トランザクションが失敗しました。

同じトレース手法を使用して、システムコールやカーネル関数エントリなどでもスタックをダンプできます。これは、ほとんどの場合、trace コマンドに渡す引数を変更すれば可能です。

その他の関連情報

trace の詳細については、BCC トレースツールのドキュメントをご覧ください。 Android デバイスで BCC を実行する方法の詳細については、adeb プロジェクトの BCC ハウツーガイドをご覧ください。