Skip to content

Commit 5880338

Browse files
author
Stevie Johnstone
committed
Be robust against malformed inputs to Decode
There were a few places where malformed input could result in index out of range panics. Added unit tests for these failures and fixed the issues.
1 parent 3b4b52f commit 5880338

File tree

2 files changed

+82
-8
lines changed

2 files changed

+82
-8
lines changed

snappy.go

+36-8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,22 @@ package snappy
33
import (
44
"bytes"
55
"encoding/binary"
6+
"errors"
67

78
master "github.com/golang/snappy"
89
)
910

10-
var xerialHeader = []byte{130, 83, 78, 65, 80, 80, 89, 0}
11+
const (
12+
sizeOffset = 16
13+
sizeBytes = 4
14+
)
15+
16+
var (
17+
xerialHeader = []byte{130, 83, 78, 65, 80, 80, 89, 0}
18+
// ErrMalformed is returned by the decoder when the xerial framing
19+
// is malformed
20+
ErrMalformed = errors.New("malformed xerial framing")
21+
)
1122

1223
// Encode encodes data as snappy with no framing header.
1324
func Encode(src []byte) []byte {
@@ -17,26 +28,43 @@ func Encode(src []byte) []byte {
1728
// Decode decodes snappy data whether it is traditional unframed
1829
// or includes the xerial framing format.
1930
func Decode(src []byte) ([]byte, error) {
31+
var max = len(src)
32+
if max < len(xerialHeader) {
33+
return nil, ErrMalformed
34+
}
35+
2036
if !bytes.Equal(src[:8], xerialHeader) {
2137
return master.Decode(nil, src)
2238
}
2339

40+
if max < sizeOffset+sizeBytes {
41+
return nil, ErrMalformed
42+
}
43+
2444
var (
25-
pos = uint32(16)
26-
max = uint32(len(src))
45+
pos = sizeOffset
2746
dst = make([]byte, 0, len(src))
2847
chunk []byte
2948
err error
3049
)
31-
for pos < max {
32-
size := binary.BigEndian.Uint32(src[pos : pos+4])
33-
pos += 4
3450

35-
chunk, err = master.Decode(chunk, src[pos:pos+size])
51+
for pos+sizeBytes <= max {
52+
size := int(binary.BigEndian.Uint32(src[pos : pos+sizeBytes]))
53+
pos += sizeBytes
54+
55+
nextPos := pos + size
56+
// On architectures where int is 32-bytes wide size + pos could
57+
// overflow so we need to check the low bound as well as the
58+
// high
59+
if nextPos < pos || nextPos > max {
60+
return nil, ErrMalformed
61+
}
62+
63+
chunk, err = master.Decode(chunk, src[pos:nextPos])
3664
if err != nil {
3765
return nil, err
3866
}
39-
pos += size
67+
pos = nextPos
4068
dst = append(dst, chunk...)
4169
}
4270
return dst, nil

snappy_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,49 @@ func TestSnappyDecodeStreams(t *testing.T) {
4747
}
4848
}
4949
}
50+
51+
func TestSnappyDecodeMalformedTruncatedHeader(t *testing.T) {
52+
// Truncated headers should not cause a panic.
53+
for i := 0; i < len(xerialHeader); i++ {
54+
buf := make([]byte, i)
55+
copy(buf, xerialHeader[:i])
56+
if _, err := Decode(buf); err != ErrMalformed {
57+
t.Errorf("expected ErrMalformed got %v", err)
58+
}
59+
}
60+
}
61+
62+
func TestSnappyDecodeMalformedTruncatedSize(t *testing.T) {
63+
// Inputs with valid Xerial header but truncated "size" field
64+
sizes := []int{sizeOffset + 1, sizeOffset + 2, sizeOffset + 3}
65+
for _, size := range sizes {
66+
buf := make([]byte, size)
67+
copy(buf, xerialHeader)
68+
if _, err := Decode(buf); err != ErrMalformed {
69+
t.Errorf("expected ErrMalformed got %v", err)
70+
}
71+
}
72+
}
73+
74+
func TestSnappyDecodeMalformedBNoData(t *testing.T) {
75+
// No data after the size field
76+
buf := make([]byte, 20)
77+
copy(buf, xerialHeader)
78+
// indicate that there's one byte of data to be read
79+
buf[len(buf)-1] = 1
80+
if _, err := Decode(buf); err != ErrMalformed {
81+
t.Errorf("expected ErrMalformed got %v", err)
82+
}
83+
}
84+
85+
func TestSnappyMasterDecodeFailed(t *testing.T) {
86+
buf := make([]byte, 21)
87+
copy(buf, xerialHeader)
88+
// indicate that there's one byte of data to be read
89+
buf[len(buf)-2] = 1
90+
// A payload which will not decode
91+
buf[len(buf)-1] = 1
92+
if _, err := Decode(buf); err == ErrMalformed || err == nil {
93+
t.Errorf("unexpected err: %v", err)
94+
}
95+
}

0 commit comments

Comments
 (0)