Skip to content

Commit 4775b7f

Browse files
horghnigeltao
authored andcommitted
image/gif: handle an extra data sub-block byte.
This changes the decoder's behaviour when there is stray/extra data found after an image is decompressed (e.g., data sub-blocks after an LZW End of Information Code). Instead of raising an error, we silently skip over such data until we find the end of the image data marked by a Block Terminator. We skip at most one byte as sample problem GIFs exhibit this property. GIFs should not have and do not need such stray data (though the specification is arguably ambiguous). However GIFs with such properties have been seen in the wild. Fixes #16146 Change-Id: Ie7e69052bab5256b4834992304e6ca58e93c1879 Reviewed-on: https://go-review.googlesource.com/37258 Reviewed-by: Nigel Tao <[email protected]> Run-TryBot: Nigel Tao <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 9b15c13 commit 4775b7f

File tree

2 files changed

+62
-19
lines changed

2 files changed

+62
-19
lines changed

src/image/gif/reader.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
231231
}
232232
return errNotEnough
233233
}
234-
// Both lzwr and br should be exhausted. Reading from them should
235-
// yield (0, io.EOF).
234+
// In theory, both lzwr and br should be exhausted. Reading from them
235+
// should yield (0, io.EOF).
236236
//
237237
// The spec (Appendix F - Compression), says that "An End of
238238
// Information code... must be the last code output by the encoder
@@ -248,11 +248,21 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
248248
}
249249
return errTooMuch
250250
}
251-
if n, err := br.Read(d.tmp[:1]); n != 0 || err != io.EOF {
251+
252+
// In practice, some GIFs have an extra byte in the data sub-block
253+
// stream, which we ignore. See https://golang.org/issue/16146.
254+
for nExtraBytes := 0; ; {
255+
n, err := br.Read(d.tmp[:2])
256+
nExtraBytes += n
257+
if nExtraBytes > 1 {
258+
return errTooMuch
259+
}
260+
if err == io.EOF {
261+
break
262+
}
252263
if err != nil {
253264
return fmt.Errorf("gif: reading image data: %v", err)
254265
}
255-
return errTooMuch
256266
}
257267

258268
// Check that the color indexes are inside the palette.

src/image/gif/reader_test.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,35 @@ func lzwEncode(in []byte) []byte {
3737
}
3838

3939
func TestDecode(t *testing.T) {
40+
// extra contains superfluous bytes to inject into the GIF, either at the end
41+
// of an existing data sub-block (past the LZW End of Information code) or in
42+
// a separate data sub-block. The 0x02 values are arbitrary.
43+
const extra = "\x02\x02\x02\x02"
44+
4045
testCases := []struct {
41-
nPix int // The number of pixels in the image data.
42-
extra bool // Whether to write an extra block after the LZW-encoded data.
43-
wantErr error
46+
nPix int // The number of pixels in the image data.
47+
// If non-zero, write this many extra bytes inside the data sub-block
48+
// containing the LZW end code.
49+
extraExisting int
50+
// If non-zero, write an extra block of this many bytes.
51+
extraSeparate int
52+
wantErr error
4453
}{
45-
{0, false, errNotEnough},
46-
{1, false, errNotEnough},
47-
{2, false, nil},
48-
{2, true, errTooMuch},
49-
{3, false, errTooMuch},
54+
{0, 0, 0, errNotEnough},
55+
{1, 0, 0, errNotEnough},
56+
{2, 0, 0, nil},
57+
// An extra data sub-block after the compressed section with 1 byte which we
58+
// silently skip.
59+
{2, 0, 1, nil},
60+
// An extra data sub-block after the compressed section with 2 bytes. In
61+
// this case we complain that there is too much data.
62+
{2, 0, 2, errTooMuch},
63+
// Too much pixel data.
64+
{3, 0, 0, errTooMuch},
65+
// An extra byte after LZW data, but inside the same data sub-block.
66+
{2, 1, 0, nil},
67+
// Two extra bytes after LZW data, but inside the same data sub-block.
68+
{2, 2, 0, nil},
5069
}
5170
for _, tc := range testCases {
5271
b := &bytes.Buffer{}
@@ -59,22 +78,35 @@ func TestDecode(t *testing.T) {
5978
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
6079
if tc.nPix > 0 {
6180
enc := lzwEncode(make([]byte, tc.nPix))
62-
if len(enc) > 0xff {
63-
t.Errorf("nPix=%d, extra=%t: compressed length %d is too large", tc.nPix, tc.extra, len(enc))
81+
if len(enc)+tc.extraExisting > 0xff {
82+
t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large",
83+
tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc))
6484
continue
6585
}
66-
b.WriteByte(byte(len(enc)))
86+
87+
// Write the size of the data sub-block containing the LZW data.
88+
b.WriteByte(byte(len(enc) + tc.extraExisting))
89+
90+
// Write the LZW data.
6791
b.Write(enc)
92+
93+
// Write extra bytes inside the same data sub-block where LZW data
94+
// ended. Each arbitrarily 0x02.
95+
b.WriteString(extra[:tc.extraExisting])
6896
}
69-
if tc.extra {
70-
b.WriteString("\x01\x02") // A 1-byte payload with an 0x02 byte.
97+
98+
if tc.extraSeparate > 0 {
99+
// Data sub-block size. This indicates how many extra bytes follow.
100+
b.WriteByte(byte(tc.extraSeparate))
101+
b.WriteString(extra[:tc.extraSeparate])
71102
}
72103
b.WriteByte(0x00) // An empty block signifies the end of the image data.
73104
b.WriteString(trailerStr)
74105

75106
got, err := Decode(b)
76107
if err != tc.wantErr {
77-
t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, err, tc.wantErr)
108+
t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
109+
tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr)
78110
}
79111

80112
if tc.wantErr != nil {
@@ -90,7 +122,8 @@ func TestDecode(t *testing.T) {
90122
},
91123
}
92124
if !reflect.DeepEqual(got, want) {
93-
t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, got, want)
125+
t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
126+
tc.nPix, tc.extraExisting, tc.extraSeparate, got, want)
94127
}
95128
}
96129
}

0 commit comments

Comments
 (0)