golang/go

x/image/tiff: excessive memory consumption

dvyukov opened this issue · 6 comments

The following program:

package main

import (
    "bytes"
    "fmt"
    "golang.org/x/image/tiff"
)

func main() {
    cfg, err := tiff.DecodeConfig(bytes.NewReader(data))
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", cfg)
    tiff.Decode(bytes.NewReader(data))
}

var data = []byte(
    "II*\x00\xc8\x03\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\x88\x88\x88\x00\x00\x00\x00dRH=\xd2eca\xf0" +
        "SSR\xe91*#\xe27/&\xe6<<<$\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\xa2\xa2\xa3\x03/.-\x9c\x99\x87r\xff\xbd\xab\x95\xff" +
        "\xff\xff\xff\xff\x84\x86\x88\xff\x80q_\xffp`O\xffbbc<" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00" +
        "eee\x00Z[\\\x00\x1e\x1a\x16\xab\xb9\xb8\xb8\xff\x95\x94\x92\xff" +
        "}m\\\xff\xbd\xbb\xb9\xff\xc4\xc0\xbc\xff\xc1\xaa\x8f\xffū\x8e\xff" +
        "@=;x``a\x00\\\\\\\x00\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\x02\x02\x01A\x1a\x16\x11\xa8~o]\xff\xf1\xf0\xef\xff" +
        "\xc9\xcb\xcd\xff;72\xffD9,\xff\xa9\x93z\xff\u05fb\x9c\xff" +
        "\xde¢\xff`TF\xda\x12\x13\x15\x02@@@\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\x0e\f\nz?7.穓{\xff" +
        "\xbb\xa8\x93\xff\xbb\xab\x99\xff\x86ua\xfftpl\xff\xb5\xa0\x87\xff" +
        \x9a\xffӹ\x9b\xff\xb7\x9f\x84\xff:98mYZZ\x00" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x05\x00\x00\x002" +
        "i\\M\xeb\xdfá\xffҷ\x97\xffӹ\x9a\xff\xb6\xa2\x8b\xff" +
        \x95\xffӹ\x9a\xffԹ\x9a\xffۿ\x9f\xfftj^\xff" +
        "\r\x0f\x10\x87\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00" +
        "\x00\x00\x00\x00\x15\x12\x0f\x93ʹ\x96\xffҸ\x9a\xffж\x98\xff" +
        \x9a\xffӸ\x9a\xff\xc0\xa9\x8f\xff\xb7\xa2\x8b\xffۿ\x9f\xff" +
        "\x87vd\xff\x00\x00\x00<\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10cVH\xdfۿ\xa0\xff" +
        \x98\xffж\x98\xffպ\x9b\xff\x9c\x8bw\xff\x91\x87{\xff" +
        \x9b\xff\x9d\x8as\xff\x00\x00\x00)\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x03\x03W" +
        "\xb9\xa2\x88\xffӹ\x9a\xffж\x98\xffж\x98\xffϴ\x96\xff" +
        \x96\xffֻ\x9d\xff\xa6\x91y\xff\x00\x00\x004\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
        "\x00\x00\x00\x12|m[\xf5ڿ\x9f\xffж\x98\xffж\x98\xff" +
        \x98\xffж\x98\xffֻ\x9d\xff\xa1\x8dv\xff\x00\x00\x000" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
        "\x00\x00\x00\x00\x00\x00\x00\x03dXI\xe8\xdc\xc1\xa1\xffж\x98\xff" +
        \x98\xffж\x98\xffж\x98\xffڿ\xa0\xff\x88vc\xfc" +
        "\x00\x00\x00\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00###\x00" +
        "###\x00%%%\x00\x02\x03\x05\x03gZK\xe8\xdd\xc1\xa1\xff" +
        \x98\xffж\x98\xffж\x98\xffӸ\x9a\xffܿ\x9f\xff" +
        "vmb\xff\v\f\x0ee\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\xec\xec\xec\x00\xec\xec\xec\x00\xf9\xf9\xf9\x00qrs\x0e:1'\xf1" +
        "\xe1Ť\xffպ\x9b\xffؽ\x9e\xff\xdd¢\xffж\x98\xff" +
        "qcT\xeb-..\x8bAABK\xff\xff\xff\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00&&&\x00&&&\x00(((\x00\x10\x10\x10\f" +
        "\x00\x00\x00cxk]\U000378cd\xff\x9d\x88p\xffpbR\xec" +
        " \x1c\x16\xa0\x1f\x1f\x1f ~~~\x00\xd9\xd9\xd9\x00\xff\xff\xff\x00" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
        "\x00\x00\x00\x00\x00\x00\x00\x00344\xbeFFF\xb5OOOA" +
        "\x00\x00\x00\x11\x00\x00\x00\x00!!!\x00\u007f\u007f\u007f\x00\xd9\xd9\xd9\x00" +
        "\xff\xff\xff\x00\xff\xff\xff\x00\f\x00\x00\x01\x03\x00\x01\x00\x00\x00\x10\x00" +
        "\x00\x00\x01\x01\x03\x00\x01\x00\x00\x00\x0f\x00\x00\x00\x02\x01\x03\x00\x04\x00" +
        "\x00\x00^\x04\x00\x00\x06\x01\x03\x00\x01\x00\x00\x00\x02\x00\x00\x00\x11\x01" +
        "\x04\x00\x01\x00\x00\x00\b\x00\x00\x00\x15\x01\x03\x00\x01\x00\x00\x00\x04\x00" +
        "\x00\x00\x16\x01\x03\x00\x01\x00\x00\x00\x0f\x00\x00\x00\x17\x01\x04\x00\x01\x00" +
        "\x00\x00\xfa\x00\x00\xfa\x1a\x01\x05\x00\x01\x00\x00\x00f\x04\x00\x00\x1b\x01" +
        "\x05\x00\x01\x00\x00\x00n\x04\x00\x00(\x01\x03\x00\x01\x00\x00\x00\x02\x00" +
        "\x00\x00R\x01\x03\x00\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\b\x00" +
        "\b\x00\b\x00\b\x00")

when run with ulimit -v 1000000 crashes as:

{ColorModel:0xc820032030 Width:16 Height:15}
fatal error: runtime: out of memory
runtime.mallocgc(0xfa0000fa, 0x4fd060, 0x1, 0xc820034c00)
    src/runtime/malloc.go:632 +0x972 fp=0xc820045590 sp=0xc8200454c0
runtime.newarray(0x4fd060, 0xfa0000fa, 0x7fdc0fc12498)
    src/runtime/malloc.go:756 +0xc9 fp=0xc8200455d0 sp=0xc820045590
runtime.makeslice(0x4f5b60, 0xfa0000fa, 0xfa0000fa, 0x0, 0x0, 0x0)
    src/runtime/slice.go:32 +0x165 fp=0xc820045620 sp=0xc8200455d0
golang.org/x/image/tiff.Decode(0x7fdc0fc12260, 0xc820014420, 0x7fdc0fc12498, 0xc820010440, 0x0, 0x0)
    src/golang.org/x/image/tiff/reader.go:641 +0xc23 fp=0xc820045e10 sp=0xc820045620
main.main()
    tiff.go:15 +0x31c fp=0xc820045f50 sp=0xc820045e10

That is, tiff tries to allocate 0xfa0000fa (4194304250) bytes to decode 15x16 image. That is too much.

on commit eb11b45157c1b71f30b3cec66306f1cd779a689e
go version devel +3cab476 Sun Jun 21 03:11:01 2015 +0000 linux/amd64

Another similar case is when IFD offset is large, so tiff/buffer fills the buf till the offset, which can consumer a lot of memory if tiff-file is big.

Commit: 63626fb251ce5d89650d28bc5d6ccd7d63a70fef
go version go1.11.1 linux/amd64

This effectively CVE-2022-41727 ignored for 8 years... the magic of CVEs...

@dvyukov this CVE was in tiff.DecodeConfig not Decode as in your stack trace...

(Decode can allocate arbitrary amounts of memory, especially png which use u32 for dimensions.)

Humm... this reproducer is also fixed by the commit.

"This makes DecodeConfig safe to use to determine if the image is of a reasonable size to call Decode on"
Image in this issue is 15x16, so it looks safe to call Decode on, right? So this issue is the same vector, in the same code, fixed by the same commit at least :)

Indeed.

I guess the last replacement of ReadAt fixes your case and the first one fixed my case