エクスプロイト&脆弱性
macOSおよびiOSに影響するCVMServerの脆弱性
トレンドマイクロは、「Core Virtual Machine Server (CVMServer) 」に存在するmacOSの脆弱性を確認しました。「CVE-2021-30724」が割り当てられた当該脆弱性は、整数オーバーフローにより境界を超えるメモリアクセスを発生させ、そこから特権昇格が可能となるものです。
トレンドマイクロは、「Core Virtual Machine Server (CVMServer) 」に存在するmacOSの脆弱性を確認しました。「CVE-2021-30724」が割り当てられた当該脆弱性は、整数オーバーフローにより境界を超えるメモリアクセスを発生させ、そこから特権昇格が可能となるものです。この問題は古いバージョンのmacOS Big Sur 11.4、iOS 14.6、およびiPadOS 14.6が動作するデバイスに影響します。
この脆弱性はすでにApple社によって修正済みとなっています。本記事では、この脆弱性が確認された経緯や、脆弱性が誘発される条件について説明します。
■CVMServer
CVMServerはXPC Servicesの一つで、XPCリクエストを処理するためにrootで動作するシステムデーモンです。Apple が実装しているフレームワークであるXPCは、低レベルの異なるプロセス間の通信メカニズムを提供します。クライアントプロセスは、XPC関連のAPIを介してXPCリクエストメッセージを送信します。すると、サーバがそのメッセージを受け取り、処理します。最もよく使用されるクライアントの一つは、OpenCLフレームワークで書かれています。このクライアントが主に使用するロジックは、多様なXPCメッセージを送り出すための長いswitch-case文です。図1は、CVMServerのswitch-case文のロジックの例です。
図1:多様なXPCメッセージを送り出すCVMServerのswitch-caseのロジック
■脆弱性の存在箇所
脆弱性はXPCリクエストメッセージのハンドラー、つまりOpenCLソースコードを使って要素を構築するリクエスト(case msgType=18)の処理に存在します。
図2:CVMServerのロジックのcase 18に脆弱性が存在する
図2は、脆弱性が存在するロジックを示しています。図中のitem[3*count]は、xpc_shmem_mapから返されたマップされた長さです(134行目)。一方、beginOffsetは、XPCリクエストメッセージから制御することができます(135行目)。item[3*count]の値がbeginOffsetの値よりも小さい場合、ロジックによるとremainLenの値は整数のオーバーフローとなります。これにより、144行目のチェックが迂回されることになります。したがって、item[count]=accessDataLenに大きな整数を指定することで、脆弱性を誘発することができます(136行目から137行目)。
■脆弱性の誘発
図1は、case 4の中でcontext->attachedフラグが設定されていることも示しています。つまり、リクエスト(case msgType=18)を送信するためには、CVMS のサービスがアタッチされ、XPC リクエスト msgType=4 が送信されなければなりません。また、XPC リクエストをサービスに送信するためには、まず接続を確立する必要があります。トレンドマイクロはAPIコール _xpc_connection_create_mach_service を相互参照することでサービス名 "com.apple.cvmsServ" を取得し、接続を確立することができました。
int64_t cvms_connection_create(xpc_connection_t *conn) { int64_t error = 528; xpc_connection_t client = xpc_connection_create_mach_service("com.apple.cvmsServ", NULL, 2); xpc_connection_set_event_handler(client, ^(xpc_object_t event) {}); xpc_connection_resume(client); xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_int64 (req, "message", 1); xpc_object_t res = xpc_connection_send_message_with_reply_sync(client, req); printf("response: %s\n", xpc_copy_description(res)); if (xpc_get_type(res) == XPC_TYPE_DICTIONARY) { error = xpc_dictionary_get_int64(res, "error"); if (!error) { *conn = client; } } return error; } |
その後、デバッグによって取得した引数を使ってサービスをアタッチしました。
int64_t cvms_service_attach(xpc_connection_t conn) { int64_t error = 528; xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_int64 (req, "message", 4); xpc_dictionary_set_string(req, "framework_name", "OpenCL"); xpc_dictionary_set_string(req, "bitcode_name", ""); xpc_dictionary_set_string(req, "plugin_name", "/System/Library/Frameworks/OpenGL.framework/Libraries/libGLVMPlugin.dylib"); struct AttachArgs { int64_t a1; int64_t a2; } args = {0, 0x0000211000000009};//M1 Mac use 0x000021100000000a xpc_dictionary_set_data(req, "args", &args, sizeof(args)); xpc_object_t res = xpc_connection_send_message_with_reply_sync(conn, req); printf("response: %s\n", xpc_copy_description(res)); if (xpc_get_type(res) == XPC_TYPE_DICTIONARY) { error = xpc_dictionary_get_int64(res, "error"); if (!error) { int64_t pool_index = xpc_dictionary_get_int64(res, "pool_index"); printf("pool_index: %lld\n", pool_index); } } return error; } |
CVMSのサービスをアタッチし、XPCリクエスト msgType=4を送信した後、脆弱性が見つかったリクエスト(case msgType=18)を送信することができます。この脆弱性がどのように引き起こされるかをわかりやすくするために、XPCメッセージの構造を図2で示しています。
97行目から105行目を見ると、request["source"]がXPC配列にあり、ソースコードデータのリストを格納していることがわかります。108行目では、配列の各項目に32バイト(4ポインタサイズ)が割り当てられています。
図3:逆コンパイルされたsource_data_arrayのレイアウト
111行目から156行目までのdo-whileループは、各データソースの値で配列項目が埋められています。データソース値のタイプは、xpc_type_dataまたはxpc_type_shmemのいずれかです。ここでのロジックは、アドレス範囲[accessBeginPointer, accessBeginPointer+accessDataLength)は、範囲[mappedBaseAddress, mappedBaseAddress+mappedLength)のサブセットでなければならないというものです。したがってこのロジックでは、accessDataLengthの値がmappedLengthからbeginOffsetの値を引いた値よりも小さいかどうかをチェックします。脆弱性を引き起こすには、このチェックが迂回されなければなりません。今回の分析では、これらの値はすべてXPCリクエストメッセージから制御することができました。
138行目では、beginOffset値のチェックがあり、1ページまたは4K以下のサイズでなければなりません。しかし、xpc_shmem_mapから返されるmappedLengthは、常に4Kのサイズに設定されているため、脆弱性を誘発することは難しいと考えられます。ここで、xpc_shmem_map関数の実装を調べると、そのトリックが明らかになります。xpc_xshmemオブジェクトのフィールドオフセット0x20で、mappedLengthに任意の小さな値を割り当てるのです。今回の例では「1」を割り当てました。
この方法により、144行目のチェックを整数オーバーフローによって迂回し、指定した大きな数値による境界外メモリアクセスを発生させることが可能となりました。トリガーコードは、図5に示すようなコードとなります。概念実証(Proof of Concept、PoC)の全容はGitHubに掲載されています。
図4:MappedLengthは、フィールドオフセット0x20で小さな値を割り当てられる
図5:サーバのチェックを迂回するために、mapped lengthを1に修正したコード
■実装された修正プログラム
Apple社は既にCVE-2021-30724の修正プログラムを実装済みです。解決策は単純なもので、整数オーバーフローを回避するためのチェックを追加しています(図6の178行目)。なお、バイナリのCVMCompilerにも同様の問題が存在しており、CVE-2021-30724のために追加された解決策でこちらも修正されていることがわかりました。
図6:178行目で、CVE-2021-30724が修正されていることを示すコード
上記の解決策は類似のケースにおいては有効ですが、より包括的な別の方法として、脆弱性の根本原因であるxpc_shmem_mapのAPI実装にチェックを入れることが考えられます。すべてのシステムのネイティブMach-Oからのxpc_shmem_map APIコールをgrepすることで、類似の問題を検索することができます。実際にトレンドマイクロがCVMCompilerに同じ問題を発見したのも、この方法でした。
■トレンドマイクロの対策
このように、当該脆弱性の悪用は困難であるとはいえ不可能ではありません。CVE-2021-30724に修正プログラムが適用されていない場合、攻撃者はこの脆弱性を利用して権限を昇格させることが可能になります。ユーザは、最新の更新プログラムを適用しデバイスを最新の状態に保ってください。Apple社はこの問題に対処するため、macOS Big Sur 11.4、iOS 14.6およびiPadOS 14.6のセキュリティアップデートをリリースしています。また、「ウイルスバスター™ for Mac」や「ウイルスバスタークラウド™」などのソリューションは、このような脆弱性を悪用した攻撃を検知してブロックすることができます。
参考記事:
- 「CVE-2021-30724: CVMServer Vulnerability in macOS and iOS」
By Mickey Jin
翻訳: 室賀 美和(Core Technology Marketing, Trend Micro™ Research)