375 lines
10 KiB
Go
375 lines
10 KiB
Go
|
// Copyright 2017 The go-interpreter Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
// package validate provides functions for validating WebAssembly modules.
|
||
|
package validate
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io"
|
||
|
|
||
|
"github.com/go-interpreter/wagon/wasm"
|
||
|
ops "github.com/go-interpreter/wagon/wasm/operators"
|
||
|
)
|
||
|
|
||
|
// vibhavp: TODO: We do not verify whether blocks don't access for the parent block, do that.
|
||
|
func verifyBody(fn *wasm.FunctionSig, body *wasm.FunctionBody, module *wasm.Module) (*mockVM, error) {
|
||
|
vm := &mockVM{
|
||
|
stack: []operand{},
|
||
|
stackTop: 0,
|
||
|
|
||
|
code: bytes.NewReader(body.Code),
|
||
|
origLength: len(body.Code),
|
||
|
|
||
|
polymorphic: false,
|
||
|
blocks: []block{},
|
||
|
curFunc: fn,
|
||
|
}
|
||
|
|
||
|
localVariables := []operand{}
|
||
|
|
||
|
// Paramters count as local variables too
|
||
|
// This comment explains how local variables work: https://github.com/WebAssembly/design/issues/1037#issuecomment-293505798
|
||
|
for _, entry := range fn.ParamTypes {
|
||
|
localVariables = append(localVariables, operand{entry})
|
||
|
}
|
||
|
|
||
|
for _, entry := range body.Locals {
|
||
|
vars := make([]operand, entry.Count)
|
||
|
for i := uint32(0); i < entry.Count; i++ {
|
||
|
vars[i].Type = entry.Type
|
||
|
logger.Printf("Var %v", entry.Type)
|
||
|
}
|
||
|
localVariables = append(localVariables, vars...)
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
op, err := vm.code.ReadByte()
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
} else if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
opStruct, err := ops.New(op)
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
logger.Printf("PC: %d OP: %s polymorphic: %v", vm.pc(), opStruct.Name, vm.isPolymorphic())
|
||
|
|
||
|
if !opStruct.Polymorphic {
|
||
|
if err := vm.adjustStack(opStruct); err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch op {
|
||
|
case ops.If, ops.Block, ops.Loop:
|
||
|
sig, err := vm.fetchVarInt()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
switch wasm.ValueType(sig) {
|
||
|
case wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueType(wasm.BlockTypeEmpty):
|
||
|
vm.pushBlock(op, wasm.BlockType(sig))
|
||
|
default:
|
||
|
if !vm.isPolymorphic() {
|
||
|
return vm, InvalidImmediateError{"block_type", opStruct.Name}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case ops.Else:
|
||
|
block := vm.topBlock()
|
||
|
if block == nil || block.op != ops.If {
|
||
|
return vm, UnmatchedOpError(op)
|
||
|
}
|
||
|
|
||
|
if block.blockType != wasm.BlockTypeEmpty {
|
||
|
top, under := vm.topOperand()
|
||
|
if !vm.isPolymorphic() && (under || top.Type != wasm.ValueType(block.blockType)) {
|
||
|
return vm, InvalidTypeError{wasm.ValueType(block.blockType), top.Type}
|
||
|
}
|
||
|
vm.pushOperand(wasm.ValueType(block.blockType))
|
||
|
}
|
||
|
vm.stackTop = block.stackTop
|
||
|
case ops.End:
|
||
|
isPolymorphic := vm.isPolymorphic()
|
||
|
|
||
|
block := vm.popBlock()
|
||
|
if block == nil {
|
||
|
return vm, UnmatchedOpError(op)
|
||
|
}
|
||
|
|
||
|
if block.blockType != wasm.BlockTypeEmpty {
|
||
|
top, under := vm.topOperand()
|
||
|
if !isPolymorphic && (under || top.Type != wasm.ValueType(block.blockType)) {
|
||
|
return vm, InvalidTypeError{wasm.ValueType(block.blockType), top.Type}
|
||
|
}
|
||
|
vm.stackTop = block.stackTop
|
||
|
vm.pushOperand(wasm.ValueType(block.blockType))
|
||
|
vm.stackTop = block.stackTop + 1 // as we pushed an element
|
||
|
} else {
|
||
|
vm.stackTop = block.stackTop
|
||
|
}
|
||
|
|
||
|
case ops.BrIf, ops.Br:
|
||
|
depth, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
if err = vm.canBranch(int(depth)); !vm.isPolymorphic() && err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
if op == ops.Br {
|
||
|
vm.setPolymorphic()
|
||
|
}
|
||
|
case ops.BrTable:
|
||
|
operand, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && (under || operand.Type != wasm.ValueTypeI32) {
|
||
|
return vm, InvalidTypeError{wasm.ValueTypeI32, operand.Type}
|
||
|
}
|
||
|
// read table entries
|
||
|
targetCount, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
var targetTable []uint32
|
||
|
for i := uint32(0); i < targetCount; i++ {
|
||
|
entry, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
if err = vm.canBranch(int(entry)); !vm.isPolymorphic() && err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
targetTable = append(targetTable, entry)
|
||
|
}
|
||
|
|
||
|
defaultTarget, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
if err = vm.canBranch(int(defaultTarget)); !vm.isPolymorphic() && err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
vm.setPolymorphic()
|
||
|
|
||
|
case ops.Return:
|
||
|
if len(fn.ReturnTypes) > 1 {
|
||
|
panic("not implemented")
|
||
|
}
|
||
|
if len(fn.ReturnTypes) != 0 {
|
||
|
// only single returns supported for now
|
||
|
top, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && (under || top.Type != fn.ReturnTypes[0]) {
|
||
|
return vm, InvalidTypeError{fn.ReturnTypes[0], top.Type}
|
||
|
}
|
||
|
}
|
||
|
vm.setPolymorphic()
|
||
|
|
||
|
case ops.Unreachable:
|
||
|
vm.setPolymorphic()
|
||
|
|
||
|
case ops.I32Const:
|
||
|
_, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
case ops.I64Const:
|
||
|
_, err := vm.fetchVarInt64()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
case ops.F32Const:
|
||
|
_, err := vm.fetchUint32()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
case ops.F64Const:
|
||
|
_, err := vm.fetchUint64()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
case ops.GetLocal, ops.SetLocal, ops.TeeLocal:
|
||
|
i, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
if int(i) >= len(localVariables) {
|
||
|
return vm, InvalidLocalIndexError(i)
|
||
|
}
|
||
|
|
||
|
v := localVariables[i]
|
||
|
|
||
|
if op == ops.GetLocal {
|
||
|
vm.pushOperand(v.Type)
|
||
|
} else { // == set_local or tee_local
|
||
|
top, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && (under || top.Type != v.Type) {
|
||
|
return vm, InvalidTypeError{v.Type, top.Type}
|
||
|
}
|
||
|
if op == ops.TeeLocal {
|
||
|
vm.pushOperand(v.Type)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case ops.GetGlobal, ops.SetGlobal:
|
||
|
index, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
gv := module.GetGlobal(int(index))
|
||
|
if gv == nil {
|
||
|
return vm, wasm.InvalidGlobalIndexError(index)
|
||
|
}
|
||
|
if op == ops.GetGlobal {
|
||
|
vm.pushOperand(gv.Type.Type)
|
||
|
} else {
|
||
|
val, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && (under || val.Type != gv.Type.Type) {
|
||
|
return vm, InvalidTypeError{gv.Type.Type, val.Type}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case ops.I32Load, ops.I64Load, ops.F32Load, ops.F64Load, ops.I32Load8s, ops.I32Load8u, ops.I32Load16s, ops.I32Load16u, ops.I64Load8s, ops.I64Load8u, ops.I64Load16s, ops.I64Load16u, ops.I64Load32s, ops.I64Load32u, ops.I32Store, ops.I64Store, ops.F32Store, ops.F64Store, ops.I32Store8, ops.I32Store16, ops.I64Store8, ops.I64Store16, ops.I64Store32:
|
||
|
// read memory_immediate
|
||
|
// flags
|
||
|
_, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
// offset
|
||
|
_, err = vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
case ops.CurrentMemory, ops.GrowMemory:
|
||
|
_, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
case ops.Call:
|
||
|
index, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
fn := module.GetFunction(int(index))
|
||
|
if fn == nil {
|
||
|
return vm, wasm.InvalidFunctionIndexError(index)
|
||
|
}
|
||
|
|
||
|
logger.Printf("Function being called: %v", fn)
|
||
|
for index := range fn.Sig.ParamTypes {
|
||
|
argType := fn.Sig.ParamTypes[len(fn.Sig.ParamTypes)-index-1]
|
||
|
operand, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && (under || operand.Type != argType) {
|
||
|
return vm, InvalidTypeError{argType, operand.Type}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(fn.Sig.ReturnTypes) > 0 {
|
||
|
vm.pushOperand(fn.Sig.ReturnTypes[0])
|
||
|
}
|
||
|
|
||
|
case ops.CallIndirect:
|
||
|
if module.Table == nil || len(module.Table.Entries) == 0 {
|
||
|
return vm, NoSectionError(wasm.SectionIDTable)
|
||
|
}
|
||
|
// The call_indirect process consists of getting two i32 values
|
||
|
// off (first from the bytecode stream, and the second from
|
||
|
// the stack) and using first as an index into the "Types" section
|
||
|
// of the module, while the the second one into the function index
|
||
|
// space. The signature of the two elements are then compared
|
||
|
// to see if they match, and the call proceeds as normal if they
|
||
|
// do.
|
||
|
// Since this is possible only during program execution, we only
|
||
|
// perform the static check for the function index mentioned
|
||
|
// in the bytecode stream here.
|
||
|
|
||
|
// type index
|
||
|
index, err := vm.fetchVarUint()
|
||
|
if err != nil {
|
||
|
return vm, err
|
||
|
}
|
||
|
|
||
|
fnExpectSig := module.Types.Entries[index]
|
||
|
|
||
|
if operand, under := vm.popOperand(); !vm.isPolymorphic() && (under || operand.Type != wasm.ValueTypeI32) {
|
||
|
return vm, InvalidTypeError{wasm.ValueTypeI32, operand.Type}
|
||
|
}
|
||
|
|
||
|
for index := range fnExpectSig.ParamTypes {
|
||
|
argType := fnExpectSig.ParamTypes[len(fnExpectSig.ParamTypes)-index-1]
|
||
|
operand, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && (under || (operand.Type != argType)) {
|
||
|
return vm, InvalidTypeError{argType, operand.Type}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(fnExpectSig.ReturnTypes) > 0 {
|
||
|
vm.pushOperand(fnExpectSig.ReturnTypes[0])
|
||
|
}
|
||
|
|
||
|
case ops.Drop:
|
||
|
if _, under := vm.popOperand(); !vm.isPolymorphic() && under {
|
||
|
return vm, ErrStackUnderflow
|
||
|
}
|
||
|
|
||
|
case ops.Select:
|
||
|
if vm.isPolymorphic() {
|
||
|
continue
|
||
|
}
|
||
|
operands := make([]operand, 2)
|
||
|
c, under := vm.popOperand()
|
||
|
if under || c.Type != wasm.ValueTypeI32 {
|
||
|
return vm, InvalidTypeError{wasm.ValueTypeI32, c.Type}
|
||
|
}
|
||
|
|
||
|
for i := 0; i < 2; i++ {
|
||
|
operand, under := vm.popOperand()
|
||
|
if !vm.isPolymorphic() && under {
|
||
|
return vm, ErrStackUnderflow
|
||
|
}
|
||
|
operands[i] = operand
|
||
|
}
|
||
|
|
||
|
// last 2 popped values should be of the same type
|
||
|
if operands[0].Type != operands[1].Type {
|
||
|
return vm, InvalidTypeError{operands[1].Type, operands[2].Type}
|
||
|
}
|
||
|
|
||
|
vm.pushOperand(operands[1].Type)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return vm, nil
|
||
|
}
|
||
|
|
||
|
// VerifyModule verifies the given module according to WebAssembly verification
|
||
|
// specs.
|
||
|
func VerifyModule(module *wasm.Module) error {
|
||
|
if module.Function == nil || module.Types == nil || len(module.Types.Entries) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
if module.Code == nil {
|
||
|
return NoSectionError(wasm.SectionIDCode)
|
||
|
}
|
||
|
|
||
|
logger.Printf("There are %d functions", len(module.Function.Types))
|
||
|
for i, fn := range module.FunctionIndexSpace {
|
||
|
if vm, err := verifyBody(fn.Sig, fn.Body, module); err != nil {
|
||
|
return Error{vm.pc(), i, err}
|
||
|
}
|
||
|
logger.Printf("No errors in function %d", i)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|