Skip to content

Commit

Permalink
Strictly Limit the size of decompressed values to 1 MiB
Browse files Browse the repository at this point in the history
The default message size limit in GossipSub is 1 MiB, which is unchanged
in Lotus. This means when decompressing values, we can never have a
valid compressed message that expands to larger than 1 MiB.

Set this limit explicitly in the zstd decoder.
  • Loading branch information
masih committed Jan 24, 2025
1 parent e771bca commit 13487e2
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 1 deletion.
12 changes: 11 additions & 1 deletion internal/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package encoding

import (
"bytes"
"fmt"

"github.com/klauspost/compress/zstd"
cbg "github.com/whyrusleeping/cbor-gen"
)

// maxDecompressedSize is the default maximum amount of memory allocated by the
// zstd decoder. The limit of 1MiB is chosen based on the default maximum message
// size in GossipSub.
const maxDecompressedSize = 1 << 20

type CBORMarshalUnmarshaler interface {
cbg.CBORMarshaler
cbg.CBORUnmarshaler
Expand Down Expand Up @@ -47,7 +53,7 @@ func NewZSTD[T CBORMarshalUnmarshaler]() (*ZSTD[T], error) {
if err != nil {
return nil, err
}

Check warning on line 55 in internal/encoding/encoding.go

View check run for this annotation

Codecov / codecov/patch

internal/encoding/encoding.go#L54-L55

Added lines #L54 - L55 were not covered by tests
reader, err := zstd.NewReader(nil)
reader, err := zstd.NewReader(nil, zstd.WithDecoderMaxMemory(maxDecompressedSize))
if err != nil {
return nil, err
}

Check warning on line 59 in internal/encoding/encoding.go

View check run for this annotation

Codecov / codecov/patch

internal/encoding/encoding.go#L58-L59

Added lines #L58 - L59 were not covered by tests
Expand All @@ -60,6 +66,10 @@ func NewZSTD[T CBORMarshalUnmarshaler]() (*ZSTD[T], error) {

func (c *ZSTD[T]) Encode(m T) ([]byte, error) {
cborEncoded, err := c.cborEncoding.Encode(m)
if len(cborEncoded) > maxDecompressedSize {
// Error out early if the encoded value is too large to be decompressed.
return nil, fmt.Errorf("encoded value cannot exceed maximum size: %d > %d", len(cborEncoded), maxDecompressedSize)
}

Check warning on line 72 in internal/encoding/encoding.go

View check run for this annotation

Codecov / codecov/patch

internal/encoding/encoding.go#L70-L72

Added lines #L70 - L72 were not covered by tests
if err != nil {
return nil, err
}

Check warning on line 75 in internal/encoding/encoding.go

View check run for this annotation

Codecov / codecov/patch

internal/encoding/encoding.go#L74-L75

Added lines #L74 - L75 were not covered by tests
Expand Down
24 changes: 24 additions & 0 deletions internal/encoding/encoding_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package encoding_test

import (
"bytes"
"io"
"testing"

"github.com/filecoin-project/go-f3/internal/encoding"
"github.com/klauspost/compress/zstd"
"github.com/stretchr/testify/require"
cbg "github.com/whyrusleeping/cbor-gen"
)
Expand Down Expand Up @@ -53,3 +55,25 @@ func TestZSTD(t *testing.T) {
require.NoError(t, err)
require.Equal(t, data.Value, decoded.Value)
}

func TestZSTDLimits(t *testing.T) {
subject, err := encoding.NewZSTD[*testValue]()
require.NoError(t, err)

writer, err := zstd.NewWriter(nil)
require.NoError(t, err)

var v testValue
v.Value = string(make([]byte, cbg.ByteArrayMaxLen*2))

var buf bytes.Buffer
require.NoError(t, v.MarshalCBOR(&buf))

tooLargeACompression := writer.EncodeAll(buf.Bytes(), nil)
// Assert the compressed size is less than 1MiB, in other words, transportable by
// the default GossipSub message size limit.
require.Less(t, len(tooLargeACompression), 1<<20)

var dest testValue
require.ErrorContains(t, subject.Decode(tooLargeACompression, &dest), "decompressed size exceeds configured limit")
}

0 comments on commit 13487e2

Please sign in to comment.