1316634 - Implement efficient WebAssembly.validate

1316634: Use ModuleEnvironment in the FunctionDecoder.

1316634: Move function validation to WasmBinaryFormat.h/cpp && rename it WasmValidate.

1316634: Implement efficient WebAssembly.validate

1316634: Use CheckedInt before checking against maximum limits
This commit is contained in:
win7-7 2025-12-28 18:51:33 +02:00 committed by wuggy
commit ba8732b25b
13 changed files with 518 additions and 443 deletions

View file

@ -349,7 +349,6 @@ main_deunified_sources = [
'vm/Xdr.cpp',
'wasm/AsmJS.cpp',
'wasm/WasmBaselineCompile.cpp',
'wasm/WasmBinaryFormat.cpp',
'wasm/WasmBinaryIterator.cpp',
'wasm/WasmBinaryToAST.cpp',
'wasm/WasmBinaryToExperimentalText.cpp',
@ -368,7 +367,8 @@ main_deunified_sources = [
'wasm/WasmTable.cpp',
'wasm/WasmTextToBinary.cpp',
'wasm/WasmTextUtils.cpp',
'wasm/WasmTypes.cpp'
'wasm/WasmTypes.cpp',
'wasm/WasmValidate.cpp'
]
# jsarray.cpp and jsatom.cpp cannot be built in unified mode because

View file

@ -38,12 +38,12 @@
#include "vm/StringBuffer.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmValidate.h"
#include "jsobjinlines.h"

View file

@ -117,10 +117,10 @@
# include "jit/x86-shared/Assembler-x86-shared.h"
#endif
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmBinaryIterator.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
#include "jit/MacroAssembler-inl.h"

View file

@ -24,7 +24,7 @@
#include "jsprf.h"
#include "jit/AtomicOp.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmValidate.h"
namespace js {
namespace wasm {

View file

@ -23,8 +23,8 @@
#include "jscntxt.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmBinaryIterator.h"
#include "wasm/WasmValidate.h"
using namespace js;
using namespace js::wasm;

View file

@ -17,408 +17,17 @@
#include "wasm/WasmCompile.h"
#include "mozilla/CheckedInt.h"
#include "jsprf.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmBinaryIterator.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::CheckedInt;
using mozilla::IsNaN;
namespace {
struct ValidatingPolicy : OpIterPolicy
{
// Validation is what we're all about here.
static const bool Validate = true;
};
typedef OpIter<ValidatingPolicy> ValidatingOpIter;
class FunctionDecoder
{
const ModuleGenerator& mg_;
const ValTypeVector& locals_;
ValidatingOpIter iter_;
public:
FunctionDecoder(const ModuleGenerator& mg, const ValTypeVector& locals, Decoder& d)
: mg_(mg), locals_(locals), iter_(d)
{}
const ModuleGenerator& mg() const { return mg_; }
ValidatingOpIter& iter() { return iter_; }
const ValTypeVector& locals() const { return locals_; }
bool checkHasMemory() {
if (!mg().usesMemory())
return iter().fail("can't touch memory without memory");
return true;
}
};
} // end anonymous namespace
static bool
DecodeCallArgs(FunctionDecoder& f, const Sig& sig)
{
const ValTypeVector& args = sig.args();
uint32_t numArgs = args.length();
for (size_t i = 0; i < numArgs; ++i) {
ValType argType = args[i];
if (!f.iter().readCallArg(argType, numArgs, i, nullptr))
return false;
}
return f.iter().readCallArgsEnd(numArgs);
}
static bool
DecodeCallReturn(FunctionDecoder& f, const Sig& sig)
{
return f.iter().readCallReturn(sig.ret());
}
static bool
DecodeCall(FunctionDecoder& f)
{
uint32_t funcIndex;
if (!f.iter().readCall(&funcIndex))
return false;
if (funcIndex >= f.mg().numFuncs())
return f.iter().fail("callee index out of range");
if (!f.iter().inReachableCode())
return true;
const Sig* sig = &f.mg().funcSig(funcIndex);
return DecodeCallArgs(f, *sig) &&
DecodeCallReturn(f, *sig);
}
static bool
DecodeCallIndirect(FunctionDecoder& f)
{
if (!f.mg().numTables())
return f.iter().fail("can't call_indirect without a table");
uint32_t sigIndex;
if (!f.iter().readCallIndirect(&sigIndex, nullptr))
return false;
if (sigIndex >= f.mg().numSigs())
return f.iter().fail("signature index out of range");
if (!f.iter().inReachableCode())
return true;
const Sig& sig = f.mg().sig(sigIndex);
if (!DecodeCallArgs(f, sig))
return false;
return DecodeCallReturn(f, sig);
}
static bool
DecodeBrTable(FunctionDecoder& f)
{
uint32_t tableLength;
ExprType type = ExprType::Limit;
if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
return false;
uint32_t depth;
for (size_t i = 0, e = tableLength; i < e; ++i) {
if (!f.iter().readBrTableEntry(&type, nullptr, &depth))
return false;
}
// Read the default label.
return f.iter().readBrTableDefault(&type, nullptr, &depth);
}
static bool
DecodeFunctionBodyExprs(FunctionDecoder& f)
{
#define CHECK(c) if (!(c)) return false; break
while (true) {
uint16_t op;
if (!f.iter().readOp(&op))
return false;
switch (op) {
case uint16_t(Op::End):
if (!f.iter().readEnd(nullptr, nullptr, nullptr))
return false;
if (f.iter().controlStackEmpty())
return true;
break;
case uint16_t(Op::Nop):
CHECK(f.iter().readNop());
case uint16_t(Op::Drop):
CHECK(f.iter().readDrop());
case uint16_t(Op::Call):
CHECK(DecodeCall(f));
case uint16_t(Op::CallIndirect):
CHECK(DecodeCallIndirect(f));
case uint16_t(Op::I32Const):
CHECK(f.iter().readI32Const(nullptr));
case uint16_t(Op::I64Const):
CHECK(f.iter().readI64Const(nullptr));
case uint16_t(Op::F32Const):
CHECK(f.iter().readF32Const(nullptr));
case uint16_t(Op::F64Const):
CHECK(f.iter().readF64Const(nullptr));
case uint16_t(Op::GetLocal):
CHECK(f.iter().readGetLocal(f.locals(), nullptr));
case uint16_t(Op::SetLocal):
CHECK(f.iter().readSetLocal(f.locals(), nullptr, nullptr));
case uint16_t(Op::TeeLocal):
CHECK(f.iter().readTeeLocal(f.locals(), nullptr, nullptr));
case uint16_t(Op::GetGlobal):
CHECK(f.iter().readGetGlobal(f.mg().globals(), nullptr));
case uint16_t(Op::SetGlobal):
CHECK(f.iter().readSetGlobal(f.mg().globals(), nullptr, nullptr));
case uint16_t(Op::Select):
CHECK(f.iter().readSelect(nullptr, nullptr, nullptr, nullptr));
case uint16_t(Op::Block):
CHECK(f.iter().readBlock());
case uint16_t(Op::Loop):
CHECK(f.iter().readLoop());
case uint16_t(Op::If):
CHECK(f.iter().readIf(nullptr));
case uint16_t(Op::Else):
CHECK(f.iter().readElse(nullptr, nullptr));
case uint16_t(Op::I32Clz):
case uint16_t(Op::I32Ctz):
case uint16_t(Op::I32Popcnt):
CHECK(f.iter().readUnary(ValType::I32, nullptr));
case uint16_t(Op::I64Clz):
case uint16_t(Op::I64Ctz):
case uint16_t(Op::I64Popcnt):
CHECK(f.iter().readUnary(ValType::I64, nullptr));
case uint16_t(Op::F32Abs):
case uint16_t(Op::F32Neg):
case uint16_t(Op::F32Ceil):
case uint16_t(Op::F32Floor):
case uint16_t(Op::F32Sqrt):
case uint16_t(Op::F32Trunc):
case uint16_t(Op::F32Nearest):
CHECK(f.iter().readUnary(ValType::F32, nullptr));
case uint16_t(Op::F64Abs):
case uint16_t(Op::F64Neg):
case uint16_t(Op::F64Ceil):
case uint16_t(Op::F64Floor):
case uint16_t(Op::F64Sqrt):
case uint16_t(Op::F64Trunc):
case uint16_t(Op::F64Nearest):
CHECK(f.iter().readUnary(ValType::F64, nullptr));
case uint16_t(Op::I32Add):
case uint16_t(Op::I32Sub):
case uint16_t(Op::I32Mul):
case uint16_t(Op::I32DivS):
case uint16_t(Op::I32DivU):
case uint16_t(Op::I32RemS):
case uint16_t(Op::I32RemU):
case uint16_t(Op::I32And):
case uint16_t(Op::I32Or):
case uint16_t(Op::I32Xor):
case uint16_t(Op::I32Shl):
case uint16_t(Op::I32ShrS):
case uint16_t(Op::I32ShrU):
case uint16_t(Op::I32Rotl):
case uint16_t(Op::I32Rotr):
CHECK(f.iter().readBinary(ValType::I32, nullptr, nullptr));
case uint16_t(Op::I64Add):
case uint16_t(Op::I64Sub):
case uint16_t(Op::I64Mul):
case uint16_t(Op::I64DivS):
case uint16_t(Op::I64DivU):
case uint16_t(Op::I64RemS):
case uint16_t(Op::I64RemU):
case uint16_t(Op::I64And):
case uint16_t(Op::I64Or):
case uint16_t(Op::I64Xor):
case uint16_t(Op::I64Shl):
case uint16_t(Op::I64ShrS):
case uint16_t(Op::I64ShrU):
case uint16_t(Op::I64Rotl):
case uint16_t(Op::I64Rotr):
CHECK(f.iter().readBinary(ValType::I64, nullptr, nullptr));
case uint16_t(Op::F32Add):
case uint16_t(Op::F32Sub):
case uint16_t(Op::F32Mul):
case uint16_t(Op::F32Div):
case uint16_t(Op::F32Min):
case uint16_t(Op::F32Max):
case uint16_t(Op::F32CopySign):
CHECK(f.iter().readBinary(ValType::F32, nullptr, nullptr));
case uint16_t(Op::F64Add):
case uint16_t(Op::F64Sub):
case uint16_t(Op::F64Mul):
case uint16_t(Op::F64Div):
case uint16_t(Op::F64Min):
case uint16_t(Op::F64Max):
case uint16_t(Op::F64CopySign):
CHECK(f.iter().readBinary(ValType::F64, nullptr, nullptr));
case uint16_t(Op::I32Eq):
case uint16_t(Op::I32Ne):
case uint16_t(Op::I32LtS):
case uint16_t(Op::I32LtU):
case uint16_t(Op::I32LeS):
case uint16_t(Op::I32LeU):
case uint16_t(Op::I32GtS):
case uint16_t(Op::I32GtU):
case uint16_t(Op::I32GeS):
case uint16_t(Op::I32GeU):
CHECK(f.iter().readComparison(ValType::I32, nullptr, nullptr));
case uint16_t(Op::I64Eq):
case uint16_t(Op::I64Ne):
case uint16_t(Op::I64LtS):
case uint16_t(Op::I64LtU):
case uint16_t(Op::I64LeS):
case uint16_t(Op::I64LeU):
case uint16_t(Op::I64GtS):
case uint16_t(Op::I64GtU):
case uint16_t(Op::I64GeS):
case uint16_t(Op::I64GeU):
CHECK(f.iter().readComparison(ValType::I64, nullptr, nullptr));
case uint16_t(Op::F32Eq):
case uint16_t(Op::F32Ne):
case uint16_t(Op::F32Lt):
case uint16_t(Op::F32Le):
case uint16_t(Op::F32Gt):
case uint16_t(Op::F32Ge):
CHECK(f.iter().readComparison(ValType::F32, nullptr, nullptr));
case uint16_t(Op::F64Eq):
case uint16_t(Op::F64Ne):
case uint16_t(Op::F64Lt):
case uint16_t(Op::F64Le):
case uint16_t(Op::F64Gt):
case uint16_t(Op::F64Ge):
CHECK(f.iter().readComparison(ValType::F64, nullptr, nullptr));
case uint16_t(Op::I32Eqz):
CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr));
case uint16_t(Op::I64Eqz):
case uint16_t(Op::I32WrapI64):
CHECK(f.iter().readConversion(ValType::I64, ValType::I32, nullptr));
case uint16_t(Op::I32TruncSF32):
case uint16_t(Op::I32TruncUF32):
case uint16_t(Op::I32ReinterpretF32):
CHECK(f.iter().readConversion(ValType::F32, ValType::I32, nullptr));
case uint16_t(Op::I32TruncSF64):
case uint16_t(Op::I32TruncUF64):
CHECK(f.iter().readConversion(ValType::F64, ValType::I32, nullptr));
case uint16_t(Op::I64ExtendSI32):
case uint16_t(Op::I64ExtendUI32):
CHECK(f.iter().readConversion(ValType::I32, ValType::I64, nullptr));
case uint16_t(Op::I64TruncSF32):
case uint16_t(Op::I64TruncUF32):
CHECK(f.iter().readConversion(ValType::F32, ValType::I64, nullptr));
case uint16_t(Op::I64TruncSF64):
case uint16_t(Op::I64TruncUF64):
case uint16_t(Op::I64ReinterpretF64):
CHECK(f.iter().readConversion(ValType::F64, ValType::I64, nullptr));
case uint16_t(Op::F32ConvertSI32):
case uint16_t(Op::F32ConvertUI32):
case uint16_t(Op::F32ReinterpretI32):
CHECK(f.iter().readConversion(ValType::I32, ValType::F32, nullptr));
case uint16_t(Op::F32ConvertSI64):
case uint16_t(Op::F32ConvertUI64):
CHECK(f.iter().readConversion(ValType::I64, ValType::F32, nullptr));
case uint16_t(Op::F32DemoteF64):
CHECK(f.iter().readConversion(ValType::F64, ValType::F32, nullptr));
case uint16_t(Op::F64ConvertSI32):
case uint16_t(Op::F64ConvertUI32):
CHECK(f.iter().readConversion(ValType::I32, ValType::F64, nullptr));
case uint16_t(Op::F64ConvertSI64):
case uint16_t(Op::F64ConvertUI64):
case uint16_t(Op::F64ReinterpretI64):
CHECK(f.iter().readConversion(ValType::I64, ValType::F64, nullptr));
case uint16_t(Op::F64PromoteF32):
CHECK(f.iter().readConversion(ValType::F32, ValType::F64, nullptr));
case uint16_t(Op::I32Extend8S):
case uint16_t(Op::I32Extend16S):
CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr));
case uint16_t(Op::I64Extend8S):
case uint16_t(Op::I64Extend16S):
case uint16_t(Op::I64Extend32S):
CHECK(f.iter().readConversion(ValType::I64, ValType::I64, nullptr));
case uint16_t(Op::I32Load8S):
case uint16_t(Op::I32Load8U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 1, nullptr));
case uint16_t(Op::I32Load16S):
case uint16_t(Op::I32Load16U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 2, nullptr));
case uint16_t(Op::I32Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 4, nullptr));
case uint16_t(Op::I64Load8S):
case uint16_t(Op::I64Load8U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 1, nullptr));
case uint16_t(Op::I64Load16S):
case uint16_t(Op::I64Load16U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 2, nullptr));
case uint16_t(Op::I64Load32S):
case uint16_t(Op::I64Load32U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 4, nullptr));
case uint16_t(Op::I64Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 8, nullptr));
case uint16_t(Op::F32Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F32, 4, nullptr));
case uint16_t(Op::F64Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F64, 8, nullptr));
case uint16_t(Op::I32Store8):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 1, nullptr, nullptr));
case uint16_t(Op::I32Store16):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 2, nullptr, nullptr));
case uint16_t(Op::I32Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 4, nullptr, nullptr));
case uint16_t(Op::I64Store8):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 1, nullptr, nullptr));
case uint16_t(Op::I64Store16):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 2, nullptr, nullptr));
case uint16_t(Op::I64Store32):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 4, nullptr, nullptr));
case uint16_t(Op::I64Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 8, nullptr, nullptr));
case uint16_t(Op::F32Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F32, 4, nullptr, nullptr));
case uint16_t(Op::F64Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F64, 8, nullptr, nullptr));
case uint16_t(Op::GrowMemory):
CHECK(f.checkHasMemory() && f.iter().readGrowMemory(nullptr));
case uint16_t(Op::CurrentMemory):
CHECK(f.checkHasMemory() && f.iter().readCurrentMemory());
case uint16_t(Op::Br):
CHECK(f.iter().readBr(nullptr, nullptr, nullptr));
case uint16_t(Op::BrIf):
CHECK(f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr));
case uint16_t(Op::BrTable):
CHECK(DecodeBrTable(f));
case uint16_t(Op::Return):
CHECK(f.iter().readReturn(nullptr));
case uint16_t(Op::Unreachable):
CHECK(f.iter().readUnreachable());
default:
return f.iter().unrecognizedOpcode(op);
}
}
MOZ_CRASH("unreachable");
#undef CHECK
}
static bool
DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex)
{
@ -436,23 +45,7 @@ DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex)
if (!mg.startFuncDef(offsetInModule, &fg))
return false;
ValTypeVector locals;
const Sig& sig = mg.funcSig(funcIndex);
if (!locals.appendAll(sig.args()))
return false;
if (!DecodeLocalEntries(d, ModuleKind::Wasm, &locals))
return false;
FunctionDecoder f(mg, locals, d);
if (!f.iter().readFunctionStart(sig.ret()))
return false;
if (!DecodeFunctionBodyExprs(f))
return false;
if (!f.iter().readFunctionEnd())
if (!ValidateFunctionBody(mg.env(), funcIndex, d))
return false;
if (d.currentPosition() != bodyBegin + bodySize)

View file

@ -801,18 +801,6 @@ ModuleGenerator::numFuncDefs() const
return env_->funcSigs.length() - numFuncImports();
}
uint32_t
ModuleGenerator::numFuncs() const
{
// asm.js pre-reserves a bunch of function index space which is
// incrementally filled in during function-body validation. Thus, there are
// a few possible interpretations of numFuncs() (total index space size vs.
// exact number of imports/definitions encountered so far) and to simplify
// things we simply only define this quantity for wasm.
MOZ_ASSERT(!isAsmJS());
return env_->funcSigs.length();
}
const SigWithId&
ModuleGenerator::funcSig(uint32_t funcIndex) const
{
@ -978,7 +966,7 @@ ModuleGenerator::finishFuncDefs()
MOZ_ASSERT(funcCodeRange(i).funcIndex() == i);
} else {
MOZ_ASSERT(numFinishedFuncDefs_ == numFuncDefs());
for (uint32_t i = 0; i < numFuncs(); i++)
for (uint32_t i = 0; i < env_->numFuncs(); i++)
MOZ_ASSERT(funcCodeRange(i).funcIndex() == i);
}
#endif

View file

@ -19,8 +19,8 @@
#define wasm_generator_h
#include "jit/MacroAssembler.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmValidate.h"
namespace js {
namespace wasm {
@ -289,7 +289,6 @@ private:
// Functions declarations:
uint32_t numFuncImports() const;
uint32_t numFuncDefs() const;
uint32_t numFuncs() const;
// Function definitions:
MOZ_MUST_USE bool startFuncDefs();

View file

@ -23,10 +23,10 @@
#include "jit/CodeGenerator.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmBinaryIterator.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
using namespace js;
using namespace js::jit;

View file

@ -31,6 +31,7 @@
#include "wasm/WasmInstance.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
#include "jsobjinlines.h"
@ -1927,12 +1928,8 @@ WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp)
if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode))
return false;
CompileArgs compileArgs;
if (!InitCompileArgs(cx, &compileArgs))
return false;
UniqueChars error;
bool validated = !!Compile(*bytecode, compileArgs, &error);
bool validated = Validate(*bytecode, &error);
// If the reason for validation failure was OOM (signalled by null error
// message), report out-of-memory so that validate's return is always

View file

@ -30,8 +30,8 @@
#include "js/CharacterEncoding.h"
#include "js/HashTable.h"
#include "wasm/WasmAST.h"
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmTypes.h"
#include "wasm/WasmValidate.h"
using namespace js;
using namespace js::wasm;

View file

@ -15,15 +15,17 @@
* limitations under the License.
*/
#include "wasm/WasmBinaryFormat.h"
#include "wasm/WasmValidate.h"
#include "mozilla/CheckedInt.h"
#include "jsprf.h"
#include "jit/JitOptions.h"
#include "wasm/WasmBinaryIterator.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::CheckedInt;
@ -140,6 +142,405 @@ wasm::DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals)
return true;
}
// Function body validation.
struct ValidatingPolicy : OpIterPolicy
{
// Validation is what we're all about here.
static const bool Validate = true;
};
typedef OpIter<ValidatingPolicy> ValidatingOpIter;
class FunctionDecoder
{
const ModuleEnvironment& env_;
const ValTypeVector& locals_;
ValidatingOpIter iter_;
public:
FunctionDecoder(const ModuleEnvironment& env, const ValTypeVector& locals, Decoder& d)
: env_(env), locals_(locals), iter_(d)
{}
const ModuleEnvironment& env() const { return env_; }
ValidatingOpIter& iter() { return iter_; }
const ValTypeVector& locals() const { return locals_; }
bool checkHasMemory() {
if (!env().usesMemory())
return iter().fail("can't touch memory without memory");
return true;
}
};
static bool
DecodeCallArgs(FunctionDecoder& f, const Sig& sig)
{
const ValTypeVector& args = sig.args();
uint32_t numArgs = args.length();
for (size_t i = 0; i < numArgs; ++i) {
ValType argType = args[i];
if (!f.iter().readCallArg(argType, numArgs, i, nullptr))
return false;
}
return f.iter().readCallArgsEnd(numArgs);
}
static bool
DecodeCallReturn(FunctionDecoder& f, const Sig& sig)
{
return f.iter().readCallReturn(sig.ret());
}
static bool
DecodeCall(FunctionDecoder& f)
{
uint32_t funcIndex;
if (!f.iter().readCall(&funcIndex))
return false;
if (funcIndex >= f.env().numFuncs())
return f.iter().fail("callee index out of range");
if (!f.iter().inReachableCode())
return true;
const Sig* sig = f.env().funcSigs[funcIndex];
return DecodeCallArgs(f, *sig) &&
DecodeCallReturn(f, *sig);
}
static bool
DecodeCallIndirect(FunctionDecoder& f)
{
if (!f.env().numTables())
return f.iter().fail("can't call_indirect without a table");
uint32_t sigIndex;
if (!f.iter().readCallIndirect(&sigIndex, nullptr))
return false;
if (sigIndex >= f.env().numSigs())
return f.iter().fail("signature index out of range");
if (!f.iter().inReachableCode())
return true;
const Sig& sig = f.env().sigs[sigIndex];
if (!DecodeCallArgs(f, sig))
return false;
return DecodeCallReturn(f, sig);
}
static bool
DecodeBrTable(FunctionDecoder& f)
{
uint32_t tableLength;
ExprType type = ExprType::Limit;
if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
return false;
uint32_t depth;
for (size_t i = 0, e = tableLength; i < e; ++i) {
if (!f.iter().readBrTableEntry(&type, nullptr, &depth))
return false;
}
// Read the default label.
return f.iter().readBrTableDefault(&type, nullptr, &depth);
}
static bool
DecodeFunctionBodyExprs(FunctionDecoder& f)
{
#define CHECK(c) if (!(c)) return false; break
while (true) {
uint16_t op;
if (!f.iter().readOp(&op))
return false;
switch (op) {
case uint16_t(Op::End):
if (!f.iter().readEnd(nullptr, nullptr, nullptr))
return false;
if (f.iter().controlStackEmpty())
return true;
break;
case uint16_t(Op::Nop):
CHECK(f.iter().readNop());
case uint16_t(Op::Drop):
CHECK(f.iter().readDrop());
case uint16_t(Op::Call):
CHECK(DecodeCall(f));
case uint16_t(Op::CallIndirect):
CHECK(DecodeCallIndirect(f));
case uint16_t(Op::I32Const):
CHECK(f.iter().readI32Const(nullptr));
case uint16_t(Op::I64Const):
CHECK(f.iter().readI64Const(nullptr));
case uint16_t(Op::F32Const):
CHECK(f.iter().readF32Const(nullptr));
case uint16_t(Op::F64Const):
CHECK(f.iter().readF64Const(nullptr));
case uint16_t(Op::GetLocal):
CHECK(f.iter().readGetLocal(f.locals(), nullptr));
case uint16_t(Op::SetLocal):
CHECK(f.iter().readSetLocal(f.locals(), nullptr, nullptr));
case uint16_t(Op::TeeLocal):
CHECK(f.iter().readTeeLocal(f.locals(), nullptr, nullptr));
case uint16_t(Op::GetGlobal):
CHECK(f.iter().readGetGlobal(f.env().globals, nullptr));
case uint16_t(Op::SetGlobal):
CHECK(f.iter().readSetGlobal(f.env().globals, nullptr, nullptr));
case uint16_t(Op::Select):
CHECK(f.iter().readSelect(nullptr, nullptr, nullptr, nullptr));
case uint16_t(Op::Block):
CHECK(f.iter().readBlock());
case uint16_t(Op::Loop):
CHECK(f.iter().readLoop());
case uint16_t(Op::If):
CHECK(f.iter().readIf(nullptr));
case uint16_t(Op::Else):
CHECK(f.iter().readElse(nullptr, nullptr));
case uint16_t(Op::I32Clz):
case uint16_t(Op::I32Ctz):
case uint16_t(Op::I32Popcnt):
CHECK(f.iter().readUnary(ValType::I32, nullptr));
case uint16_t(Op::I64Clz):
case uint16_t(Op::I64Ctz):
case uint16_t(Op::I64Popcnt):
CHECK(f.iter().readUnary(ValType::I64, nullptr));
case uint16_t(Op::F32Abs):
case uint16_t(Op::F32Neg):
case uint16_t(Op::F32Ceil):
case uint16_t(Op::F32Floor):
case uint16_t(Op::F32Sqrt):
case uint16_t(Op::F32Trunc):
case uint16_t(Op::F32Nearest):
CHECK(f.iter().readUnary(ValType::F32, nullptr));
case uint16_t(Op::F64Abs):
case uint16_t(Op::F64Neg):
case uint16_t(Op::F64Ceil):
case uint16_t(Op::F64Floor):
case uint16_t(Op::F64Sqrt):
case uint16_t(Op::F64Trunc):
case uint16_t(Op::F64Nearest):
CHECK(f.iter().readUnary(ValType::F64, nullptr));
case uint16_t(Op::I32Add):
case uint16_t(Op::I32Sub):
case uint16_t(Op::I32Mul):
case uint16_t(Op::I32DivS):
case uint16_t(Op::I32DivU):
case uint16_t(Op::I32RemS):
case uint16_t(Op::I32RemU):
case uint16_t(Op::I32And):
case uint16_t(Op::I32Or):
case uint16_t(Op::I32Xor):
case uint16_t(Op::I32Shl):
case uint16_t(Op::I32ShrS):
case uint16_t(Op::I32ShrU):
case uint16_t(Op::I32Rotl):
case uint16_t(Op::I32Rotr):
CHECK(f.iter().readBinary(ValType::I32, nullptr, nullptr));
case uint16_t(Op::I64Add):
case uint16_t(Op::I64Sub):
case uint16_t(Op::I64Mul):
case uint16_t(Op::I64DivS):
case uint16_t(Op::I64DivU):
case uint16_t(Op::I64RemS):
case uint16_t(Op::I64RemU):
case uint16_t(Op::I64And):
case uint16_t(Op::I64Or):
case uint16_t(Op::I64Xor):
case uint16_t(Op::I64Shl):
case uint16_t(Op::I64ShrS):
case uint16_t(Op::I64ShrU):
case uint16_t(Op::I64Rotl):
case uint16_t(Op::I64Rotr):
CHECK(f.iter().readBinary(ValType::I64, nullptr, nullptr));
case uint16_t(Op::F32Add):
case uint16_t(Op::F32Sub):
case uint16_t(Op::F32Mul):
case uint16_t(Op::F32Div):
case uint16_t(Op::F32Min):
case uint16_t(Op::F32Max):
case uint16_t(Op::F32CopySign):
CHECK(f.iter().readBinary(ValType::F32, nullptr, nullptr));
case uint16_t(Op::F64Add):
case uint16_t(Op::F64Sub):
case uint16_t(Op::F64Mul):
case uint16_t(Op::F64Div):
case uint16_t(Op::F64Min):
case uint16_t(Op::F64Max):
case uint16_t(Op::F64CopySign):
CHECK(f.iter().readBinary(ValType::F64, nullptr, nullptr));
case uint16_t(Op::I32Eq):
case uint16_t(Op::I32Ne):
case uint16_t(Op::I32LtS):
case uint16_t(Op::I32LtU):
case uint16_t(Op::I32LeS):
case uint16_t(Op::I32LeU):
case uint16_t(Op::I32GtS):
case uint16_t(Op::I32GtU):
case uint16_t(Op::I32GeS):
case uint16_t(Op::I32GeU):
CHECK(f.iter().readComparison(ValType::I32, nullptr, nullptr));
case uint16_t(Op::I64Eq):
case uint16_t(Op::I64Ne):
case uint16_t(Op::I64LtS):
case uint16_t(Op::I64LtU):
case uint16_t(Op::I64LeS):
case uint16_t(Op::I64LeU):
case uint16_t(Op::I64GtS):
case uint16_t(Op::I64GtU):
case uint16_t(Op::I64GeS):
case uint16_t(Op::I64GeU):
CHECK(f.iter().readComparison(ValType::I64, nullptr, nullptr));
case uint16_t(Op::F32Eq):
case uint16_t(Op::F32Ne):
case uint16_t(Op::F32Lt):
case uint16_t(Op::F32Le):
case uint16_t(Op::F32Gt):
case uint16_t(Op::F32Ge):
CHECK(f.iter().readComparison(ValType::F32, nullptr, nullptr));
case uint16_t(Op::F64Eq):
case uint16_t(Op::F64Ne):
case uint16_t(Op::F64Lt):
case uint16_t(Op::F64Le):
case uint16_t(Op::F64Gt):
case uint16_t(Op::F64Ge):
CHECK(f.iter().readComparison(ValType::F64, nullptr, nullptr));
case uint16_t(Op::I32Eqz):
CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr));
case uint16_t(Op::I64Eqz):
case uint16_t(Op::I32WrapI64):
CHECK(f.iter().readConversion(ValType::I64, ValType::I32, nullptr));
case uint16_t(Op::I32TruncSF32):
case uint16_t(Op::I32TruncUF32):
case uint16_t(Op::I32ReinterpretF32):
CHECK(f.iter().readConversion(ValType::F32, ValType::I32, nullptr));
case uint16_t(Op::I32TruncSF64):
case uint16_t(Op::I32TruncUF64):
CHECK(f.iter().readConversion(ValType::F64, ValType::I32, nullptr));
case uint16_t(Op::I64ExtendSI32):
case uint16_t(Op::I64ExtendUI32):
CHECK(f.iter().readConversion(ValType::I32, ValType::I64, nullptr));
case uint16_t(Op::I64TruncSF32):
case uint16_t(Op::I64TruncUF32):
CHECK(f.iter().readConversion(ValType::F32, ValType::I64, nullptr));
case uint16_t(Op::I64TruncSF64):
case uint16_t(Op::I64TruncUF64):
case uint16_t(Op::I64ReinterpretF64):
CHECK(f.iter().readConversion(ValType::F64, ValType::I64, nullptr));
case uint16_t(Op::F32ConvertSI32):
case uint16_t(Op::F32ConvertUI32):
case uint16_t(Op::F32ReinterpretI32):
CHECK(f.iter().readConversion(ValType::I32, ValType::F32, nullptr));
case uint16_t(Op::F32ConvertSI64):
case uint16_t(Op::F32ConvertUI64):
CHECK(f.iter().readConversion(ValType::I64, ValType::F32, nullptr));
case uint16_t(Op::F32DemoteF64):
CHECK(f.iter().readConversion(ValType::F64, ValType::F32, nullptr));
case uint16_t(Op::F64ConvertSI32):
case uint16_t(Op::F64ConvertUI32):
CHECK(f.iter().readConversion(ValType::I32, ValType::F64, nullptr));
case uint16_t(Op::F64ConvertSI64):
case uint16_t(Op::F64ConvertUI64):
case uint16_t(Op::F64ReinterpretI64):
CHECK(f.iter().readConversion(ValType::I64, ValType::F64, nullptr));
case uint16_t(Op::F64PromoteF32):
CHECK(f.iter().readConversion(ValType::F32, ValType::F64, nullptr));
case uint16_t(Op::I32Load8S):
case uint16_t(Op::I32Load8U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 1, nullptr));
case uint16_t(Op::I32Load16S):
case uint16_t(Op::I32Load16U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 2, nullptr));
case uint16_t(Op::I32Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 4, nullptr));
case uint16_t(Op::I64Load8S):
case uint16_t(Op::I64Load8U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 1, nullptr));
case uint16_t(Op::I64Load16S):
case uint16_t(Op::I64Load16U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 2, nullptr));
case uint16_t(Op::I64Load32S):
case uint16_t(Op::I64Load32U):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 4, nullptr));
case uint16_t(Op::I64Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 8, nullptr));
case uint16_t(Op::F32Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F32, 4, nullptr));
case uint16_t(Op::F64Load):
CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F64, 8, nullptr));
case uint16_t(Op::I32Store8):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 1, nullptr, nullptr));
case uint16_t(Op::I32Store16):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 2, nullptr, nullptr));
case uint16_t(Op::I32Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 4, nullptr, nullptr));
case uint16_t(Op::I64Store8):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 1, nullptr, nullptr));
case uint16_t(Op::I64Store16):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 2, nullptr, nullptr));
case uint16_t(Op::I64Store32):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 4, nullptr, nullptr));
case uint16_t(Op::I64Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 8, nullptr, nullptr));
case uint16_t(Op::F32Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F32, 4, nullptr, nullptr));
case uint16_t(Op::F64Store):
CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F64, 8, nullptr, nullptr));
case uint16_t(Op::GrowMemory):
CHECK(f.checkHasMemory() && f.iter().readGrowMemory(nullptr));
case uint16_t(Op::CurrentMemory):
CHECK(f.checkHasMemory() && f.iter().readCurrentMemory());
case uint16_t(Op::Br):
CHECK(f.iter().readBr(nullptr, nullptr, nullptr));
case uint16_t(Op::BrIf):
CHECK(f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr));
case uint16_t(Op::BrTable):
CHECK(DecodeBrTable(f));
case uint16_t(Op::Return):
CHECK(f.iter().readReturn(nullptr));
case uint16_t(Op::Unreachable):
CHECK(f.iter().readUnreachable());
default:
return f.iter().unrecognizedOpcode(op);
}
}
MOZ_CRASH("unreachable");
#undef CHECK
}
bool
wasm::ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d)
{
ValTypeVector locals;
const Sig& sig = *env.funcSigs[funcIndex];
if (!locals.appendAll(sig.args()))
return false;
if (!DecodeLocalEntries(d, ModuleKind::Wasm, &locals))
return false;
FunctionDecoder f(env, locals, d);
if (!f.iter().readFunctionStart(sig.ret()))
return false;
if (!DecodeFunctionBodyExprs(f))
return false;
return f.iter().readFunctionEnd();
}
// Section macros.
static bool
@ -625,11 +1026,12 @@ DecodeGlobalSection(Decoder& d, GlobalDescVector* globals)
if (!d.readVarU32(&numDefs))
return d.fail("expected number of globals");
uint32_t numGlobals = globals->length() + numDefs;
if (numGlobals > MaxGlobals)
CheckedInt<uint32_t> numGlobals = globals->length();
numGlobals += numDefs;
if (!numGlobals.isValid() || numGlobals.value() > MaxGlobals)
return d.fail("too many globals");
if (!globals->reserve(numGlobals))
if (!globals->reserve(numGlobals.value()))
return false;
for (uint32_t i = 0; i < numDefs; i++) {
@ -894,6 +1296,59 @@ wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env)
return true;
}
static bool
DecodeFunctionBody(Decoder& d, const ModuleEnvironment& env, uint32_t funcIndex)
{
uint32_t bodySize;
if (!d.readVarU32(&bodySize))
return d.fail("expected number of function body bytes");
if (d.bytesRemain() < bodySize)
return d.fail("function body length too big");
const uint8_t* bodyBegin = d.currentPosition();
if (!ValidateFunctionBody(env, funcIndex, d))
return false;
if (d.currentPosition() != bodyBegin + bodySize)
return d.fail("function body length mismatch");
return true;
}
static bool
DecodeCodeSection(Decoder& d, const ModuleEnvironment& env)
{
uint32_t sectionStart, sectionSize;
if (!d.startSection(SectionId::Code, &sectionStart, &sectionSize, "code"))
return false;
if (sectionStart == Decoder::NotStarted) {
if (env.numFuncDefs() != 0)
return d.fail("expected function bodies");
return true;
}
uint32_t numFuncDefs;
if (!d.readVarU32(&numFuncDefs))
return d.fail("expected function body count");
if (numFuncDefs != env.numFuncDefs())
return d.fail("function body count does not match function signature count");
for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
if (!DecodeFunctionBody(d, env, env.numFuncImports() + funcDefIndex))
return false;
}
if (!d.finishSection(sectionStart, sectionSize, "code"))
return false;
return true;
}
bool
wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments)
{
@ -951,5 +1406,30 @@ wasm::DecodeUnknownSections(Decoder& d)
return false;
}
return true;
}
// Validate algorithm.
bool
wasm::Validate(const ShareableBytes& bytecode, UniqueChars* error)
{
Decoder d(bytecode.begin(), bytecode.end(), error);
ModuleEnvironment env;
if (!DecodeModuleEnvironment(d, &env))
return false;
if (!DecodeCodeSection(d, env))
return false;
DataSegmentVector dataSegments;
if (!DecodeDataSection(d, env, &dataSegments))
return false;
if (!DecodeUnknownSections(d))
return false;
return true;
}

View file

@ -16,10 +16,11 @@
* limitations under the License.
*/
#ifndef wasm_binary_format_h
#define wasm_binary_format_h
#ifndef wasm_validate_h
#define wasm_validate_h
#include "wasm/WasmCode.h"
#include "wasm/WasmTypes.h"
namespace js {
namespace wasm {
@ -622,6 +623,13 @@ struct ModuleEnvironment
minMemoryLength(0)
{}
size_t numTables() const {
return tables.length();
}
size_t numSigs() const {
return sigs.length();
}
size_t numFuncs() const {
// asm.js pre-reserves a bunch of function index space which is
// incrementally filled in during function-body validation. Thus, there
@ -637,6 +645,10 @@ struct ModuleEnvironment
MOZ_ASSERT(!isAsmJS());
return funcSigs.length() - funcImportGlobalDataOffsets.length();
}
size_t numFuncImports() const {
MOZ_ASSERT(!isAsmJS());
return funcImportGlobalDataOffsets.length();
}
bool usesMemory() const {
return UsesMemory(memoryUsage);
}
@ -665,6 +677,12 @@ DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* s
MOZ_MUST_USE bool
DecodeUnknownSections(Decoder& d);
[[nodiscard]] bool
ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d);
[[nodiscard]] bool
Validate(const ShareableBytes& bytecode, UniqueChars* error);
} // namespace wasm
} // namespace js