
Android カーネルの脆弱性 CVE-2019-2215 のトリガーと分析
2017年11月、syzkallerシステムによってLinuxカーネルにおけるuse-after-freeバグが検出されました。2018年2月には一部のLinuxカーネルとAndroidバージョンで修正が施されました。
この修正はAndroid月次セキュリティアップデートに含まれることはなかったため、PixelやPixel2など多くの新発売デバイスではパッチが適用されていませんでした。
2019年9月、Project Zeroによりこのバグのセキュリティ上の影響がAndroidに通知されました。その後、Androidはこの脆弱性にCVE-2019-2215を割り当て、正式かつ周知のものとしました。
CVE-2019-2215はbinder.cにおけるuse-after-freeであり、Androidアプリケーションから特権の昇格(rootアクセス取得)を可能にします。この脆弱性を悪用するためにユーザーの操作は必要ありません。悪意のあるローカルアプリケーションのインストールのみが必要です。
ここでは、このAndroidカーネル脆弱性についてより詳細に紹介し、この脆弱性を利用してAndroidデバイス全体のrootアクセス(特権昇格)を取得します。
以下のProof of Concept(PoC)を使用します。
https://github.com/cloudfuzz/android-kernel-exploitation
まず、Androidエミュレータ上でこの脆弱性を発火させ、カーネルクラッシュを発生させる方法を示します。その後、その危険性を理解するために、PoCを使用してシミュレートされたAndroidデバイスでrootアクセスを取得する手順を進めます。続いて、カーネルコードを解析し、原因を調べます(静的解析と動的解析)。
解析後、どのようにrootアクセスを取得したかを確認します。最後に、パッチによってこの脆弱性がどのように緩和されるかを見ます。
この脆弱性を発火させてカーネルクラッシュを発生させるには、以下の手順を実行します。
次のビデオをご覧ください。
[video]
このセクション(および次のセクション)では、静的解析と動的解析を使用してクラッシュが発生する理由を理解します。
ここでは、問題を理解するためにカーネルコードの静的解析を行います。crash_report.txtにはKASanからのレポートが含まれており、これがuse-after-freeバグであると示しています。つまり、ヒープ上にオブジェクトが割り当てられ(それへの参照がある)、そのオブジェクトをヒープから解放した後に、誤って参照を使ってそのオブジェクトを呼び出したことを意味します。このレポートには、これら3つの段階のスタックトレースが出力されています。
上記で、PoC内の'trigger.cpp'を使用したことを覚えているでしょうか。
以下がtrigger.cppのメインコードです。``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }
Let's see what 'trigger.cpp' is doing.
Android(他のUnix系オペレーティングシステムと同様)には、いくつかのプロセスが存在します。実行するプログラムはそれぞれ1つ(または複数)のプロセスを作成し、これらのプロセスはOSによって管理されます。OSはプロセス間を切り替え(マルチタスキング)、プロセスを終了させるなどします。セキュリティ上の理由から、プロセスはデフォルトで互いに隔離されています。
場合によっては、あるプロセスが別のプロセスとデータを交換する必要があります。これをプロセス間通信(IPC)と呼びます。Linuxでは、プロセスが通信するためのいくつかの方法があります。Androidは **'Binder'** と呼ばれる特定のIPCメカニズムを導入しました。Binderはプロセス間通信を容易にするカーネルドライバです。
Androidでは、IPCはカーネルメソッド(ほとんどは drivers/binder.c にあります)を直接呼び出すか、高レベルの実装(たとえばJava)を使用して行うことができます。

**binder** を使用するには、カーネルのbinderモジュールを開く必要があります。これは trigger.cpp の3行目で行われます。そして、ファイルディスクリプタポインタを取得します。この fd を使用することで、IPCのカーネルイニシエータと受信者を識別できます。
ドライバとのすべてのやり取りは、少数の **'ioctl'** コマンド(BINDER_THREAD_EXIT、BINDER_WRITE_READ、...)を通じて行われます。
binderの詳細: [link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)
Linuxには '**event polling**' と呼ばれる概念があります。複数のファイルディスクリプタ(ドライバを開いたり、IOを扱ったりするときに取得するもの)を監視したい場合に、'**epoll**' API を使用します。
**epoll** は、2つの重要なフィールドを持つカーネル構造体です。
* interest list = 監視したいファイルディスクリプタのリスト
* ready list = I/Oの準備ができているファイルディスクリプタのリスト
イベントポーリングを使用するには、まず epoll を作成し(4行目)、次にカーネルの **epoll_ctl** メソッドを呼び出して、ファイルディスクリプタ(**fd**)に関連付けられたイベント(&event)を、作成した epoll(**epfd**)に追加または削除します(EPOLL_CTL_ADD)。
=> `epoll_event event` は、関連付けられたファイル(fd)が読み取り操作に利用可能になったときにトリガーされるイベントです。
これで **trigger.cpp** が何をしているか理解できました(深く掘り下げる必要はありません!)。**binder** モジュールを開き、準備ができたときに待ち受ける **epoll** を作成します。そして6行目で、3行目で開始した binder から exit します。
#### Allocate:
open() を呼び出すことで、実際には **open_binder()**(binder.c 内の open() の実装)を呼び出します。open_binder() では、新しい **'binder_proc'** 構造体が作成され、次のように設定されます:
` fd->pricate_data = binder_proc`
**epoll_create()** を呼び出すことで、新しい epoll 構造体が作成され、キュー構造に追加されます。

**epoll_ctl(epdf, ADD, fd, event)** を呼び出すことで、新しい **ep_item** が作成され、**fd**(待ち受けるファイルディスクリプタ)をこの **ep_item** に関連付け、event_poll の**赤黒木**(ep_item を保存する ep 内のデータ構造)に挿入します。また、**ep_item_poll()** を呼び出します。このメソッドはコールバック関数を ep_item に関連付ける処理を行います。
新しい **binder_thread** 構造体を作成し(**ここで割り当てが行われます**)、それを(上で作成した)**binder_proc** にリンクします。次に **epoll_entry** 構造体が作成され、2つのリスト **epoll_entry->wait** と **epoll_entry->whead** を持ちます。これらのリストは両方とも、先に作成した **binder_thread** へのポインタを持ちます。
そして **epoll_entry** は ep_item にリンクされます(**ep_item->pwqlist** はこの epoll_entry を含むリストです)。

#### Free:
**ioctl(fd, ...)** を呼び出すことで、fd->private_data を通じて **binder_proc** にアクセスし、その後 **binder_thread** 構造体がメモリから**解放**されます。
#### Use:
現在のプロセスが終了すると、**epoll_ctl(epfd, DEL, fd, event)** が呼び出されます。
これにより **ep_remove(event_poll, ep_item)** が呼び出されます。このメソッドは **ep_item->pwqlist** から **epoll_entry** を取得し、次に **ep_item** の待機リスト(**ep_tem->wait**)を取得します。これはリンクリストであり、この待機リストの項目の1つを削除しようとします。
次のコードを使用します(疑似コードを使用):```
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;
ここで wait->entry は、メモリから削除された binder_thread へのポインタです。つまり、解放後使用(Use-After-Free)であり、バグを引き起こします!!
red_black_tree を持つ event_poll を作成しました。各ノードは ep_item であり、そのフィールドは epoll_entry のリストです。各 epoll_entry は binder_thread 構造体への2つのポインタ(wait, whead)を持ちます。
ioctl() を呼び出すことで、binder_thread をメモリから解放しました。そして、終了時に、この構造体はまだ利用可能なポインタを介してアクセスされます。
動的解析とは、プログラムを実行することでリアルタイムにテストおよび評価し、実行中にエラーを見つけることです。
手順: