macOSのダブルフリー脆弱性「CVE-2019-8635」を解説、権限昇格と任意のコード実行が可能に

トレンドマイクロは、macOSで同一領域のメモリが二重に解放される「ダブルフリー」の脆弱性を発見しました。この脆弱性には共通脆弱性識別子「CVE-2019-8635」が割り当てられています。CVE-2019-8635は、AMD製GPU「Radeon」のコンポーネントにおけるメモリ破壊に起因します。攻撃者がこの脆弱性の利用に成功すると、権限を昇格し、ルート権限で不正なコードを実行可能です。弊社の情報公開を受けAppleは修正プログラムを公開しました。

CVE-2019-8635には、「discard_StretchTex2Tex」関数および「AMDRadeonX400_AMDSIGLContext」クラスにおけるサイドバンドトークンの処理のそれぞれに存在する2つの欠陥が含まれます。「AMDRadeonX400_AMDSIGLContext」クラスは「IOAccelContext2」クラスを拡張した「IOAccelGLContext2」クラスから派生したクラスで、グラフィックの描画に使用されます。

■ダブルフリーの原因となる関数

問題の脆弱性に起因するメモリの二重解放は、「AMDRadeonX4000_AMDSIGLContext」クラスの「discard_StretchTex2Tex」関数および「AMDSIGLContext::process_StretchTex2Tex」関数で発生します。これらの関数には、「AMDRadeonX4000_AMDSIGLContext」のユーザクライアントの「selector 2」関数である「IOAccelContext2::submit_data_buffers」を使用することでアクセス可能です。一方、「AMDRadeonX4000_AMDGraphicsAccelerator」のクライアントは接続タイプ「1」で開くことが可能です。

「AMDRadeonX4000_AMDSIGLContext」クラスの「discard_StretchTex2Tex」関数のダブルフリーを利用した権限昇格脆弱性

この脆弱性を利用することにより、ローカルの攻撃者はユーザスペースでコードを実行することを可能にします。前提条件として、攻撃者は低い権限でのコード実行が可能である必要があります。

この脆弱性は、ユーザが提供したデータに対して適切なバリデーションがなされていないことに起因します。その結果、割り当てられたデータ構造の終端を越えた領域の読み取りが発生します。攻撃者は、その他の脆弱性とともにこれを利用することで、権限をカーネルレベルに昇格することが可能です。

「AMDRadeonX4000_AMDSIGLContext」クラスにおけるダブルフリーを利用した権限昇格脆弱性

ダブルフリーの脆弱性は、同じ「AMDRadeonX4000_AMDSIGLContext」クラスにおけるサイドバンドトークンの処理にも存在します。これにより、ローカルの攻撃者は、影響を受けるバージョンのmacOSで任意のコードを実行することが可能になります。上述の欠陥と同様に、攻撃者は低い権限でのコード実行が可能である必要があります。

前述したダブルフリーは「AMDRadeonX4000_AMDSIGLContext::discard_StretchTex2Tex」関数で発生しますが、こちらは「AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex」関数で発生します。この脆弱性は、オブジェクトを操作する前にその存在を確認するバリデーションがなされていないことに起因します。この脆弱性を利用することで、攻撃者は権限をカーネルレベルに昇格することが可能です。

脆弱性攻撃の潜在的な可能性という点においてこれら2つの脆弱性は類似しており、その違いは利用する関数にあります。

■脆弱性の解析

図1は「AMDRadeonX4000_AMDSIGLContext::discard_StretchTex2Tex」関数の疑似コードです。「cmdinfo+32」が0x8c00(10進数で35840)に等しい場合、この関数は「IOAccelResource」である「v10」および「v11」を、それぞれ「*(shareMem_start_address_187_offset16+8)」と「*(shareMem_start_address_187_offset16+12)」の値をインデックスとして「IOAccelShared2」から取得します。次にこの関数は、2つのアクセレレータリソースを「IOAccelResource2::clientRelease()」を使用して解放します。しかし、これら2つのインデックスは、「IOAccelContext2」のユーザクライアントを使用し、ユーザスペースからマップメモリを通して直接操作することが可能です。ここで、ユーザスペースが「lookupResource」関数のインデックスと同じ領域をマッピングしている場合、「clientRelease」関数は同じリソースクライアントを2度解放することになります。これによりダブルフリーが発生します。

「AMDRadeonX4000_AMDSIGLContext::discard_StretchTex2Tex」関数の疑似コード

図1:「AMDRadeonX4000_AMDSIGLContext::discard_StretchTex2Tex」関数の疑似コード

図2は「AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex」関数の疑似コードです。「v15」が0x8c00(10進数で35840)に等しいとき、この関数は「accelResource_offset8」と「accelResource_offset12」を、それぞれ共有メモリのオフセット24および28の値をインデックスとして「IOAccelShared2」から取得します。最終的に、この関数は「IOAccelShared2」から「accelResource_offset12」を解放します。そして、「accelResource_offset8->member2」が10と等しくない場合、「IOAccelShared2」から「accelResource_offset8」を解放します。しかし、共有メモリのオフセット24および28を同じ値に設定すると、同一の「accelResource」を2回解放することになります。

「process_StretchTex2Tex()」関数がストレッチ操作を完了する際、「IOAccelResource2::clientRelease()」関数が2つのリソースクライアントを解放します。しかし、これら2つの「accelResource2」は、それぞれのインデックスと共に「IOAccelShared2::lookupResource」関数を使用して「AMDRadeonX4000_AMDSIGLContext」クラス内の「accelShare2」の共有メモリから取得されます。これらのインデックスは「IOAccelContext2」のユーザクライアントがメモリをマッピングすることでユーザスペースから操作可能です。ユーザスペースが「lookupResource」関数のためのインデックスと同一のインデックスをマッピングした場合、「clientRelease」は同じリソースクライアントを2回解放し、ダブルフリーが発生します。

「AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex」関数の疑似コード

図2:「AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex」関数の疑似コード

図1および図2に示した疑似コードにおいて、共有メモリのアドレスは「commandStreamInfo」のオフセット24の位置を指しています。しかし、図3のように、この「commandStreamInfo」のバッファは実際「IOAccelContext2::processSidebandBuffer」関数でセットされており、変数「v5」は共有メモリのオフセット16の位置を、したがって「this->member196」は「commandStreamInfo」のオフセット24の位置を指しています。

「IOAccelContext2::processSidebandBuffer」の疑似コード

図3:「IOAccelContext2::processSidebandBuffer」の疑似コード

図4は「IOAccelContext2::clientMemoryForType」関数の疑似コードです。この関数はユーザスペースのバッファをカーネルスペースにマッピングする関数「IOConnectMapMemory64」によって呼び出されます。この関数を使用する際は、パラメータとして「connect」オブジェクト、メモリタイプ、およびその他の引数を与える必要があります。図4では、「connect」オブジェクトは「IOAccelContext2」のインスタンス、メモリタイプは「0」です。メモリタイプに「0」を指定すると、「clientMemoryForType」関数はバッファメモリのディスクリプタを作成し、ユーザスペースに開始アドレスを返します。続いて、この関数はバッファメモリアドレスを変数「shareMem_start_vm_address_187」にセットします。この変数名は便宜的に命名したものです。これがまさに「IOAccelContext2::processSidebandBuffer」関数で使用される値です。

ここで、この共有バッファと2つのインデックスは操作可能なため、ダブルフリーを引き起こすことができます。

「IOAccelContext2::clientMemoryForType」関数の疑似コード

図4:「IOAccelContext2::clientMemoryForType」関数の疑似コード

以下は、アプリケーションのクラッシュログに含まれるバックトレース情報です。「discard_StretchTex2Tex」関数を使用した場合は、「AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex」という関数名とそのオフセット「process_StretchTex2Tex(IOAccelCommandStreamInfo&) + 2893」だけが異なります。

* thread #1, stop reason = signal SIGSTOP
frame #0: 0xffffff7f8d7adc37 IOAcceleratorFamily2`IOAccelResource2::clientRelease(IOAccelShared2*) + 13
frame #1: 0xffffff7f8d880dad AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex(IOAccelCommandStreamInfo&) + 2893
frame #2: 0xffffff7f8d79b5d5 IOAcceleratorFamily2`IOAccelContext2::processSidebandBuffer(IOAccelCommandDescriptor*, bool) + 273
frame #3: 0xffffff7f8d8885e4 AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::processSidebandBuffer(IOAccelCommandDescriptor*, bool) + 182
frame #4: 0xffffff7f8d79bae7 IOAcceleratorFamily2`IOAccelContext2::processDataBuffers(unsigned int) + 85
frame #5: 0xffffff7f8d7a2380 IOAcceleratorFamily2`IOAccelGLContext2::processDataBuffers(unsigned int) + 804
frame #6: 0xffffff7f8d798c30 IOAcceleratorFamily2`IOAccelContext2::submit_data_buffers(IOAccelContextSubmitDataBuffersIn*, IOAccelContextSubmitDataBuffersOut*, unsigned long long, unsigned long long*) + 1208
frame #7: 0xffffff800b027a3c kernel.development`::shim_io_connect_method_structureI_structureO(method=, object=, input=, inputCount=, output=, outputCount=0xffffff8742023968) at IOUserClient.cpp:0 [opt]
frame #8: 0xffffff800b025ca0 kernel.development`IOUserClient::externalMethod(this=, selector=, args=0xffffff87420239b8, dispatch=0x0000000000000000, target=0x0000000000000000, reference=) at IOUserClient.cpp:5459 [opt]
*frame #9: 0xffffff800b02ebff kernel.development`::is_io_connect_method(connection=0xffffff80b094e000, selector=2, scalar_input=, scalar_inputCnt=, inband_input=, inband_inputCnt=136, ool_input=0, ool_input_size=0, inband_output=””, inband_outputCnt=0xffffff80b0d81e0c, scalar_output=0xffffff8742023ce0, scalar_outputCnt=0xffffff8742023cdc, ool_output=0, ool_output_size=0xffffff80ab5c7574) at IOUserClient.cpp:3994 [opt]
frame #10: 0xffffff7f913044c2
frame #11: 0xffffff800a9bbd64 kernel.development`_Xio_connect_method(InHeadP=, OutHeadP=0xffffff8742023ce0) at device_server.c:8379 [opt]
frame #12: 0xffffff800a88d27d kernel.development`ipc_kobject_server(request=0xffffff80ab5c7400, option=) at ipc_kobject.c:359 [opt]
frame #13: 0xffffff800a859465 kernel.development`ipc_kmsg_send(kmsg=0xffffff80ab5c7400, option=3, send_timeout=0) at ipc_kmsg.c:1832 [opt]
frame #14: 0xffffff800a878a75 kernel.development`mach_msg_overwrite_trap(args=) at mach_msg.c:549 [opt]
frame #15: 0xffffff800a9f63a3 kernel.development`mach_call_munger64(state=0xffffff80af471bc0) at bsd_i386.c:573 [opt]
frame #16: 0xffffff800a823486 kernel.development`hndl_mach_scall64 + 22

カーネルによって検出されたシステムエラー「カーネルパニック」が発生すると、パニックテキストがログに追加されます。ユーザスペースのコードによって検出される類似したエラーとは異なり、カーネルパニックは、カーネルコードでハンドルされていないプロセッサの例外に起因します。これには例えば無効なメモリアドレスへのアクセスや呼び出しチェーンのバグのようなものが含まれます。問題のパニックログは以下の通りです。

panic(cpu 6 caller 0xffffff800aa1391c): Kernel trap at 0xffffff7f8d7adc37, type 14=page fault, registers
:CR0: 0x0000000080010033, CR2: 0x0000000000000018, CR3: 0x0000000fea85f063, CR4: 0x00000000001626e0
RAX: 0x0000000000000000, RBX: 0xffffff800b473e28, RCX: 0x00000000ffffffff, RDX: 0x0000000000000000
RSP: 0xffffff8742023610, RBP: 0xffffff8742023610, RSI: 0xffffff80b0f8e470, RDI: 0xffffff80afa29300
R8: 0x0000000000000229, R9: 0xffffff800b2c4d00, R10: 0xffffff800b2c2c70, R11: 0x0000000000000058
R12: 0xffffff87299cb9b4, R13: 0x0000000000000001, R14: 0xffffff80b094e608, R15: 0xffffff80b094e000
RFL: 0x0000000000010282, RIP: 0xffffff7f8d7adc37, CS: 0x0000000000000008, SS: 0x0000000000000010
Fault CR2: 0x0000000000000018, Error code: 0x0000000000000002, Fault CPU: 0x6, PL: 0, VF: 0

以下はカーネルパニック発生時のレジスタのデバッグ情報です。レジスタ「$r12」は共有メモリアドレスのオフセット16の位置を指しています。リソースのインデックスは両方とも0x42になっています。

(lldb) register read
General Purpose Registers:
rax = 0x0000000000000000
rbx = 0xffffff800b473e28 kernel.development`kdebug_enable
rcx = 0x00000000ffffffff
rdx = 0x0000000000000000
rdi = 0xffffff80afa29300
rsi = 0xffffff80b0f8e470
rbp = 0xffffff8742023610
rsp = 0xffffff8742023610
r8 = 0x0000000000000229
r9 = 0xffffff800b2c4d00 kernel.development`zone_array + 8336
r10 = 0xffffff800b2c2c70 kernel.development`zone_array
r11 = 0x0000000000000058
r12 = 0xffffff87299cb9b4
r13 = 0x0000000000000001
r14 = 0xffffff80b094e608
r15 = 0xffffff80b094e000
rip = 0xffffff7f8d7adc37
IOAcceleratorFamily2`IOAccelResource2::clientRelease(IOAccelShared2*) + 13
rflags = 0x0000000000010282
cs = 0x0000000000000008
fs = 0x00000000ffff0000
gs = 0x00000000afa20000
(lldb) x/20g $r12
0xffffff87299cb9b4: 0x00000364001a8c00 0x0000004200000042
0xffffff87299cb9c4: 0x0000104000000101 0x0055550000900002
0xffffff87299cb9d4: 0x0004000800040008 0x1048000000010001
0xffffff87299cb9e4: 0x0055560000900002 0x0002000800020008
0xffffff87299cb9f4: 0x0000000000010001 0x0000000000000000
0xffffff87299cba04: 0x0000000400000004 0x0000000000000000
0xffffff87299cba14: 0x0000000200000002 0x00000364001a8c00
0xffffff87299cba24: 0x0000004200000042 0x0000104800000101
0xffffff87299cba34: 0x0055560000900002 0x0002000800020008
0xffffff87299cba44: 0x1050000000010001 0x0055570000900002

■トレンドマイクロの対策

攻撃者は修正プログラムが適用されていないmacOSを攻撃し、権限を昇格してシステムを侵害するためにダブルフリー脆弱性を利用することが可能です。Appleはすでにこのメモリ破壊の問題に対処するために、メモリの取り扱いを改善した修正プログラムを公開しています。現在、macOS 10.14.4のためのセキュリティ更新プログラムが利用可能です。ユーザは速やかに更新プログラムを適用してください。

ユーザは、さまざまな欠陥を利用する攻撃を検出およびブロックするために、個人利用者向けの総合セキュリティ対策製品「ウイルスバスター for Mac」(「ウイルスバスター™ クラウド」に同梱)、法人利用者向けの総合セキュリティ対策製品「Trend Micro Security for Mac」、「User Protection」ソリューションを利用可能です。

参考記事:

翻訳: 澤山 高士(Core Technology Marketing, Trend Micro™ Research)