作者:棧長@螞蟻安全實驗室
原文鏈接:https://mp.weixin.qq.com/s/bYhQlblYxQRHqTb3zOrTew
1、背景
Google于4 月 13 日發布了最新的 Chrome 安全通告,公告鏈接(https://chromereleases.googleblog.com/),其中修復了pwn2own中攻破 Chrome 所使用的一個嚴重的安全漏洞(CVE-2021-21220),該漏洞影響x64架構的 Chrome,可導致Chrome 渲染進程遠程代碼執行,并使用了巧妙的手段繞過了 Chrome 內部的各種緩釋措施,目前Chrome最新版89.0.4389.128已修復。
在 Google 官方發布安全通告之前,4 月 12 號已有安全研究員公開了該漏洞的利用代碼,該漏洞影響范圍廣,危害大,光年實驗室第一時間對該漏洞進行了分析。
實際測試老版本的x64架構的Chrome或 Chromium 83、86、87、88 受此漏洞影響,存在漏洞的代碼在 5 年前就被引入,最遠可能影響至 Chrome 55 版本,下面是具體的漏洞分析詳情:
2、漏洞分析
漏洞存在于 Chrome 的 JS 引擎的 JIT 編譯器 Turbofan 當中,Instruction Selector階段在處理ChangeInt32ToInt64節點時,會先檢查 node 的 input 節點,如果 input 節點的操作碼是 Load,那么會根據該 input節點的 LoadRepresentation 和 MachineRepresentation進行一些特殊的處理,如果判斷該 input 節點的 MachineRepresentation 的類型是kWord32, 那么會根據 LoadRepresentation 是有符號的還是無符號的選擇對應的指令,如果是有符號的選擇X64Movsxlq,在x86指令集中是有符號擴展,如果是無符號的選擇X64Movl, 在x86指令集中是無符號擴展。
漏洞的根源是V8 對ChangeInt32ToInt64的假設是該節點的輸入必定被解釋為一個有符號的Int32的值,所以無論 LoadRepresentation如何,都應該使用X64Movsxlq指令。
void InstructionSelector::VisitChangeInt32ToInt64(Node* node) {
DCHECK_EQ(node->InputCount(), 1);
Node* input = node->InputAt(0);
if (input->opcode() == IrOpcode::kTruncateInt64ToInt32) {
node->ReplaceInput(0, input->InputAt(0));
}
X64OperandGenerator g(this);
Node* const value = node->InputAt(0);
if (value->opcode() == IrOpcode::kLoad && CanCover(node, value)) {
LoadRepresentation load_rep = LoadRepresentationOf(value->op());
MachineRepresentation rep = load_rep.representation();
InstructionCode opcode;
switch (rep) {
case MachineRepresentation::kBit:// Fall through.
case MachineRepresentation::kWord8:
opcode = load_rep.IsSigned() ? kX64Movsxbq : kX64Movzxbq;
break;
case MachineRepresentation::kWord16:
opcode = load_rep.IsSigned() ? kX64Movsxwq : kX64Movzxwq;
break;
case MachineRepresentation::kWord32:
opcode = load_rep.IsSigned() ? kX64Movsxlq : kX64Movl;
break;
default:
UNREACHABLE();
}
觸發漏洞的 poc 如下:
const arr = new Uint32Array([2 ** 31]);
function foo() {
return (arr[0] ^ 0) + 1;
}
console.log(foo()); //這一行輸出-2147483647
for (let i = 0; i < 100000; i++)
foo();
console.log("after optimization");
console.log(foo());//這一行輸出2147483649
同樣一個函數在優化前和優化后返回的結果不一致。在優化前,arr[0] ^ 0的結果用十六進制表示是0x80000000, 對于異或運算,JS 引擎會將結果看做一個有符號的 Int32 的值,所以異或的結果是-2147483648, 加上1 以后變成-2147483647。
但是為什么優化后的結果不一致了呢,我們可以觀察程序運行過程中生成的 Turbofan 的圖。arr[0] ^ 0這個表達式被優化成了一個Load節點, 對應下圖中的 #81 節點,而(arr[0] ^0) + 1這個加法運算被優化成了#58 ChangeInt32ToInt64節點和#50 Int64Add節點,如下圖中黃色高亮部分所示,在圖中可以看到,Load 節點的 MachineRepresentation 是Word32,而LoadRepresentation的類型是Uint32, 故而 Turbofan 選擇了movl指令,也就是無符號擴展指令,導致 Load 節點的值會被無符號擴展為 64 位,然后和 1 相加,最后結果自然是2147483649。

用調試器調試也可以驗證,在執行到mov ecx, DWORD PTR [rcx] 這一行時,rcx寄存器指向的值為0x80000000, 無符號擴展變成了2147483648, 最后加上 1 變成了2147483649。

3、止血修復方案
由于 Chrome 的沙箱機制,該漏洞仍需配合一個提權漏洞或沙箱漏洞才能在受害者的主機上執行任意代碼,尚不能形成完整的攻擊鏈路,但是鑒于市面上有些基于 Chromium 內核的應用關閉了沙箱功能,這些應用仍然有可能受到該漏洞的影響,產生實際的危害。
使用了 Chromium 內核的開發者可以參考此修復鏈接(https://chromium-review.googlesource.com/c/v8/v8/+/2820971)進行修復,在 Instruction Selector 階段處理 ChangeInt32ToInt64節點時,如果MachineRepresentation 是kWorkd32,無論LoadRepresentation 是有符號的還是無符號的,都選擇X64Movsxlq指令。或者將 Chromium 內核升級到最新版本。Chrome 用戶則需盡快將 Chrome 升級到最新版本。
螞蟻安全光年實驗室:隸屬于螞蟻安全實驗室。通過對基礎軟件及設備的安全研究,達到全球頂尖破解能力,致力于保障螞蟻集團及行業金融級基礎設施安全。因發現并報告行業系統漏洞,上百次獲得Google、Apple等國際廠商致謝。
掃碼關注螞蟻安全實驗室微信公眾號,干貨不斷!

本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1556/