fix(go/grpc): avoid panic on short FlatBuffers input (#8684)

* fix(go/grpc): avoid panic on short FlatBuffers input

The gRPC codec read the root UOffsetT without checking input size. On
buffers shorter than SizeUOffsetT, GetUint32 touched data[3] and the
process panics.

Add a simple length check and validate the root offset stays within the
buffer. Return clear errors (insufficient data / invalid root offset)
instead of panicking.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

* fix(go/grpc): avoid signed overflow in offset

Keep the bounds check in the unsigned domain (UOffsetT) to avoid
signedness pitfalls.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

* chore: clarify comment regarding offset

A full FlatBuffer structure would be:

- uoffset_t: root table offset (4 bytes)
- soffset_t: vtable offset in root table (4 bytes)
- uint16_t: vtable size (2 bytes)
- uint16_t: table size (2 bytes)

In total 12 bytes. We are only validating the data length
before trying to read the uoffset_t, not the full structure.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

---------

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
Co-authored-by: Derek Bailey <derekbailey@google.com>
This commit is contained in:
Ville Vesilehto
2025-08-28 10:31:57 +03:00
committed by GitHub
parent 6164edf558
commit 1e6c851dba

View File

@@ -1,7 +1,17 @@
package flatbuffers
import "errors"
var (
// Codec implements gRPC-go Codec which is used to encode and decode messages.
var Codec = "flatbuffers"
Codec = "flatbuffers"
// ErrInsufficientData is returned when the data is too short to read the root UOffsetT.
ErrInsufficientData = errors.New("insufficient data")
// ErrInvalidRootOffset is returned when the root UOffsetT is out of bounds.
ErrInvalidRootOffset = errors.New("invalid root offset")
)
// FlatbuffersCodec defines the interface gRPC uses to encode and decode messages. Note
// that implementations of this interface must be thread safe; a Codec's
@@ -15,7 +25,21 @@ func (FlatbuffersCodec) Marshal(v interface{}) ([]byte, error) {
// Unmarshal parses the wire format into v.
func (FlatbuffersCodec) Unmarshal(data []byte, v interface{}) error {
v.(flatbuffersInit).Init(data, GetUOffsetT(data))
// Need at least 4 bytes to read the root table offset (UOffsetT).
// Vtable soffset_t and metadata are read later during field access.
if len(data) < SizeUOffsetT {
return ErrInsufficientData
}
off := GetUOffsetT(data)
// The root UOffsetT must be within the data buffer
// Compare in the unsigned domain to avoid signedness pitfalls
if off > UOffsetT(len(data)-SizeUOffsetT) {
return ErrInvalidRootOffset
}
v.(flatbuffersInit).Init(data, off)
return nil
}