fuzz: enable imports in differential fuzzing (#886)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -19,9 +19,7 @@ fn run(data: &[u8]) -> Result<()> {
|
||||
config.memory64_enabled = false;
|
||||
// Also, multiple memories are not supported.
|
||||
config.max_memories = 1;
|
||||
// TODO: after having CompiledModule.Imports(), enable this with dummy imports.
|
||||
// See https://github.com/bytecodealliance/wasmtime/blob/f242975c49385edafe4f72dfa5f0ff6aae23eda3/crates/fuzzing/src/oracles/dummy.rs#L6-L20
|
||||
config.max_imports = 0;
|
||||
config.max_imports = 10;
|
||||
// If we don't set the limit, we will soon reach the OOM and the fuzzing will be killed by OS.
|
||||
config.max_memory_pages = 10;
|
||||
config.memory_max_size_required = true;
|
||||
|
||||
@@ -2,7 +2,9 @@ package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -10,6 +12,8 @@ import (
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wasm/binary"
|
||||
)
|
||||
|
||||
// require_no_diff ensures that the behavior is the same between the compiler and the interpreter for any given binary.
|
||||
@@ -47,6 +51,30 @@ func require_no_diff(binaryPtr uintptr, binarySize int, watPtr uintptr, watSize
|
||||
return
|
||||
}
|
||||
|
||||
// We haven't had public APIs for referencing all the imported entries from wazero.CompiledModule,
|
||||
// so we use the unsafe.Pointer and the internal memory layout to get the internal *wasm.Module
|
||||
// from wazero.CompiledFunction. This must be synced with the struct definition of wazero.compiledModule (internal one).
|
||||
func extractInternalWasmModuleFromCompiledModule(c wazero.CompiledModule) (*wasm.Module, error) {
|
||||
// This is the internal representation of interface in Go.
|
||||
// https://research.swtch.com/interfaces
|
||||
type iface struct {
|
||||
tp *byte
|
||||
data unsafe.Pointer
|
||||
}
|
||||
|
||||
// This corresponds to the unexported wazero.compiledModule to get *wasm.Module from wazero.CompiledModule interface.
|
||||
type compiledModule struct {
|
||||
module *wasm.Module
|
||||
}
|
||||
|
||||
ciface := (*iface)(unsafe.Pointer(&c))
|
||||
if ciface == nil {
|
||||
return nil, errors.New("invalid pointer")
|
||||
}
|
||||
cm := (*compiledModule)(ciface.data)
|
||||
return cm.module, nil
|
||||
}
|
||||
|
||||
// requireNoDiff ensures that the behavior is the same between the compiler and the interpreter for any given binary.
|
||||
func requireNoDiff(wasmBin []byte, requireNoError func(err error)) {
|
||||
// Choose the context to use for function calls.
|
||||
@@ -54,19 +82,28 @@ func requireNoDiff(wasmBin []byte, requireNoError func(err error)) {
|
||||
|
||||
compiler := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler())
|
||||
interpreter := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
|
||||
defer compiler.Close(ctx)
|
||||
defer interpreter.Close(ctx)
|
||||
|
||||
compiledCompiled, err := compiler.CompileModule(ctx, wasmBin)
|
||||
compilerCompiled, err := compiler.CompileModule(ctx, wasmBin)
|
||||
requireNoError(err)
|
||||
|
||||
interpreterCompiled, err := interpreter.CompileModule(ctx, wasmBin)
|
||||
requireNoError(err)
|
||||
|
||||
internalMod, err := extractInternalWasmModuleFromCompiledModule(compilerCompiled)
|
||||
requireNoError(err)
|
||||
|
||||
if skip := ensureDummyImports(compiler, internalMod, requireNoError); skip {
|
||||
return
|
||||
}
|
||||
ensureDummyImports(interpreter, internalMod, requireNoError)
|
||||
|
||||
// Instantiate module.
|
||||
compilerMod, compilerInstErr := compiler.InstantiateModule(ctx, compiledCompiled, wazero.NewModuleConfig())
|
||||
interpreterMod, interpreterInstErr := interpreter.InstantiateModule(ctx, interpreterCompiled, wazero.NewModuleConfig())
|
||||
compilerMod, compilerInstErr := compiler.InstantiateModule(ctx, compilerCompiled,
|
||||
wazero.NewModuleConfig().WithName(string(internalMod.ID[:])))
|
||||
interpreterMod, interpreterInstErr := interpreter.InstantiateModule(ctx, interpreterCompiled,
|
||||
wazero.NewModuleConfig().WithName(string(internalMod.ID[:])))
|
||||
|
||||
okToInvoke, err := ensureInstantiationError(compilerInstErr, interpreterInstErr)
|
||||
requireNoError(err)
|
||||
@@ -77,6 +114,108 @@ func requireNoDiff(wasmBin []byte, requireNoError func(err error)) {
|
||||
}
|
||||
}
|
||||
|
||||
// ensureDummyImports instantiates the modules which are required imports by `origin` *wasm.Module.
|
||||
func ensureDummyImports(r wazero.Runtime, origin *wasm.Module, requireNoError func(err error)) (skip bool) {
|
||||
impMods := make(map[string][]*wasm.Import)
|
||||
for _, imp := range origin.ImportSection {
|
||||
impMods[imp.Module] = append(impMods[imp.Module], imp)
|
||||
}
|
||||
|
||||
for mName, impMod := range impMods {
|
||||
usedName := make(map[string]struct{}, len(impMod))
|
||||
m := &wasm.Module{NameSection: &wasm.NameSection{ModuleName: mName}}
|
||||
|
||||
for _, imp := range impMod {
|
||||
_, ok := usedName[imp.Name]
|
||||
if ok {
|
||||
// Import segment can have duplicated "{module_name}.{name}" pair while it is prohibited for exports.
|
||||
// Decision on allowing modules with these "ill" imports or not is up to embedder, and wazero chooses
|
||||
// not to allow. Hence, we skip the entire case.
|
||||
// See "Note" at https://www.w3.org/TR/wasm-core-2/syntax/modules.html#imports
|
||||
return true
|
||||
} else {
|
||||
usedName[imp.Name] = struct{}{}
|
||||
}
|
||||
|
||||
var index uint32
|
||||
switch imp.Type {
|
||||
case wasm.ExternTypeFunc:
|
||||
tp := origin.TypeSection[imp.DescFunc]
|
||||
typeIdx := uint32(len(m.TypeSection))
|
||||
index = uint32(len(m.FunctionSection))
|
||||
m.FunctionSection = append(m.FunctionSection, typeIdx)
|
||||
m.TypeSection = append(m.TypeSection, tp)
|
||||
body := bytes.NewBuffer(nil)
|
||||
for _, vt := range tp.Results {
|
||||
switch vt {
|
||||
case wasm.ValueTypeI32:
|
||||
body.WriteByte(wasm.OpcodeI32Const)
|
||||
body.WriteByte(0)
|
||||
case wasm.ValueTypeI64:
|
||||
body.WriteByte(wasm.OpcodeI64Const)
|
||||
body.WriteByte(0)
|
||||
case wasm.ValueTypeF32:
|
||||
body.Write([]byte{wasm.OpcodeF32Const, 0, 0, 0, 0})
|
||||
case wasm.ValueTypeF64:
|
||||
body.Write([]byte{wasm.OpcodeF64Const, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
case wasm.ValueTypeV128:
|
||||
body.Write([]byte{
|
||||
wasm.OpcodeVecPrefix, wasm.OpcodeVecV128Const,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
})
|
||||
case wasm.ValueTypeExternref:
|
||||
body.Write([]byte{wasm.OpcodeRefNull, wasm.RefTypeExternref})
|
||||
case wasm.ValueTypeFuncref:
|
||||
body.Write([]byte{wasm.OpcodeRefNull, wasm.RefTypeFuncref})
|
||||
}
|
||||
}
|
||||
body.WriteByte(wasm.OpcodeEnd)
|
||||
m.CodeSection = append(m.CodeSection, &wasm.Code{Body: body.Bytes()})
|
||||
case wasm.ExternTypeGlobal:
|
||||
index = uint32(len(m.GlobalSection))
|
||||
var data []byte
|
||||
var opcode byte
|
||||
switch imp.DescGlobal.ValType {
|
||||
case wasm.ValueTypeI32:
|
||||
opcode = wasm.OpcodeI32Const
|
||||
data = []byte{0}
|
||||
case wasm.ValueTypeI64:
|
||||
opcode = wasm.OpcodeI64Const
|
||||
data = []byte{0}
|
||||
case wasm.ValueTypeF32:
|
||||
opcode = wasm.OpcodeF32Const
|
||||
data = []byte{0, 0, 0, 0}
|
||||
case wasm.ValueTypeF64:
|
||||
opcode = wasm.OpcodeF64Const
|
||||
data = []byte{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
case wasm.ValueTypeV128:
|
||||
opcode = wasm.OpcodeVecPrefix
|
||||
data = []byte{wasm.OpcodeVecV128Const, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
case wasm.ValueTypeExternref:
|
||||
opcode = wasm.OpcodeRefNull
|
||||
data = []byte{wasm.RefTypeExternref}
|
||||
case wasm.ValueTypeFuncref:
|
||||
opcode = wasm.OpcodeRefNull
|
||||
data = []byte{wasm.RefTypeFuncref}
|
||||
}
|
||||
m.GlobalSection = append(m.GlobalSection, &wasm.Global{
|
||||
Type: imp.DescGlobal, Init: &wasm.ConstantExpression{Opcode: opcode, Data: data},
|
||||
})
|
||||
case wasm.ExternTypeMemory:
|
||||
m.MemorySection = imp.DescMem
|
||||
index = 0
|
||||
case wasm.ExternTypeTable:
|
||||
index = uint32(len(m.TableSection))
|
||||
m.TableSection = append(m.TableSection, imp.DescTable)
|
||||
}
|
||||
m.ExportSection = append(m.ExportSection, &wasm.Export{Type: imp.Type, Name: imp.Name, Index: index})
|
||||
}
|
||||
_, err := r.InstantiateModuleFromBinary(context.Background(), binary.EncodeModule(m))
|
||||
requireNoError(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const valueTypeVector = 0x7b
|
||||
|
||||
// ensureInvocationResultMatch invokes all the exported functions from the module, and compare all the results between compiler vs interpreter.
|
||||
|
||||
Reference in New Issue
Block a user