gographics / imagick

Go binding to ImageMagick's MagickWand C API
https://godoc.org/github.com/gographics/imagick/imagick
Other
1.76k stars 183 forks source link

MagickCoreSignature assertion while CoalesceImages #169

Open CMogilko opened 6 years ago

CMogilko commented 6 years ago

Hi, I've got panic by SIGABRT while making ThumbnailImage() with CoalesceImages() and gif-files.

magick/profile.c:120: CloneImageProfiles: Assertion `clone_image->signature == MagickCoreSignature' failed.
SIGABRT: abort

It seems that image blob was corrupted.

My environment: OS: fedora 28 Go: go version go1.10.3 linux/amd64 ImageMagick: ImageMagick-6.9.9.38-1.fc28.x86_64

Panic stacktrace:

test: magick/profile.c:120: CloneImageProfiles: Assertion `clone_image->signature == MagickCoreSignature' failed.
SIGABRT: abort
PC=0x7ffa02ed2eab m=33 sigcode=18446744073709551610

goroutine 0 [idle]:
runtime: unknown pc 0x7ffa02ed2eab
stack: frame={sp:0x7ff9a0bf29f0, fp:0x0} stack=[0x7ff9a03f31d0,0x7ff9a0bf2dd0)
00007ff9a0bf28f0:  000000000000000c  00007ff964000920 
00007ff9a0bf2900:  00000000000000b0  00000000000000c0 
00007ff9a0bf2910:  0000000007fffff7  40e0000000000000 
00007ff9a0bf2920:  4059000000000000  4059000000000000 
00007ff9a0bf2930:  0000000000000000  0000000000000000 
00007ff9a0bf2940:  0000000000000000  00007ffa009859b2 
00007ff9a0bf2950:  0000000d00000001  00007ff900000001 
00007ff9a0bf2960:  0000000000000000  0000000000000008 
00007ff9a0bf2970:  40b1110000000000  40a4140000000000 
00007ff9a0bf2980:  40a8180000000000  0000000000000000 
00007ff9a0bf2990:  0000000000000000  0000000000000003 
00007ff9a0bf29a0:  0000000d00000001  00007ff900000001 
00007ff9a0bf29b0:  0000000000000000  0000000000000008 
00007ff9a0bf29c0:  3032310000000000  705138f2a6326200 
00007ff9a0bf29d0:  00007ff9a0bf2a10  0000000000000072 
00007ff9a0bf29e0:  0000000000000072  00007ff9a0bf2c50 
00007ff9a0bf29f0: <0000000000000000  00007ff9652d5ec0 
00007ff9a0bf2a00:  00007ff9a0bf2ce0  00007ffa02f147b7 
00007ff9a0bf2a10:  00000000fbad8000  00007ff9652d5ec0 
00007ff9a0bf2a20:  00007ff9652d5f25  00007ff9652d5ec0 
00007ff9a0bf2a30:  00007ff9652d5ec0  00007ff9652d5f32 
00007ff9a0bf2a40:  00007ff9652d5fec  00007ff9652d5ec0 
00007ff9a0bf2a50:  00007ff9652d5fec  0000000000000000 
00007ff9a0bf2a60:  0000000000000000  0000000000000000 
00007ff9a0bf2a70:  fffffffe7fffffff  ffffffffffffffff 
00007ff9a0bf2a80:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2a90:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2aa0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ab0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ac0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ad0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ae0:  ffffffffffffffff  ffffffffffffffff 
runtime: unknown pc 0x7ffa02ed2eab
stack: frame={sp:0x7ff9a0bf29f0, fp:0x0} stack=[0x7ff9a03f31d0,0x7ff9a0bf2dd0)
00007ff9a0bf28f0:  000000000000000c  00007ff964000920 
00007ff9a0bf2900:  00000000000000b0  00000000000000c0 
00007ff9a0bf2910:  0000000007fffff7  40e0000000000000 
00007ff9a0bf2920:  4059000000000000  4059000000000000 
00007ff9a0bf2930:  0000000000000000  0000000000000000 
00007ff9a0bf2940:  0000000000000000  00007ffa009859b2 
00007ff9a0bf2950:  0000000d00000001  00007ff900000001 
00007ff9a0bf2960:  0000000000000000  0000000000000008 
00007ff9a0bf2970:  40b1110000000000  40a4140000000000 
00007ff9a0bf2980:  40a8180000000000  0000000000000000 
00007ff9a0bf2990:  0000000000000000  0000000000000003 
00007ff9a0bf29a0:  0000000d00000001  00007ff900000001 
00007ff9a0bf29b0:  0000000000000000  0000000000000008 
00007ff9a0bf29c0:  3032310000000000  705138f2a6326200 
00007ff9a0bf29d0:  00007ff9a0bf2a10  0000000000000072 
00007ff9a0bf29e0:  0000000000000072  00007ff9a0bf2c50 
00007ff9a0bf29f0: <0000000000000000  00007ff9652d5ec0 
00007ff9a0bf2a00:  00007ff9a0bf2ce0  00007ffa02f147b7 
00007ff9a0bf2a10:  00000000fbad8000  00007ff9652d5ec0 
00007ff9a0bf2a20:  00007ff9652d5f25  00007ff9652d5ec0 
00007ff9a0bf2a30:  00007ff9652d5ec0  00007ff9652d5f32 
00007ff9a0bf2a40:  00007ff9652d5fec  00007ff9652d5ec0 
00007ff9a0bf2a50:  00007ff9652d5fec  0000000000000000 
00007ff9a0bf2a60:  0000000000000000  0000000000000000 
00007ff9a0bf2a70:  fffffffe7fffffff  ffffffffffffffff 
00007ff9a0bf2a80:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2a90:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2aa0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ab0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ac0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ad0:  ffffffffffffffff  ffffffffffffffff 
00007ff9a0bf2ae0:  ffffffffffffffff  ffffffffffffffff 

goroutine 486 [syscall]:
runtime.cgocall(0x4acfd0, 0xc4203ebe88, 0x454faf)
    /usr/lib/golang/src/runtime/cgocall.go:128 +0x64 fp=0xc4203ebe58 sp=0xc4203ebe20 pc=0x418734
gopkg.in/gographics/imagick.v2/imagick._Cfunc_MagickCoalesceImages(0x7ff9652e8d50, 0x0)
    _cgo_gotypes.go:4037 +0x4a fp=0xc4203ebe88 sp=0xc4203ebe58 pc=0x4a63aa
gopkg.in/gographics/imagick.v2/imagick.(*MagickWand).CoalesceImages.func1(0x7ff9652e8d50, 0x3)
    /root/fe/gopath/src/gopkg.in/gographics/imagick.v2/imagick/magick_wand_image.go:357 +0x56 fp=0xc4203ebec0 sp=0xc4203ebe88 pc=0x4a80e6
gopkg.in/gographics/imagick.v2/imagick.(*MagickWand).CoalesceImages(0xc4200a8180, 0x3)
    /root/fe/gopath/src/gopkg.in/gographics/imagick.v2/imagick/magick_wand_image.go:357 +0x2e fp=0xc4203ebee0 sp=0xc4203ebec0 pc=0x4a76fe
main.(*ImageMagick).Resize(0x791fe0, 0xc42758a000, 0x1780ac, 0x1782ac, 0x1782ac, 0x0, 0x0, 0x0, 0x0)
    /root/fe/gopath/src/test/main.go:112 +0x2d8 fp=0xc4203ebf40 sp=0xc4203ebee0 pc=0x4aab48
main.do.func1(0xc426b24190, 0x4fbe80, 0x791fe0, 0x4f0253, 0x44)
    /root/fe/gopath/src/test/main.go:66 +0xfd fp=0xc4203ebfb8 sp=0xc4203ebf40 pc=0x4aadbd
runtime.goexit()
    /usr/lib/golang/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc4203ebfc0 sp=0xc4203ebfb8 pc=0x4669b1
created by main.do
    /root/fe/gopath/src/test/main.go:58 +0xf3

Test tool for reproducing, it may be required to run tool up to 10 times to get panic. Gif is attached to issue.

package main

import (
    "fmt"
    "gopkg.in/gographics/imagick.v2/imagick"
    "io/ioutil"
    "runtime"
    "strings"
    "sync"
)

func main() {
    do()
}

func do() {
    p, err := NewImageMagick()
    if err != nil {
        fmt.Println(err)
        return
    }

    files := []string{
        "1411808383_1369456619-ae4fe668748eb6af81bdf35a2d9a13ad.gif",
        "1411808383_1369456619-ae4fe668748eb6af81bdf35a2d9a13ad.gif",
        "1411808383_1369456619-ae4fe668748eb6af81bdf35a2d9a13ad.gif",
        "1411808383_1369456619-ae4fe668748eb6af81bdf35a2d9a13ad.gif",
        "1411808383_1369456619-ae4fe668748eb6af81bdf35a2d9a13ad.gif",
    }

    for k := 0; k < 10; k++ {
        wg := &sync.WaitGroup{}
        for j := 0; j < 10; j++ {

            for i := range files {
                wg.Add(1)
                go func(file string) {
                    defer wg.Done()
                    raw, err := ioutil.ReadFile(file)
                    if err != nil {
                        fmt.Println(err)
                        return
                    }

                    _, err = p.Resize(raw)
                    if err != nil {
                        fmt.Println(err)
                        return
                    }

                    //fmt.Printf("result: %+v\n", i)
                }(files[i])
            }
        }
        wg.Wait()
        fmt.Println("ok")
    }
}

type Processor interface {
    Resize([]byte) ([]byte, error)
}

type ImageMagick struct {
}

func (i *ImageMagick) Resize(blob []byte) ([]byte, error) {
    mw := imagick.NewMagickWand()

    err := mw.ReadImageBlob(blob)
    if err != nil {
        return nil, err
    }

    err = mw.StripImage()
    if err != nil {
        return nil, err
    }

    originalFormat := mw.GetImageFormat()
    err = mw.SetImageFormat("gif")
    if err != nil {
        return nil, err
    }

    err = mw.SetImageCompressionQuality(75)
    if err != nil {
        return nil, err
    }
    if strings.ToLower(originalFormat) == "gif" {
        mw = mw.CoalesceImages()
    }
    factor := float64(mw.GetImageHeight()) / float64(mw.GetImageWidth())

    previewHeight := RoundTo2(uint(factor * float64(140)))
    err = mw.ThumbnailImage(140, previewHeight)
    if err != nil {
        return nil, err
    }

    return mw.GetImageBlob(), nil
}

func NewImageMagick() (Processor, error) {
    imagick.Initialize()

    i := &ImageMagick{}

    runtime.SetFinalizer(i, func(_ *ImageMagick) {
        imagick.Terminate()
    })

    return i, nil
}

func RoundTo2(in uint) uint {
    if in%2 != 0 {
        return in + 1
    }
    return in
}
justinfx commented 6 years ago

Much appreciated for including all the necessary details as well as a great repro. I was able to reproduce this as well on ImageMagick 6.9.9-35 using go 1.11 on ubuntu 16. However, this does not crash when tested against the oldest v2 version we support, 6.9.0-2. This means its entirely related to some kind of ImageMagick bug and not the Go imagick bindings. Maybe you could try this against one of the most recent 6.9.x releases to see if it is fixed? Or you can try a slightly older 6.9.x

CMogilko commented 6 years ago

@justinfx I get crash with "gopkg.in/gographics/imagick.v1 and 6.7.8.9-15 (centos7). And with 6.8.9.9 (ubuntu xenial). I tried to free memory manually with Destroy instead of finalizers. It decreased frequency of crashes a lot, but it still can happen.

justinfx commented 6 years ago

Well like I said, it didn't crash with 6.9.0-2 so the variable seems to be the version of ImageMagick

CMogilko commented 6 years ago

@justinfx I was able to reproduce panic at 6.9.0-2. I ran docker container ubuntu xenial, downloaded sources from https://launchpad.net/imagemagick/main/6.9.0-2/+download/ImageMagick-6.9.0-2.tar.gz, built it and after 1000+ iterations (outer k loop from example). I think that with 6.9.0-2 it is less frequent, but still possible.

justinfx commented 6 years ago

I can take another look at this when I have some time. But I wonder, can you reproduce this for other formats besides gif?