Commit f308efd8 authored by Nigel Tao's avatar Nigel Tao

image/gif: tighten the checks for when the amount of an image's pixel

data does not agree with its bounds.

R=r, jeff.allen
CC=golang-dev
https://golang.org/cl/7938043
parent 0ffdfe42
...@@ -17,6 +17,11 @@ import ( ...@@ -17,6 +17,11 @@ import (
"io" "io"
) )
var (
errNotEnough = errors.New("gif: not enough image data")
errTooMuch = errors.New("gif: too much image data")
)
// If the io.Reader does not also have ReadByte, then decode will introduce its own buffering. // If the io.Reader does not also have ReadByte, then decode will introduce its own buffering.
type reader interface { type reader interface {
io.Reader io.Reader
...@@ -89,29 +94,35 @@ type decoder struct { ...@@ -89,29 +94,35 @@ type decoder struct {
// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the // comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
// reader given to the LZW decoder, which is thus immune to the // reader given to the LZW decoder, which is thus immune to the
// blocking. After the LZW decoder completes, there will be a 0-byte // blocking. After the LZW decoder completes, there will be a 0-byte
// block remaining (0, ()), but under normal execution blockReader // block remaining (0, ()), which is consumed when checking that the
// doesn't consume it, so it is handled in decode. // blockReader is exhausted.
type blockReader struct { type blockReader struct {
r reader r reader
slice []byte slice []byte
err error
tmp [256]byte tmp [256]byte
} }
func (b *blockReader) Read(p []byte) (int, error) { func (b *blockReader) Read(p []byte) (int, error) {
if b.err != nil {
return 0, b.err
}
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
if len(b.slice) == 0 { if len(b.slice) == 0 {
blockLen, err := b.r.ReadByte() var blockLen uint8
if err != nil { blockLen, b.err = b.r.ReadByte()
return 0, err if b.err != nil {
return 0, b.err
} }
if blockLen == 0 { if blockLen == 0 {
return 0, io.EOF b.err = io.EOF
return 0, b.err
} }
b.slice = b.tmp[0:blockLen] b.slice = b.tmp[0:blockLen]
if _, err = io.ReadFull(b.r, b.slice); err != nil { if _, b.err = io.ReadFull(b.r, b.slice); b.err != nil {
return 0, err return 0, b.err
} }
} }
n := copy(p, b.slice) n := copy(p, b.slice)
...@@ -142,35 +153,33 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error { ...@@ -142,35 +153,33 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
} }
} }
Loop: for {
for err == nil { c, err := d.r.ReadByte()
var c byte if err != nil {
c, err = d.r.ReadByte() return err
if err == io.EOF {
break
} }
switch c { switch c {
case sExtension: case sExtension:
err = d.readExtension() if err = d.readExtension(); err != nil {
return err
}
case sImageDescriptor: case sImageDescriptor:
var m *image.Paletted m, err := d.newImageFromDescriptor()
m, err = d.newImageFromDescriptor()
if err != nil { if err != nil {
break return err
} }
if d.imageFields&fColorMapFollows != 0 { if d.imageFields&fColorMapFollows != 0 {
m.Palette, err = d.readColorMap() m.Palette, err = d.readColorMap()
if err != nil { if err != nil {
break return err
} }
// TODO: do we set transparency in this map too? That would be // TODO: do we set transparency in this map too? That would be
// d.setTransparency(m.Palette) // d.setTransparency(m.Palette)
} else { } else {
m.Palette = d.globalColorMap m.Palette = d.globalColorMap
} }
var litWidth uint8 litWidth, err := d.r.ReadByte()
litWidth, err = d.r.ReadByte()
if err != nil { if err != nil {
return err return err
} }
...@@ -178,18 +187,27 @@ Loop: ...@@ -178,18 +187,27 @@ Loop:
return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth) return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth)
} }
// A wonderfully Go-like piece of magic. // A wonderfully Go-like piece of magic.
lzwr := lzw.NewReader(&blockReader{r: d.r}, lzw.LSB, int(litWidth)) br := &blockReader{r: d.r}
lzwr := lzw.NewReader(br, lzw.LSB, int(litWidth))
if _, err = io.ReadFull(lzwr, m.Pix); err != nil { if _, err = io.ReadFull(lzwr, m.Pix); err != nil {
break if err != io.ErrUnexpectedEOF {
return err
} }
return errNotEnough
// There should be a "0" block remaining; drain that. }
c, err = d.r.ReadByte() // Both lzwr and br should be exhausted. Reading from them
// should yield (0, io.EOF).
if n, err := lzwr.Read(d.tmp[:1]); n != 0 || err != io.EOF {
if err != nil { if err != nil {
return err return err
} }
if c != 0 { return errTooMuch
return errors.New("gif: extra data after image") }
if n, err := br.Read(d.tmp[:1]); n != 0 || err != io.EOF {
if err != nil {
return err
}
return errTooMuch
} }
// Undo the interlacing if necessary. // Undo the interlacing if necessary.
...@@ -202,19 +220,15 @@ Loop: ...@@ -202,19 +220,15 @@ Loop:
d.delayTime = 0 // TODO: is this correct, or should we hold on to the value? d.delayTime = 0 // TODO: is this correct, or should we hold on to the value?
case sTrailer: case sTrailer:
break Loop
default:
err = fmt.Errorf("gif: unknown block type: 0x%.2x", c)
}
}
if err != nil {
return err
}
if len(d.image) == 0 { if len(d.image) == 0 {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
return nil return nil
default:
return fmt.Errorf("gif: unknown block type: 0x%.2x", c)
}
}
} }
func (d *decoder) readHeaderAndScreenDescriptor() error { func (d *decoder) readHeaderAndScreenDescriptor() error {
......
package gif
import (
"bytes"
"compress/lzw"
"image"
"image/color"
"reflect"
"testing"
)
func TestDecode(t *testing.T) {
// header and trailer are parts of a valid 2x1 GIF image.
const (
header = "GIF89a" +
"\x02\x00\x01\x00" + // width=2, height=1
"\x80\x00\x00" + // headerFields=(a color map of 2 pixels), backgroundIndex, aspect
"\x10\x20\x30\x40\x50\x60" // the color map, also known as a palette
trailer = "\x3b"
)
// lzwEncode returns an LZW encoding (with 2-bit literals) of n zeroes.
lzwEncode := func(n int) []byte {
b := &bytes.Buffer{}
w := lzw.NewWriter(b, lzw.LSB, 2)
w.Write(make([]byte, n))
w.Close()
return b.Bytes()
}
testCases := []struct {
nPix int // The number of pixels in the image data.
extra bool // Whether to write an extra block after the LZW-encoded data.
wantErr error
}{
{0, false, errNotEnough},
{1, false, errNotEnough},
{2, false, nil},
{2, true, errTooMuch},
{3, false, errTooMuch},
}
for _, tc := range testCases {
b := &bytes.Buffer{}
b.WriteString(header)
// Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
// then this should result in an invalid GIF image. First, write a
// magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
// byte, and 2-bit LZW literals.
b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
if tc.nPix > 0 {
enc := lzwEncode(tc.nPix)
if len(enc) > 0xff {
t.Errorf("nPix=%d, extra=%t: compressed length %d is too large", tc.nPix, tc.extra, len(enc))
continue
}
b.WriteByte(byte(len(enc)))
b.Write(enc)
}
if tc.extra {
b.WriteString("\x01\x02") // A 1-byte payload with an 0x02 byte.
}
b.WriteByte(0x00) // An empty block signifies the end of the image data.
b.WriteString(trailer)
got, err := Decode(b)
if err != tc.wantErr {
t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, err, tc.wantErr)
}
if tc.wantErr != nil {
continue
}
want := &image.Paletted{
Pix: []uint8{0, 0},
Stride: 2,
Rect: image.Rect(0, 0, 2, 1),
Palette: color.Palette{
color.RGBA{0x10, 0x20, 0x30, 0xff},
color.RGBA{0x40, 0x50, 0x60, 0xff},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, got, want)
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment