Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions grpcreplay/binary_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ package grpcreplay
import (
"encoding/binary"
"errors"
"fmt"
"io"

pb "github.com/google/go-replayers/grpcreplay/proto/grpcreplay"
"google.golang.org/protobuf/proto"
)

// maxRecordSize guards against OOM when parsing a crafted replay file.
// gRPC's default max message size is 4 MiB; 64 MiB is a generous upper bound.
const maxRecordSize = 64 << 20 // 64 MiB

type binaryWriter struct {
w io.Writer
}
Expand Down Expand Up @@ -98,6 +103,9 @@ func (r *binaryReader) readRecord() ([]byte, error) {
if err := binary.Read(r.r, binary.LittleEndian, &size); err != nil {
return nil, err
}
if size > maxRecordSize {
return nil, fmt.Errorf("grpcreplay: record size %d exceeds maximum %d", size, maxRecordSize)
}
buf := make([]byte, size)
if _, err := io.ReadFull(r.r, buf); err != nil {
return nil, err
Expand Down
18 changes: 18 additions & 0 deletions grpcreplay/grpcreplay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package grpcreplay
import (
"bytes"
"context"
"encoding/binary"
"errors"
"io"
"reflect"
Expand Down Expand Up @@ -704,3 +705,20 @@ func TestSetInitial(t *testing.T) {
t.Errorf("got initial state %q, want %q", got, want)
}
}

// TestOversizeRecord is a regression test for OOM via unchecked readRecord size.
// A crafted binary replay file with size > maxRecordSize must be rejected with
// an error rather than causing a large allocation.
func TestOversizeRecord(t *testing.T) {
var buf bytes.Buffer
// Write an oversize size field (maxRecordSize + 1).
binary.Write(&buf, binary.LittleEndian, uint32(maxRecordSize+1))

r := &binaryReader{r: &buf}
// readHeader() calls readRecord() internally.
_, err := r.readRecord()
if err == nil {
t.Fatal("readRecord should return an error for size > maxRecordSize")
}
t.Logf("correctly rejected oversized record: %v", err)
}
Loading