-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add out of bound error for wasm array access in interpreter #1823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
6e17fe7
to
92c27df
Compare
@Cellule Can you please take a look ? |
const moduleBytesView = new Uint8Array(blob); | ||
var a = Wasm.instantiateModule(moduleBytesView, {}).exports; | ||
print(a["goodload"](0)); | ||
//print(a["badload"](0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should do a try/catch here and print failed
if we don't trap
@@ -0,0 +1,7 @@ | |||
(module |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing copyright header, check other .wast files for example
@@ -0,0 +1,6 @@ | |||
const blob = WScript.LoadBinaryFile('array.wasm'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing copyright header
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add this test to rlexe.xml
you will need to add a baseline file for it too.
@@ -8208,6 +8208,12 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip) | |||
{ | |||
BYTE* buffer = arr->GetBuffer(); | |||
*(T2*)(buffer + index) = (T2)GetRegRaw<T2>(value); | |||
return; | |||
} | |||
if (this->m_functionBody->IsWasmFunction()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should do the check and trapping in OP_StArrWasm and OP_LdArrWasm, we don't want to add checks for normal asm.js
if (this->m_functionBody->IsWasmFunction()) | ||
{ | ||
//MGTODO : Change this to throw WebAssembly.RuntimeError once implemented | ||
Js::Throw::FatalInternalError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should throw a normal javascript runtime error for the time being so we can try/catch it in tests
@@ -1177,6 +1177,28 @@ LowererMDArch::LowerAsmJsLdElemHelper(IR::Instr * instr, bool isSimdLoad /*= fal | |||
Lowerer::InsertBranch(Js::OpCode::Br, doneLabel, loadLabel); | |||
done = doneLabel; | |||
} | |||
else if (instr->m_func->GetJITFunctionBody()->IsWasmFunction()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked yet at this file, but since this Pull Request is for Interpreter support, I would do this part later, also because I would prefer having x64 and x86 support at the same time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cellule I updated the PR with a new commit to handle x86 as well.
Can you rebase this change on top of master and also run case 'assert_trap':
assertTrap(m, command);
break; I tried real quick and it crashes in interpreter and doesn't throw with |
I also found a case in
You should fix that as part of this change. |
b0bb341
to
f9f7943
Compare
Updated the PR to handle the case when index + sizeof the memop exceeds the heapsize. There are pending failures that need to be handled for the spec.
|
7986b5b
to
e15956a
Compare
Reviewed 3 of 9 files at r2, 4 of 6 files at r3, 4 of 4 files at r4. lib/Backend/LowerMDShared.cpp, line 291 at r4 (raw file):
On x64, since the index opnd is 32 bits and TyMachReg is 64bits is impossible to overflow this addition. if (indexOpnd->GetSize() >= TySize[TyMachReg])
{
// Check if the addition overflowed
Lowerer::InsertBranch(Js::OpCode::JO, helperLabel, helperLabel);
} lib/Backend/LowerMDShared.cpp, line 293 at r4 (raw file):
You have to use the result of the addition here lib/Runtime/Language/InterpreterStackFrame.cpp, line 8291 at r4 (raw file):
remove this return lib/Runtime/Language/InterpreterStackFrame.cpp, line 8365 at r4 (raw file):
@MikeHolman Do you think there is a better way to check this case. The only thing I can think of is lib/Runtime/Language/InterpreterStackFrame.cpp, line 8369 at r4 (raw file):
The call to this function will do an additional check. If you can find a clean way to avoid the additional check it would be nice. test/wasm/array.js, line 9 at r4 (raw file):
Could you please test the following cases Could you also add a few cases with 32bits load/store Comments from Reviewable |
this is not true in wasm. WebAssembly memory can be any number of 64k pages (including 0). And AccessNeedsBoundCheck returns 0x1000000 in case IsHeapBufferConst() is false. Refers to: lib/Backend/Lower.cpp:8847 in e15956a. [](commit_id = e15956a, deletion_comment = False) |
IR::Instr *defInstr = baseOpnd->m_sym->m_instrDef; | ||
IR::RegOpnd *arrayBuffer = defInstr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd(); | ||
IR::Opnd *srcOpnd = IR::IndirOpnd::New(arrayBuffer, Js::ArrayBuffer::GetByteLengthOffset(), TyMachReg, m_func); | ||
IR::RegOpnd *arrayLenOpnd = IR::RegOpnd::New(TyMachReg, m_func); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buffer length is already in src2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand, src2 of which instruction ? I am reading it from the Js::ArrayBuffer::GetByteLengthOffset() right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src2 of instr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s38.i32 = LdArrViewElem [s3.var+s37.i32].u8 has no src2 ? And s3/s37 don't have the length of the array buffer ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, looks like on x64 it isn't set (since we didn't used to need it). in irbuilderasmjs.cpp line 1546 it should be set.
I think we should make Reviewed 3 of 3 files at r5. lib/WasmReader/WasmByteCodeGenerator.cpp, line 1129 at r5 (raw file):
We should add lib/WasmReader/WasmByteCodeGenerator.cpp, line 1146 at r5 (raw file):
I don't think this is by spec. If the previous instruction have side effect we should still trap only when we reach this load/store. Put this check with Comments from Reviewable |
Reviewed 3 of 3 files at r6. lib/WasmReader/WasmByteCodeGenerator.cpp, line 1128 at r6 (raw file):
This is wrong, the function is not invalid, but we need to trap accordingly if this happens. Comments from Reviewable |
|
||
if (offset >= m_module->GetMemory()->maxSize) | ||
{ | ||
throw WasmCompilationException(_u("Index is out of bounds")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we shouldn't add this check. it isn't a syntax error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are generating this bytecode sequence :
017d Ld_IntConst I3 int:-1
0183 Add_Int I2 I2 I3
0187 LdArrWasm I2 = HEAP32[I2]
When index + offset overflow, by the time we execute LdArrWasm, the value would be wrapped around. And we cant trap. We might need a different sequence here.
Adding the above check allowed me to catch such cases during bytecode generation.
Should we generate a different sequence instead ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, we probably need to emit different code here. if index+offset overflow uint32, then we need to trap at runtime. this check doesn't prevent that from happening. For example, even with small offset you might have some large index that causes index+offset to overflow
In reply to: 85627647 [](ancestors = 85627647)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have the check but emit an opcode that traps, that way it is a runtime trap not compile error, see my previous comment about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we can't know this at compile time. Offset+index might overflow where offset alone doesn't. Besides this doesn't work for import or grow memory
Review status: all files reviewed at latest revision, 10 unresolved discussions, some commit checks failed. lib/Runtime/Language/InterpreterStackFrame.cpp, line 8365 at r4 (raw file):
|
lib/Runtime/Language/InterpreterStackFrame.cpp, line 8365 at r4 (raw file):
|
Review status: all files reviewed at latest revision, 10 unresolved discussions, some commit checks failed. lib/Runtime/Language/InterpreterStackFrame.cpp, line 8365 at r4 (raw file):
|
3bfabcd
to
2d2616f
Compare
@@ -3108,6 +3121,14 @@ IRBuilderAsmJs::BuildLong1Int1(Js::OpCodeAsmJs newOpcode, uint32 offset, Js::Reg | |||
srcOpnd = BuildSrcOpnd(srcRegSlot, TyUint32); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Js::OpCodeAsmJs::Conv_ITD & Js::OpCodeAsmJs::Conv_UTD
This looks like a bad copy paste on my part, could you remove it while you're there
lowererMD->m_lowerer->GenerateRuntimeError(loadLabel, JSERR_InvalidTypedArrayIndex, IR::HelperOp_RuntimeRangeError); | ||
Lowerer::InsertBranch(Js::OpCode::Br, loadLabel, helperLabel); | ||
|
||
addrOpnd->AsIndirOpnd()->SetIndexOpnd(indexPair.low->AsRegOpnd()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now it is true that indexPair.low
is a RegOpnd, but once we implement const folding for int64, the index could be a constant opnd.
Since we have unit tests hitting that path, just put an assert for now and it will be easier to fix once we get there.
LGTM after minor fixes and rebase on master |
1955d37
to
3458514
Compare
Done. Thanks @Cellule and @MikeHolman. |
Out of bound memory access in Wasm traps. Code to insert relevant checks and error is added in the interpreter and jit. Wasm provides an offset + index addressing mode for memory accesses. We represent this using an int64 to address problems with wrapping due to overflow. Also new opcode LdArrViewElemWasm is added specially for wasm since we cannot use LdArrViewElem of AsmJs which is side-effect free.
Closes #1877