land/vendor/github.com/go-interpreter/wagon/validate/validate.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
}