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
- Before dispatch, reject
sectionSize > remaining for every section and pass only buf[offset:offset+int(sectionSize)] to the section decoder.
- 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.
- 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.
- 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.
Problem
The component decoder allocates slices directly from untrusted
u32vector counts before proving that the enclosing section contains enough bytes for that many entries. Examples include: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
0xffffffffreachesmake([]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
sectionSize > remainingfor every section and pass onlybuf[offset:offset+int(sectionSize)]to the section decoder.uint32 -> intconversion andcount * sizeof(T)overflow before allocation.Regression tests
For every vector-bearing section and nested descriptor, feed a minimal payload containing count
0xffffffffand assertDecodereturns a typed error without panicking or materially allocating. Also fuzz: