Skip to content

Commit ab541a0

Browse files
klauspostmdempsky
authored andcommitted
compress/flate: fix corrupted output
The fastest compression mode can pick up a false match for every 2GB of input data resulting in incorrectly decompressed data. Since matches are allowed to be up to and including at maxMatchOffset we must offset the buffer by an additional element to prevent the first 4 bytes to match after an out-of-reach value after shiftOffsets has been called. We offset by `maxMatchOffset + 1` so offset 0 in the table will now fail the `if offset > maxMatchOffset` in all cases. Fixes #41420 Change-Id: If1fbe01728e132b8a207e3f3f439edd832dcc710 GitHub-Last-Rev: 50fabab GitHub-Pull-Request: #41477 Reviewed-on: https://go-review.googlesource.com/c/go/+/255879 Reviewed-by: Matthew Dempsky <[email protected]> Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Joe Tsai <[email protected]> Trust: Matthew Dempsky <[email protected]>
1 parent 06839e3 commit ab541a0

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

src/compress/flate/deflate_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"internal/testenv"
1212
"io"
1313
"io/ioutil"
14+
"math/rand"
1415
"reflect"
1516
"runtime/debug"
1617
"sync"
@@ -896,6 +897,62 @@ func TestBestSpeedMaxMatchOffset(t *testing.T) {
896897
}
897898
}
898899

900+
func TestBestSpeedShiftOffsets(t *testing.T) {
901+
// Test if shiftoffsets properly preserves matches and resets out-of-range matches
902+
// seen in https://github.com/golang/go/issues/4142
903+
enc := newDeflateFast()
904+
905+
// testData may not generate internal matches.
906+
testData := make([]byte, 32)
907+
rng := rand.New(rand.NewSource(0))
908+
for i := range testData {
909+
testData[i] = byte(rng.Uint32())
910+
}
911+
912+
// Encode the testdata with clean state.
913+
// Second part should pick up matches from the first block.
914+
wantFirstTokens := len(enc.encode(nil, testData))
915+
wantSecondTokens := len(enc.encode(nil, testData))
916+
917+
if wantFirstTokens <= wantSecondTokens {
918+
t.Fatalf("test needs matches between inputs to be generated")
919+
}
920+
// Forward the current indicator to before wraparound.
921+
enc.cur = bufferReset - int32(len(testData))
922+
923+
// Part 1 before wrap, should match clean state.
924+
got := len(enc.encode(nil, testData))
925+
if wantFirstTokens != got {
926+
t.Errorf("got %d, want %d tokens", got, wantFirstTokens)
927+
}
928+
929+
// Verify we are about to wrap.
930+
if enc.cur != bufferReset {
931+
t.Errorf("got %d, want e.cur to be at bufferReset (%d)", enc.cur, bufferReset)
932+
}
933+
934+
// Part 2 should match clean state as well even if wrapped.
935+
got = len(enc.encode(nil, testData))
936+
if wantSecondTokens != got {
937+
t.Errorf("got %d, want %d token", got, wantSecondTokens)
938+
}
939+
940+
// Verify that we wrapped.
941+
if enc.cur >= bufferReset {
942+
t.Errorf("want e.cur to be < bufferReset (%d), got %d", bufferReset, enc.cur)
943+
}
944+
945+
// Forward the current buffer, leaving the matches at the bottom.
946+
enc.cur = bufferReset
947+
enc.shiftOffsets()
948+
949+
// Ensure that no matches were picked up.
950+
got = len(enc.encode(nil, testData))
951+
if wantFirstTokens != got {
952+
t.Errorf("got %d, want %d tokens", got, wantFirstTokens)
953+
}
954+
}
955+
899956
func TestMaxStackSize(t *testing.T) {
900957
// This test must not run in parallel with other tests as debug.SetMaxStack
901958
// affects all goroutines.

src/compress/flate/deflatefast.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ func (e *deflateFast) matchLen(s, t int32, src []byte) int32 {
270270
func (e *deflateFast) reset() {
271271
e.prev = e.prev[:0]
272272
// Bump the offset, so all matches will fail distance check.
273+
// Nothing should be >= e.cur in the table.
273274
e.cur += maxMatchOffset
274275

275276
// Protect against e.cur wraparound.
@@ -288,17 +289,21 @@ func (e *deflateFast) shiftOffsets() {
288289
for i := range e.table[:] {
289290
e.table[i] = tableEntry{}
290291
}
291-
e.cur = maxMatchOffset
292+
e.cur = maxMatchOffset + 1
292293
return
293294
}
294295

295296
// Shift down everything in the table that isn't already too far away.
296297
for i := range e.table[:] {
297-
v := e.table[i].offset - e.cur + maxMatchOffset
298+
v := e.table[i].offset - e.cur + maxMatchOffset + 1
298299
if v < 0 {
300+
// We want to reset e.cur to maxMatchOffset + 1, so we need to shift
301+
// all table entries down by (e.cur - (maxMatchOffset + 1)).
302+
// Because we ignore matches > maxMatchOffset, we can cap
303+
// any negative offsets at 0.
299304
v = 0
300305
}
301306
e.table[i].offset = v
302307
}
303-
e.cur = maxMatchOffset
308+
e.cur = maxMatchOffset + 1
304309
}

0 commit comments

Comments
 (0)