Skip to content

Commit cb227cd

Browse files
neildgopherbot
authored andcommitted
tiff: limit work when decoding malicious images
Fix two paths by which a malicious image could cause unreasonable amounts of CPU consumption while decoding. Avoid iterating over every horizontal pixel when decoding a 0-height tiled image. Limit the amount of data that will be decompressed per tile. Thanks to Philippe Antoine (Catena cyber) for reporting this issue. Fixes CVE-2023-29407 Fixes CVE-2023-29408 Fixes golang/go#61581 Fixes golang/go#61582 Change-Id: I8cbb26fa06843c6fe9fa99810cb1315431fa7d1d Reviewed-on: https://go-review.googlesource.com/c/image/+/514897 Reviewed-by: Roland Shoemaker <roland@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Damien Neil <dneil@google.com> Run-TryBot: Damien Neil <dneil@google.com>
1 parent a5392f0 commit cb227cd

File tree

2 files changed

+183
-8
lines changed

2 files changed

+183
-8
lines changed

tiff/reader.go

+28-5
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
package tiff // import "golang.org/x/image/tiff"
99

1010
import (
11+
"bytes"
1112
"compress/zlib"
1213
"encoding/binary"
1314
"fmt"
1415
"image"
1516
"image/color"
1617
"io"
17-
"io/ioutil"
1818
"math"
1919

2020
"golang.org/x/image/ccitt"
@@ -579,6 +579,11 @@ func newDecoder(r io.Reader) (*decoder, error) {
579579
default:
580580
return nil, UnsupportedError("color model")
581581
}
582+
if d.firstVal(tPhotometricInterpretation) != pRGB {
583+
if len(d.features[tBitsPerSample]) != 1 {
584+
return nil, UnsupportedError("extra samples")
585+
}
586+
}
582587

583588
return d, nil
584589
}
@@ -629,6 +634,13 @@ func Decode(r io.Reader) (img image.Image, err error) {
629634
blockWidth = int(d.firstVal(tTileWidth))
630635
blockHeight = int(d.firstVal(tTileLength))
631636

637+
// The specification says that tile widths and lengths must be a multiple of 16.
638+
// We currently permit invalid sizes, but reject anything too small to limit the
639+
// amount of work a malicious input can force us to perform.
640+
if blockWidth < 8 || blockHeight < 8 {
641+
return nil, FormatError("tile size is too small")
642+
}
643+
632644
if blockWidth != 0 {
633645
blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth
634646
}
@@ -681,6 +693,11 @@ func Decode(r io.Reader) (img image.Image, err error) {
681693
}
682694
}
683695

696+
if blocksAcross == 0 || blocksDown == 0 {
697+
return
698+
}
699+
// Maximum data per pixel is 8 bytes (RGBA64).
700+
blockMaxDataSize := int64(blockWidth) * int64(blockHeight) * 8
684701
for i := 0; i < blocksAcross; i++ {
685702
blkW := blockWidth
686703
if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 {
@@ -708,23 +725,23 @@ func Decode(r io.Reader) (img image.Image, err error) {
708725
inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero
709726
order := ccittFillOrder(d.firstVal(tFillOrder))
710727
r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group3, blkW, blkH, &ccitt.Options{Invert: inv, Align: false})
711-
d.buf, err = ioutil.ReadAll(r)
728+
d.buf, err = readBuf(r, d.buf, blockMaxDataSize)
712729
case cG4:
713730
inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero
714731
order := ccittFillOrder(d.firstVal(tFillOrder))
715732
r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group4, blkW, blkH, &ccitt.Options{Invert: inv, Align: false})
716-
d.buf, err = ioutil.ReadAll(r)
733+
d.buf, err = readBuf(r, d.buf, blockMaxDataSize)
717734
case cLZW:
718735
r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
719-
d.buf, err = ioutil.ReadAll(r)
736+
d.buf, err = readBuf(r, d.buf, blockMaxDataSize)
720737
r.Close()
721738
case cDeflate, cDeflateOld:
722739
var r io.ReadCloser
723740
r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n))
724741
if err != nil {
725742
return nil, err
726743
}
727-
d.buf, err = ioutil.ReadAll(r)
744+
d.buf, err = readBuf(r, d.buf, blockMaxDataSize)
728745
r.Close()
729746
case cPackBits:
730747
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
@@ -748,6 +765,12 @@ func Decode(r io.Reader) (img image.Image, err error) {
748765
return
749766
}
750767

768+
func readBuf(r io.Reader, buf []byte, lim int64) ([]byte, error) {
769+
b := bytes.NewBuffer(buf[:0])
770+
_, err := b.ReadFrom(io.LimitReader(r, lim))
771+
return b.Bytes(), err
772+
}
773+
751774
func init() {
752775
image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig)
753776
image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig)

tiff/reader_test.go

+155-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ package tiff
66

77
import (
88
"bytes"
9+
"compress/zlib"
910
"encoding/binary"
1011
"encoding/hex"
1112
"errors"
13+
"fmt"
1214
"image"
1315
"io"
1416
"io/ioutil"
1517
"os"
18+
"sort"
1619
"strings"
1720
"testing"
1821

@@ -414,13 +417,17 @@ func TestLargeIFDEntry(t *testing.T) {
414417
// benchmarkDecode benchmarks the decoding of an image.
415418
func benchmarkDecode(b *testing.B, filename string) {
416419
b.Helper()
417-
b.StopTimer()
418420
contents, err := ioutil.ReadFile(testdataDir + filename)
419421
if err != nil {
420422
b.Fatal(err)
421423
}
422-
r := &buffer{buf: contents}
423-
b.StartTimer()
424+
benchmarkDecodeData(b, contents)
425+
}
426+
427+
func benchmarkDecodeData(b *testing.B, data []byte) {
428+
b.Helper()
429+
r := &buffer{buf: data}
430+
b.ResetTimer()
424431
for i := 0; i < b.N; i++ {
425432
_, err := Decode(r)
426433
if err != nil {
@@ -431,3 +438,148 @@ func benchmarkDecode(b *testing.B, filename string) {
431438

432439
func BenchmarkDecodeCompressed(b *testing.B) { benchmarkDecode(b, "video-001.tiff") }
433440
func BenchmarkDecodeUncompressed(b *testing.B) { benchmarkDecode(b, "video-001-uncompressed.tiff") }
441+
442+
func BenchmarkZeroHeightTile(b *testing.B) {
443+
enc := binary.BigEndian
444+
data := newTIFF(enc)
445+
data = appendIFD(data, enc, map[uint16]interface{}{
446+
tImageWidth: uint32(4294967295),
447+
tImageLength: uint32(0),
448+
tTileWidth: uint32(1),
449+
tTileLength: uint32(0),
450+
})
451+
benchmarkDecodeData(b, data)
452+
}
453+
454+
func BenchmarkRepeatedOversizedTileData(b *testing.B) {
455+
const (
456+
imageWidth = 256
457+
imageHeight = 256
458+
tileWidth = 8
459+
tileLength = 8
460+
numTiles = (imageWidth * imageHeight) / (tileWidth * tileLength)
461+
)
462+
463+
// Create a chunk of tile data that decompresses to a large size.
464+
zdata := func() []byte {
465+
var zbuf bytes.Buffer
466+
zw := zlib.NewWriter(&zbuf)
467+
zeros := make([]byte, 1024)
468+
for i := 0; i < 1<<16; i++ {
469+
zw.Write(zeros)
470+
}
471+
zw.Close()
472+
return zbuf.Bytes()
473+
}()
474+
475+
enc := binary.BigEndian
476+
data := newTIFF(enc)
477+
478+
zoff := len(data)
479+
data = append(data, zdata...)
480+
481+
// Each tile refers to the same compressed data chunk.
482+
var tileoffs []uint32
483+
var tilesizes []uint32
484+
for i := 0; i < numTiles; i++ {
485+
tileoffs = append(tileoffs, uint32(zoff))
486+
tilesizes = append(tilesizes, uint32(len(zdata)))
487+
}
488+
489+
data = appendIFD(data, enc, map[uint16]interface{}{
490+
tImageWidth: uint32(imageWidth),
491+
tImageLength: uint32(imageHeight),
492+
tTileWidth: uint32(tileWidth),
493+
tTileLength: uint32(tileLength),
494+
tTileOffsets: tileoffs,
495+
tTileByteCounts: tilesizes,
496+
tCompression: uint16(cDeflate),
497+
tBitsPerSample: []uint16{16, 16, 16},
498+
tPhotometricInterpretation: uint16(pRGB),
499+
})
500+
benchmarkDecodeData(b, data)
501+
}
502+
503+
type byteOrder interface {
504+
binary.ByteOrder
505+
binary.AppendByteOrder
506+
}
507+
508+
// newTIFF returns the TIFF header.
509+
func newTIFF(enc byteOrder) []byte {
510+
b := []byte{0, 0, 0, 42, 0, 0, 0, 0}
511+
switch enc.Uint16([]byte{1, 0}) {
512+
case 0x1:
513+
b[0], b[1] = 'I', 'I'
514+
case 0x100:
515+
b[0], b[1] = 'M', 'M'
516+
default:
517+
panic("odd byte order")
518+
}
519+
return b
520+
}
521+
522+
// appendIFD appends an IFD to the TIFF in b,
523+
// updating the IFD location in the header.
524+
func appendIFD(b []byte, enc byteOrder, entries map[uint16]interface{}) []byte {
525+
var tags []uint16
526+
for tag := range entries {
527+
tags = append(tags, tag)
528+
}
529+
sort.Slice(tags, func(i, j int) bool {
530+
return tags[i] < tags[j]
531+
})
532+
533+
var ifd []byte
534+
for _, tag := range tags {
535+
ifd = enc.AppendUint16(ifd, tag)
536+
switch v := entries[tag].(type) {
537+
case uint16:
538+
ifd = enc.AppendUint16(ifd, dtShort)
539+
ifd = enc.AppendUint32(ifd, 1)
540+
ifd = enc.AppendUint16(ifd, v)
541+
ifd = enc.AppendUint16(ifd, v)
542+
case uint32:
543+
ifd = enc.AppendUint16(ifd, dtLong)
544+
ifd = enc.AppendUint32(ifd, 1)
545+
ifd = enc.AppendUint32(ifd, v)
546+
case []uint16:
547+
ifd = enc.AppendUint16(ifd, dtShort)
548+
ifd = enc.AppendUint32(ifd, uint32(len(v)))
549+
switch len(v) {
550+
case 0:
551+
ifd = enc.AppendUint32(ifd, 0)
552+
case 1:
553+
ifd = enc.AppendUint16(ifd, v[0])
554+
ifd = enc.AppendUint16(ifd, v[1])
555+
default:
556+
ifd = enc.AppendUint32(ifd, uint32(len(b)))
557+
for _, e := range v {
558+
b = enc.AppendUint16(b, e)
559+
}
560+
}
561+
case []uint32:
562+
ifd = enc.AppendUint16(ifd, dtLong)
563+
ifd = enc.AppendUint32(ifd, uint32(len(v)))
564+
switch len(v) {
565+
case 0:
566+
ifd = enc.AppendUint32(ifd, 0)
567+
case 1:
568+
ifd = enc.AppendUint32(ifd, v[0])
569+
default:
570+
ifd = enc.AppendUint32(ifd, uint32(len(b)))
571+
for _, e := range v {
572+
b = enc.AppendUint32(b, e)
573+
}
574+
}
575+
default:
576+
panic(fmt.Errorf("unhandled type %T", v))
577+
}
578+
}
579+
580+
enc.PutUint32(b[4:8], uint32(len(b)))
581+
b = enc.AppendUint16(b, uint16(len(entries)))
582+
b = append(b, ifd...)
583+
b = enc.AppendUint32(b, 0)
584+
return b
585+
}

0 commit comments

Comments
 (0)