对cve-2021-21220的一些分析
前言
时隔好久,再次分析该漏洞,也就是issue-1196683,这次总算是解开了心中的些许疑惑
漏洞分析
对应issue
https://bugs.chromium.org/p/chromium/issues/detail?id=1196683
该issue也添加了相对应的regress.js:https://chromium.googlesource.com/v8/v8/+/3066b7b2fcb3aa66541a4818e1165e34acc52639/test/mjsunit/compiler/regress-1196683.js
poc
根据regress-1196683.js修改:
1 | const arr = new Uint32Array([2**31]); |
运行后得出结果:
1 | -2147483647 |
在优化后出现了不一样的输出结果
patch
查看官方的补丁:
1 | diff --git a/src/compiler/backend/x64/instruction-selector-x64.cc b/src/compiler/backend/x64/instruction-selector-x64.cc |
漏洞是发生在SelectInstructions阶段的VisitChangeInt32ToInt64函数:
1 | void InstructionSelector::VisitChangeInt32ToInt64(Node* node) { |
当ChangeInt32ToInt64
节点的输入的opcode是IrOpcode::kLoad的时候,会根据输入的representation来进行指令的选择,如果是有符号的,就选择进行有符号拓展,如果是无符号的就选择进行无符号拓展。poc中的load是无符号类型(见下图),所以到了这里使用的是无符号拓展,导致计算结果变成了2147483648+1也就是2147483649
在SimplifiedLowering阶段,SpeculativeNumberBitwiseXor被replace为Word32节点:
对应代码:
1 | // src/compiler/simplified-lowering.cc |
而word32节点在EarlyOptimizationPhase被消除:
相应代码:
1 | template <typename WordNAdapter> |
我们走的是第一种情款,x^0 => x ,直接用leftnode来替换自己,这样也就导致了LoadTypedElement节点和ChangeInt32ToInt64连在了一起,触发了漏洞函数
漏洞利用
从poc中可以看出我们可以得到一个计算错误的数值,从网上爆出的exp来看是通过该操作得到了一个长度为-1的越界数组:
1 | const _arr = new Uint32Array([2**31]); |
会输出-1,而越界数组a的element属性其实只分配了1个元素大小的空间
but,为什么会这样?
在多方搜索之后,在dmxcsnsbh大佬的slide中得到了解答,slide链接:https://github.com/singularseclab/Slides/blob/main/2021/chrome_exploitation-zer0con2021.pdf
slide里面提到了很多类似的issue,都是用的同样的利用手法:
(图片来自上面提到的slide)
下面跟着slide里对该操作的turbofan IR的解释进行一步步的分析
TFBytecodeGraphBuilder
1 | let arr = JSConstruct("Array"); |
这个从turbolizer和bytecode中可以得出:
1 | [generated bytecode for function: foo (0x1273082d2185 <SharedFunctionInfo foo>)] |
turbolizer:
TFInlining
在Inlining阶段,会对jscall进行reduce:
1 | struct InliningPhase { |
该阶段会在[1]处添加call_reducer,接着在[2]处开始对graph进行reduce,reduce期间就会调用到Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node)
:
1 | // src/compiler/js-call-reducer.cc |
这里的receiver应该就是我们的数组本身:
化简下(也就是slide中的TFInlining):
1 | let arr = JSCreateArray(len); |
TFTypedLowering
在该阶段会对JSCreateArray进行lowering,调用的是Reduction JSCreateLowering::ReduceJSCreateArray(Node* node)
函数:
1 | // src/compiler/js-create-lowering.cc |
length节点是Phi节点,接着走到ReduceNewArray函数:
1 | // src/compiler/js-create-lowering.cc |
[2]处的JSArray::kInitialMaxFastElementArray值为16380,这里加入了一个CheckBounds节点,它的两个输入分别是JSArray::kInitialMaxFastElementArray节点和Phi Range(-1,0):
CheckBounds的Range(0,0)应该是出现在TypedLowering阶段里的RetypePhase,该阶段会调用到Type OperationTyper::CheckBounds(Type index, Type length)
:
1 | // src/compiler/operation-typer.cc |
这里index是我们的Phi的range,就是(-1,0),length的range是(16380,16380),但是在[3]会呗更新为(0,16379),最后在[4]做交集得出(0,0)
所以在TFTypedLowering阶段,JSCreateArray(len)被替换为:
1 | let len = Phi Range(-1,0); |
TFLoadElimination
在上一阶段,JSCreateArray被替换成一系列的操作,回到TFInlining,在JSCreateArray的后续操作就是let length = LoadField(arr,kLengthOffset);
也就是等于:
1 | let len = Phi Range(-1,0); |
在该阶段,[5]处经过Reduction LoadElimination::ReduceLoadField(Node* node, FieldAccess const& access)
函数的处理,会被替换成:
1 | StoreField(arr,kLengthOffset,checkedLen); |
在该函数处下个断点:
途径会有很多LoadField被处理,我们只关心在JSCreateArray后获取length的那个LoadField节点,这个可以对照turbolizer图来找到
1 | pwndbg> telescope object |
object就是与136号节点LoadField的输入FinishRegion节点,见下图的节点编号:
在该函数中LoadField会被替换为CheckBounds(与其说是替换,应该说是被消除了):
所以现在整体的逻辑为:
1 | let len = Phi Range(-1,0); |
在后续的常量折叠(调用Reduction ConstantFoldingReducer::Reduce(Node* node)
)中,把:
1 | let newlen = length - 1; |
替换成了:
1 | StoreField(arr,kLengthOffset,-1); |
turbolizer:
在LoadELimination阶段结束后:
整体逻辑为:
1 | let len = Phi Range(-1,0); |
其中对loop节点和那几个branch的优化可以见PortalLab实验室文章 里的分析,讲的也很详细
在LoadElimination阶段之后,逻辑就变成了length不等于0,且小于100的情况下,就会向Array的length中写入-1,orz
在后续的优化阶段CheckBounds节点会被替换成具体的比较节点,如Uint32LessThen,如果不在范围内就会进行Deoptimize
TFTyper
回到一开始的let len = Phi Range(-1,0);
这个是因为在Typer阶段,turbofan会对一些节点进行预测:
1 | const _arr = new Uint32Array([2**31]); |
而(_arr[0] ^ 0) + 1
被推测为Range(-2147483647,2147483648)
,应该是js里32位整数xor操作的范围是Range(-2147483648,2147483647):
1 | V8 version 8.6.405 |
对应的turbolizer:
参考链接
https://github.com/singularseclab/Slides/blob/main/2021/chrome_exploitation-zer0con2021.pdf
https://mp.weixin.qq.com/s/7XTclEGftJufdyLcfiAYSA
https://bugs.chromium.org/p/chromium/issues/detail?id=1196683