Skip to content
Open
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
9 changes: 8 additions & 1 deletion pkg/handler/processor/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,18 @@ func decodeDocument(ctx context.Context, i *processor.Document) error {
return nil
}

// maxDecompressedSize caps how many bytes a document may decompress to, so a
// small blob cannot expand without bound (a decompression bomb).
var maxDecompressedSize int64 = 1 << 30 // 1 GiB

func decompressDocument(i *processor.Document, reader io.Reader) error {
uncompressed, err := io.ReadAll(reader)
uncompressed, err := io.ReadAll(io.LimitReader(reader, maxDecompressedSize+1))
if err != nil {
return fmt.Errorf("unable to decompress document: %w", err)
}
if int64(len(uncompressed)) > maxDecompressedSize {
return fmt.Errorf("decompressed document exceeds the %d byte limit", maxDecompressedSize)
}
i.Blob = uncompressed
return nil
}
49 changes: 49 additions & 0 deletions pkg/handler/processor/process/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/guacsec/guac/pkg/handler/processor"
"github.com/guacsec/guac/pkg/handler/processor/guesser"
"github.com/guacsec/guac/pkg/logging"
"github.com/klauspost/compress/zstd"
)

func Test_SimpleDocProcessTest(t *testing.T) {
Expand Down Expand Up @@ -852,3 +853,51 @@ func testPublish(ctx context.Context, d *processor.Document, blobStore *blob.Blo
logger.Debugf("doc published: %+v", d.SourceInformation.Source)
return nil
}

func zstdOfZeros(t *testing.T, size int) []byte {
t.Helper()
var buf bytes.Buffer
enc, err := zstd.NewWriter(&buf)
if err != nil {
t.Fatal(err)
}
chunk := make([]byte, 1<<16)
for written := 0; written < size; written += len(chunk) {
if _, err := enc.Write(chunk); err != nil {
t.Fatal(err)
}
}
if err := enc.Close(); err != nil {
t.Fatal(err)
}
return buf.Bytes()
}

func Test_DecompressDocumentLimit(t *testing.T) {
orig := maxDecompressedSize
maxDecompressedSize = 1 << 20 // 1 MiB for the test
defer func() { maxDecompressedSize = orig }()

t.Run("decompression bomb is rejected", func(t *testing.T) {
bomb := zstdOfZeros(t, 5<<20)
doc := &processor.Document{Blob: bomb, Encoding: processor.EncodingZstd}
err := decodeDocument(context.Background(), doc)
if err == nil {
t.Fatalf("expected error for over-limit decompression, got nil (blob=%d bytes)", len(doc.Blob))
}
if !strings.Contains(err.Error(), "limit") {
t.Fatalf("expected limit error, got: %v", err)
}
})

t.Run("document under the limit still decodes", func(t *testing.T) {
small := zstdOfZeros(t, 256<<10)
doc := &processor.Document{Blob: small, Encoding: processor.EncodingZstd}
if err := decodeDocument(context.Background(), doc); err != nil {
t.Fatalf("unexpected error for under-limit document: %v", err)
}
if len(doc.Blob) != 256<<10 {
t.Fatalf("expected 262144 bytes decoded, got %d", len(doc.Blob))
}
})
}
Loading