Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion adapters/core2sn/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ func AdaptDeprecatedEntryPoint(ep *core.DeprecatedEntryPoint) starknet.EntryPoin
func AdaptDeprecatedCairoClass(
class *core.DeprecatedCairoClass,
) (starknet.DeprecatedCairoClass, error) {
decompressedProgram, err := compression.Gzip64Decode(class.Program)
decompressedProgram, err := compression.Gzip64Decode(
class.Program, core.MaxDeprecatedClassProgramSize,
)
if err != nil {
return starknet.DeprecatedCairoClass{}, err
}
Expand Down
4 changes: 4 additions & 0 deletions core/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ var (

const minDeclaredClassSize = 8

// MaxDeprecatedClassProgramSize bounds the decompressed size of a deprecated
// Cairo 0 class program. Sequencer caps classes just below this limit.
const MaxDeprecatedClassProgramSize = 4 * db.Megabyte
Comment thread
rodrodros marked this conversation as resolved.
Outdated

// Single felt identifying the number "0.1.0" as a short string
var SierraVersion010 felt.Felt = felt.Felt(
[4]uint64{
Expand Down
4 changes: 3 additions & 1 deletion core/class_hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
)

func deprecatedCairoClassHash(class *DeprecatedCairoClass) (felt.Felt, error) {
decompressedProgram, err := compression.Gzip64Decode(class.Program)
decompressedProgram, err := compression.Gzip64Decode(
class.Program, MaxDeprecatedClassProgramSize,
)
if err != nil {
return felt.Felt{}, err
}
Comment thread
rodrodros marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion rpc/v10/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2285,7 +2285,7 @@ func TestContractClassToGatewayPayload(t *testing.T) {
require.Equal(t, class.EntryPoints, decoded.EntryPoints)
require.Equal(t, class.ABI, decoded.ABI)

sierraJSON, err := compression.Gzip64Decode(decoded.SierraProgram)
sierraJSON, err := compression.Gzip64Decode(decoded.SierraProgram, math.MaxInt64)
require.NoError(t, err, "sierra_program must be gzip+base64 encoded")

var roundTripped []felt.Felt
Expand Down
5 changes: 4 additions & 1 deletion rpc/v8/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@
}
base64Program := string(program[1 : len(program)-1])

feederClass.DeprecatedCairo.Program, err = compression.Gzip64Decode(base64Program)
feederClass.DeprecatedCairo.Program, err = compression.Gzip64Decode(
base64Program,
core.MaxDeprecatedClassProgramSize,
)

Check warning on line 73 in rpc/v8/class.go

View check run for this annotation

Codecov / codecov/patch

rpc/v8/class.go#L70-L73

Added lines #L70 - L73 were not covered by tests
if err != nil {
return nil, err
}
Expand Down
5 changes: 4 additions & 1 deletion rpc/v9/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@
}
base64Program := string(program[1 : len(program)-1])

feederClass.DeprecatedCairo.Program, err = compression.Gzip64Decode(base64Program)
feederClass.DeprecatedCairo.Program, err = compression.Gzip64Decode(
base64Program,
core.MaxDeprecatedClassProgramSize,
)

Check warning on line 79 in rpc/v9/class.go

View check run for this annotation

Codecov / codecov/patch

rpc/v9/class.go#L76-L79

Added lines #L76 - L79 were not covered by tests
if err != nil {
return nil, err
}
Expand Down
22 changes: 20 additions & 2 deletions utils/compression/compression.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"math"
"sync"

"github.com/klauspost/compress/gzip"
Expand Down Expand Up @@ -147,7 +148,7 @@ func Gzip64Encode(data []byte) (string, error) {
return base64.StdEncoding.EncodeToString(compressedBuffer.Bytes()), nil
}

func Gzip64Decode(data string) ([]byte, error) {
func Gzip64Decode(data string, maxDecompressedSize int64) ([]byte, error) {
Comment thread
brbrr marked this conversation as resolved.
Comment thread
rodrodros marked this conversation as resolved.
decodedBytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil, err
Expand All @@ -156,13 +157,30 @@ func Gzip64Decode(data string) ([]byte, error) {
if err != nil {
return nil, err
}
decompressedBytes, err := io.ReadAll(gzipReader)

// We want to be able to read one more byte than the actual limit. This allows diferentiat-
// ing if the decompressed size fits (<= maxDecompressedSize) or if it overflows
// (> maxDecompressedSize)
readLimit := maxDecompressedSize
if readLimit < math.MaxInt64 {
readLimit++
}
Comment thread
rodrodros marked this conversation as resolved.

limited := io.LimitReader(gzipReader, readLimit)
decompressedBytes, err := io.ReadAll(limited)
if err != nil {
return nil, err
}
if int64(len(decompressedBytes)) > maxDecompressedSize {
return nil, fmt.Errorf(
"decompressed data exceeded the maximum byte size: %d", maxDecompressedSize,
)
}

err = gzipReader.Close()
if err != nil {
return nil, err
}
Comment thread
rodrodros marked this conversation as resolved.

return decompressedBytes, nil
}
3 changes: 2 additions & 1 deletion utils/compression/compression_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package compression_test

import (
"bytes"
"math"
"strconv"
"testing"

Expand Down Expand Up @@ -51,7 +52,7 @@ func BenchmarkGzip64Decode(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(size))
for b.Loop() {
if _, err := compression.Gzip64Decode(encoded); err != nil {
if _, err := compression.Gzip64Decode(encoded, math.MaxInt64); err != nil {
b.Fatal(err)
}
}
Expand Down
71 changes: 67 additions & 4 deletions utils/compression/compression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"io"
"math"
"runtime"
"strconv"
"sync"
Expand All @@ -24,16 +25,78 @@ func TestGzip64(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, expectedComBytes, comBytes)

decompBytes, err := compression.Gzip64Decode(comBytes)
decompBytes, err := compression.Gzip64Decode(comBytes, math.MaxInt64)
require.NoError(t, err)
assert.Equal(t, bytes, decompBytes)
}

func TestGzip64Decode(t *testing.T) {
const limit = 1024
tests := []struct {
name string
payload []byte
limit int64
wantErrContains string // empty means the payload is expected to round-trip
}{
{
name: "within limit",
payload: bytes.Repeat([]byte("a"), limit/2),
limit: limit,
},
{
name: "unbounded limit",
payload: []byte{0},
limit: math.MaxInt64,
},
{
name: "empty payload",
payload: []byte{},
limit: limit,
},
{
// The budget is inclusive: a payload that exactly fills it is valid.
name: "exactly at limit",
payload: bytes.Repeat([]byte("a"), limit),
limit: limit,
},
{
// One byte over is the smallest overflow the +1 read must catch.
name: "one byte over limit",
payload: bytes.Repeat([]byte("a"), limit+1),
limit: limit,
wantErrContains: "decompressed data exceeded the maximum byte size:",
},
{
name: "zero limit rejects any content",
payload: []byte("x"),
limit: 0,
wantErrContains: "decompressed data exceeded the maximum byte size:",
},
Comment thread
rodrodros marked this conversation as resolved.
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
encoded, err := compression.Gzip64Encode(test.payload)
require.NoError(t, err)

decoded, err := compression.Gzip64Decode(encoded, test.limit)
if test.wantErrContains != "" {
assert.ErrorContains(t, err, test.wantErrContains)
assert.Nil(t, decoded)
return
}

require.NoError(t, err)
assert.Equal(t, test.payload, decoded)
})
}
}

func FuzzGzip64(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
compressed, err := compression.Gzip64Encode(data)
require.NoError(t, err)
decompressed, err := compression.Gzip64Decode(compressed)
decompressed, err := compression.Gzip64Decode(compressed, math.MaxInt64)
require.NoError(t, err)
assert.Equal(t, data, decompressed)
})
Expand All @@ -50,7 +113,7 @@ func TestGzip64EncodeAcrossSuccessiveCalls(t *testing.T) {

encoded, err := compression.Gzip64Encode(payload)
require.NoError(t, err)
decoded, err := compression.Gzip64Decode(encoded)
decoded, err := compression.Gzip64Decode(encoded, math.MaxInt64)
require.NoError(t, err)
assert.Equal(t, payload, decoded)
})
Expand Down Expand Up @@ -204,7 +267,7 @@ func TestGzip64EncodeConcurrent(t *testing.T) {
payload := bytes.Repeat([]byte{byte('a' + i)}, chunk*(i+1))
encoded, err := compression.Gzip64Encode(payload)
assert.NoError(t, err)
decoded, err := compression.Gzip64Decode(encoded)
decoded, err := compression.Gzip64Decode(encoded, math.MaxInt64)
assert.NoError(t, err)
assert.Equal(t, payload, decoded)
})
Expand Down
Loading