Skip to content

Commit

Permalink
Bulk memory binary encoding and validation (#494)
Browse files Browse the repository at this point in the history
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Co-authored-by: Adrian Cole <adrian@tetrate.io>
  • Loading branch information
mathetake and Adrian Cole authored Apr 25, 2022
1 parent 45ff2fe commit ca36651
Show file tree
Hide file tree
Showing 11 changed files with 619 additions and 11 deletions.
5 changes: 5 additions & 0 deletions internal/wasm/binary/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ func DecodeModule(binary []byte, enabledFeatures wasm.Features, memoryMaxPages u
m.CodeSection, err = decodeCodeSection(r)
case wasm.SectionIDData:
m.DataSection, err = decodeDataSection(r)
case wasm.SectionIDDataCount:
if err := enabledFeatures.Require(wasm.FeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("data count section not supported as %v", err)
}
m.DataCountSection, err = decodeDataCountSection(r)
default:
err = ErrInvalidSectionID
}
Expand Down
6 changes: 6 additions & 0 deletions internal/wasm/binary/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ func TestDecodeModule(t *testing.T) {
require.NoError(t, e)
require.Equal(t, &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}}, m)
})
t.Run("data count section disabled", func(t *testing.T) {
input := append(append(Magic, version...),
wasm.SectionIDDataCount, 1, 0)
_, e := DecodeModule(input, wasm.Features20191205, wasm.MemoryMaxPages)
require.EqualError(t, e, `data count section not supported as feature "bulk-memory-operations" is disabled`)
})
}

func TestDecodeModule_Errors(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions internal/wasm/binary/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package binary
import (
"bytes"
"fmt"
"io"

"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
Expand Down Expand Up @@ -168,6 +169,15 @@ func decodeDataSection(r *bytes.Reader) ([]*wasm.DataSegment, error) {
return result, nil
}

func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) {
v, _, err := leb128.DecodeUint32(r)
if err != nil && err != io.EOF {
// data count is optional, so EOF is fine.
return nil, err
}
return &v, nil
}

// encodeSection encodes the sectionID, the size of its contents in bytes, followed by the contents.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
func encodeSection(sectionID wasm.SectionID, contents []byte) []byte {
Expand Down
13 changes: 13 additions & 0 deletions internal/wasm/binary/section_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,16 @@ func TestEncodeFunctionSection(t *testing.T) {
func TestEncodeStartSection(t *testing.T) {
require.Equal(t, []byte{wasm.SectionIDStart, 0x01, 0x05}, encodeStartSection(5))
}

func TestDecodeDataCountSection(t *testing.T) {
t.Run("ok", func(t *testing.T) {
v, err := decodeDataCountSection(bytes.NewReader([]byte{0x1}))
require.NoError(t, err)
require.Equal(t, uint32(1), *v)
})
t.Run("eof", func(t *testing.T) {
// EOF is fine as the datacount is optional.
_, err := decodeDataCountSection(bytes.NewReader([]byte{}))
require.NoError(t, err)
})
}
33 changes: 25 additions & 8 deletions internal/wasm/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,32 @@ const (

// FeatureNonTrappingFloatToIntConversion decides if parsing should succeed on the following instructions:
//
// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF32S]
// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF32U]
// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF32S]
// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF32U]
// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF64S]
// * [OpcodeMiscPrefix, OpcodeMiscI32TruncSatF64U]
// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF64S]
// * [OpcodeMiscPrefix, OpcodeMiscI64TruncSatF64U]
// * [ OpcodeMiscPrefix, OpcodeMiscI32TruncSatF32S]
// * [ OpcodeMiscPrefix, OpcodeMiscI32TruncSatF32U]
// * [ OpcodeMiscPrefix, OpcodeMiscI64TruncSatF32S]
// * [ OpcodeMiscPrefix, OpcodeMiscI64TruncSatF32U]
// * [ OpcodeMiscPrefix, OpcodeMiscI32TruncSatF64S]
// * [ OpcodeMiscPrefix, OpcodeMiscI32TruncSatF64U]
// * [ OpcodeMiscPrefix, OpcodeMiscI64TruncSatF64S]
// * [ OpcodeMiscPrefix, OpcodeMiscI64TruncSatF64U]
//
// See https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
FeatureNonTrappingFloatToIntConversion

// FeatureBulkMemoryOperations decides if parsing should succeed on the following instructions:
//
// * [ OpcodeMiscPrefix, OpcodeMiscMemoryInit]
// * [ OpcodeMiscPrefix, OpcodeMiscDataDrop]
// * [ OpcodeMiscPrefix, OpcodeMiscMemoryCopy]
// * [ OpcodeMiscPrefix, OpcodeMiscMemoryFill]
// * [ OpcodeMiscPrefix, OpcodeMiscTableInit]
// * [ OpcodeMiscPrefix, OpcodeMiscElemDrop]
// * [ OpcodeMiscPrefix, OpcodeMiscTableCopy]
//
// Also, if the parsing should succeed with the presence of SectionIDDataCount.
//
//See https://github.com/WebAssembly/spec/blob/main/proposals/bulk-memory-operations/Overview.md
FeatureBulkMemoryOperations
)

// Set assigns the value for the given feature.
Expand Down Expand Up @@ -111,6 +126,8 @@ func featureName(f Features) string {
case FeatureNonTrappingFloatToIntConversion:
// match https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md
return "nontrapping-float-to-int-conversion"
case FeatureBulkMemoryOperations:
return "bulk-memory-operations"
}
return ""
}
2 changes: 1 addition & 1 deletion internal/wasm/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestFeatures_String(t *testing.T) {
{name: "multi-value", feature: FeatureMultiValue, expected: "multi-value"},
{name: "features", feature: FeatureMutableGlobal | FeatureMultiValue, expected: "mutable-global|multi-value"},
{name: "undefined", feature: 1 << 63, expected: ""},
{name: "all", feature: FeaturesFinished, expected: "mutable-global|sign-extension-ops|multi-value|nontrapping-float-to-int-conversion"},
{name: "all", feature: FeaturesFinished, expected: "mutable-global|sign-extension-ops|multi-value|nontrapping-float-to-int-conversion|bulk-memory-operations"},
}

for _, tt := range tests {
Expand Down
93 changes: 93 additions & 0 deletions internal/wasm/func_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,99 @@ func (m *Module) validateFunctionWithMaxStackValues(
return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
}
valueTypeStack.push(outType)
} else if miscOpcode >= OpcodeMiscMemoryInit && miscOpcode <= OpcodeMiscTableCopy {
if err := enabledFeatures.Require(FeatureBulkMemoryOperations); err != nil {
return fmt.Errorf("%s invalid as %v", miscInstructionNames[miscOpcode], err)
}
var params []ValueType
switch miscOpcode {
case OpcodeMiscMemoryInit, OpcodeMiscMemoryCopy, OpcodeMiscMemoryFill, OpcodeMiscDataDrop:
if memory == nil {
return fmt.Errorf("memory must exist for %s", MiscInstructionName(miscOpcode))
}
if miscOpcode != OpcodeMiscDataDrop {
params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
}

if miscOpcode == OpcodeMiscMemoryInit || miscOpcode == OpcodeMiscDataDrop {
if m.DataCountSection == nil {
return fmt.Errorf("%s requires data count section", MiscInstructionName(miscOpcode))
}

// We need to read the index to the data section.
pc++
index, num, err := leb128.DecodeUint32(bytes.NewReader(body[pc:]))
if err != nil {
return fmt.Errorf("failed to read data segment index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if int(index) >= len(m.DataSection) {
return fmt.Errorf("index %d out of range of data section(len=%d)", index, len(m.DataSection))
}
pc += num - 1
}

if miscOpcode != OpcodeMiscDataDrop {
pc++
val, num, err := leb128.DecodeUint32(bytes.NewReader(body[pc:]))
if err != nil {
return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if val != 0 || num != 1 {
return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode))
}
if miscOpcode == OpcodeMiscMemoryCopy {
pc++
// memory.copy needs two memory index which are reserved as zero.
val, num, err := leb128.DecodeUint32(bytes.NewReader(body[pc:]))
if err != nil {
return fmt.Errorf("failed to read memory index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if val != 0 || num != 1 {
return fmt.Errorf("%s reserved byte must be zero encoded with 1 byte", MiscInstructionName(miscOpcode))
}
}
}
case OpcodeMiscTableInit, OpcodeMiscElemDrop, OpcodeMiscTableCopy:
if table == nil {
return fmt.Errorf("table must exist for %s", MiscInstructionName(miscOpcode))
}
if miscOpcode != OpcodeMiscElemDrop {
params = []ValueType{ValueTypeI32, ValueTypeI32, ValueTypeI32}
}
if miscOpcode == OpcodeMiscTableInit || miscOpcode == OpcodeMiscElemDrop {
pc++
index, num, err := leb128.DecodeUint32(bytes.NewReader(body[pc:]))
if err != nil {
return fmt.Errorf("failed to read element segment index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if int(index) >= len(m.ElementSection) {
return fmt.Errorf("index %d out of range of element section(len=%d)", index, len(m.ElementSection))
}
pc += num - 1
}

if miscOpcode != OpcodeMiscElemDrop {
pc++
_, num, err := leb128.DecodeUint32(bytes.NewReader(body[pc:]))
if err != nil {
return fmt.Errorf("failed to read table index for %s: %v", MiscInstructionName(miscOpcode), err)
}
if miscOpcode == OpcodeMiscTableCopy {
pc += num
// table.copy needs two table index which are reserved as zero.
_, num, err := leb128.DecodeUint32(bytes.NewReader(body[pc:]))
if err != nil {
return fmt.Errorf("failed to read table index for %s: %v", MiscInstructionName(miscOpcode), err)
}
pc += num - 1
}
}
}
for _, p := range params {
if err := valueTypeStack.popAndVerifyType(p); err != nil {
return fmt.Errorf("cannot pop the operand for %s: %v", miscInstructionNames[miscOpcode], err)
}
}
}
} else if op == OpcodeBlock {
bt, num, err := DecodeBlockType(types, bytes.NewReader(body[pc+1:]), enabledFeatures)
Expand Down
Loading

0 comments on commit ca36651

Please sign in to comment.