Skip to content

Bound decoder vector counts before allocating to prevent tiny-input OOM/panic #6

Description

@JairusSW

Problem

The component decoder allocates slices directly from untrusted u32 vector counts before proving that the enclosing section contains enough bytes for that many entries. Examples include:

count, n, err := leb128.LoadUint32(buf[offset:])
...
types := make([]Type, count)

and the same pattern for imports, exports, aliases, component/core instances, instantiate args, inline exports, canonical definitions, and nested descriptor vectors.

Relevant implementation: https://github.com/wago-org/component-model/blob/8a61139958e559a0ae61c3f49f6e9f33fddb6a95/internal/binary/decoder.go

The outer decoder passes the full remaining component buffer into section decoders rather than a slice capped to sectionSize; the section-size equality check occurs only after the decoder has walked and allocated the vector.

Impact

A tiny malformed component can force an enormous allocation or a runtime panic before validation returns an error. For example, a type section whose payload is only the five-byte LEB encoding of 0xffffffff reaches make([]Type, 4294967295) immediately. On a 64-bit host this attempts a huge allocation and can terminate the process; on narrower targets it can panic due to an out-of-range length.

Because component bytes are guest-controlled input to Service.Instantiate, this is a host denial-of-service boundary rather than merely a malformed-file diagnostic.

Suggested fix

  1. Before dispatch, reject sectionSize > remaining for every section and pass only buf[offset:offset+int(sectionSize)] to the section decoder.
  2. Add a shared bounded-vector helper that:
    • decodes the count from the section-local slice;
    • rejects counts that exceed an implementation-wide object limit;
    • rejects counts greater than the maximum possible entries given the remaining bytes and that vector's minimum encoded element size;
    • checks uint32 -> int conversion and count * sizeof(T) overflow before allocation.
  3. Apply the same discipline recursively to record fields, variant cases, function params/results, instance exports/args, flags/enums, canon options, and all other nested vectors.
  4. Consider a total decoder allocation/complexity budget so a structurally valid component with many small vectors cannot exhaust the host.

Regression tests

For every vector-bearing section and nested descriptor, feed a minimal payload containing count 0xffffffff and assert Decode returns a typed error without panicking or materially allocating. Also fuzz:

  • declared section sizes smaller than the count encoding/body;
  • counts just above the section's byte-derived maximum;
  • multiplication/conversion boundaries on 32-bit and 64-bit builds;
  • deeply nested components and nested type vectors under a total budget.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions