Project: imagemagick (imagemagick/imagemagick), language: c++
Vulnerability instance: imagemagick.cve-2017-10928
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/ImageMagick/ImageMagick/issues/539
## Description:
Issue: ImageMagick/ImageMagick#539
Title: Heap-buffer-overflow of identify&convert
State: closed
Created by: zyy89
Created at: 2017-07-04 16:34:09+00:00
Labels: bug

Issue Body:
ImageMagick-7.0.6-0
<br>
```
  ~$identify $FILE or convert $FILE
```
<br>build instructions:
```
  ~$ sudo apt-get install imagemagick libmagick++-dev
  ~$ CC="gcc" CFLAGS="-fsanitize=address" ./configure 
  ~$ make;make install
```
when identify or convert SVG file, imagemagick will cause a heap buffer overflow
> =================================================================
==28273==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000008df0 at pc 0x7f193bfe62a7 bp 0x7ffdd3a66b60 sp 0x7ffdd3a66b50
READ of size 1 at 0x603000008df0 thread T0
    #0 0x7f193bfe62a6 in GetNextToken MagickCore/token.c:258
    #1 0x7f193c1a375e in GetUserSpaceCoordinateValue coders/svg.c:331
    #2 0x7f193c1a5767 in SVGStartElement coders/svg.c:936
    #3 0x7f1937efdcd0 in xmlParseStartTag (/usr/lib/x86_64-linux-gnu/libxml2.so.2+0x45cd0)
    #4 0x7f1937f0e738  (/usr/lib/x86_64-linux-gnu/libxml2.so.2+0x56738)
    #5 0x7f1937f0f93a in xmlParseChunk (/usr/lib/x86_64-linux-gnu/libxml2.so.2+0x5793a)
    #6 0x7f193c1acb75 in ReadSVGImage coders/svg.c:3236
    #7 0x7f193bd91c4f in ReadImage MagickCore/constitute.c:497
    #8 0x7f193bfdde06 in ReadStream MagickCore/stream.c:1045
    #9 0x7f193bd9117c in PingImage MagickCore/constitute.c:226
    #10 0x7f193bd915a9 in PingImages MagickCore/constitute.c:327
    #11 0x7f193b751a2a in IdentifyImageCommand MagickWand/identify.c:319
    #12 0x7f193b7a8c7a in MagickCommandGenesis MagickWand/mogrify.c:183
    #13 0x40169a in MagickMain utilities/magick.c:149
    #14 0x4017be in main utilities/magick.c:180
    #15 0x7f193b02182f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #16 0x4012c8 in _start (/home/sf/ImageMagick-7.0.6-0/build-gcc/bin/magick+0x4012c8)


The vulnerability is caused when identify SVG image, which happens  in function GetNextToken MagickCore/token.c:258, function GetNextToken is called by GetUserSpaceCoordinateValue coders/svg.c:331.
Here is the critical code:
```
        }
      for ( ; *p != '\0'; p++)    //line 258 of MagickCore/token.c
      {
        if (((isspace((int) ((unsigned char) *p)) != 0) || (*p == '=') ||
            (*p == ',') || (*p == ':') || (*p == ';')) && (*(p-1) != '\\'))
          break;
        if ((i > 0) && (*p == '<'))
          break;
        if (i < (ssize_t) (extent-1))
          token[i++]=(*p);
        if (*p == '>')
          break;
        if (*p == '(')
          for (p++; *p != '\0'; p++)
          {
            if (i < (ssize_t) (extent-1))
              token[i++]=(*p);
            if ((*p == ')') && (*(p-1) != '\\')) //no ')' matched here
              break;
          }
      }
```

The reason of this heap buffer overflow is when p buffer has no matched ‘(‘ ‘)’characters, the POC’s  buffer p here is “"trix(      gradielGr74.075998"”, caused the heap buffer overflow read until next ‘\0’ matched. It is possible to disclosing some critical data, such as heap chunk data and even other applications’ private data. I am not sure whether it can be exploited to achieve code execution, maybe it is possible.

####Testcase: https://github.com/zyy89/pocs/blob/master/imagemagick-heap-buffer-overflow-4



Comments:
Comment by glennrp on 2017-07-04 22:27:20+00:00:
This testcase doesn't seem to be a MNG.  "file" says it's a gzip but gunzip doesn't handle it.

---

Comment by mikayla-grace on 2017-07-04 23:39:38+00:00:
Thanks for the problem report.  We can reproduce it and will have a patch to fix it in GIT master branch @ https://github.com/ImageMagick/ImageMagick later today.  The patch will be available in the beta releases of ImageMagick @ http://www.imagemagick.org/download/beta/ by sometime tomorrow. 


Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./configure --prefix="$WORK" --disable-shared --disable-docs
make "-j$(nproc)"
make install -j$(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool
RUN git clone https://github.com/imagemagick/imagemagick imagemagick
RUN git -C imagemagick checkout 9caa8b0c4269163def2ce0fa6ff1754ec0c234d2
WORKDIR $SRC/imagemagick
COPY build.sh $SRC/

Additional files:
filename: crop_fuzzer.cc
content: #include <cstdint>

#include <Magick++/Blob.h>
#include <Magick++/Image.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  uint16_t Width;
  uint16_t Height;
  if (Size < (sizeof(Width) + sizeof(Height))) {
    return 0;
  }
  Width = *reinterpret_cast<const uint16_t *>(Data);
  Height = *reinterpret_cast<const uint16_t *>(Data + sizeof(Width));
  const Magick::Blob blob(Data + sizeof(Width) + sizeof(Height),
                          Size - (sizeof(Width) + sizeof(Height)));
  Magick::Image image;
  try {
    image.read(blob);
  } catch (Magick::Exception &e) {
    return 0;
  }
  image.crop(Magick::Geometry(Width, Height));
  return 0;
}

filename: encoder_fuzzer.cc
content: #include <cstdint>

#include <Magick++/Blob.h>
#include <Magick++/Image.h>

#define FUZZ_ENCODER_STRING_LITERAL(name) #name
#define FUZZ_ENCODER FUZZ_ENCODER_STRING_LITERAL(FUZZ_IMAGEMAGICK_ENCODER)

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  const Magick::Blob blob(Data, Size);
  Magick::Image image;
  try {
    image.read(blob);
  } catch (Magick::Exception &e) {
    return 0;
  }

  Magick::Blob outBlob;
  try {
    image.write(&outBlob, FUZZ_ENCODER);
  } catch (Magick::Exception &e) {
  }
  return 0;
}

filename: encoder_list.cc
content: #include <iostream>

#include <Magick++/Blob.h>
#include <Magick++/Image.h>

extern "C" int main() {
  size_t nFormats;
  Magick::ExceptionInfo ex;
  const Magick::MagickInfo **formats = GetMagickInfoList("*", &nFormats, &ex);

  for (size_t i = 0; i < nFormats; i++) {
    const Magick::MagickInfo *format = formats[i];
    if (format->encoder && format->name) {
      std::cout << format->name << std::endl;
    }
  }
}

filename: enhance_fuzzer.cc
content: #include <cstdint>

#include <Magick++/Blob.h>
#include <Magick++/Image.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  const Magick::Blob blob(Data, Size);
  Magick::Image image;
  try {
    image.read(blob);
    image.enhance();
  } catch (Magick::Exception &e) {
    return 0;
  }
  return 0;
}

filename: huffman_decode_fuzzer.cc
content: #include <cstdint>

#include <Magick++/Blob.h>
#include <Magick++/Image.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  const Magick::Blob blob(Data, Size);
  Magick::Image image;
  try {
    image.read(blob);
  } catch (Magick::Exception &e) {
    return 0;
  }
  Magick::ExceptionInfo ex;
  auto res = HuffmanDecodeImage(image.image(), &ex);
  return 0;
}

filename: rotate_fuzzer.cc
content: #include <cstdint>

#include <Magick++/Blob.h>
#include <Magick++/Image.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  if (Size < sizeof(double)) {
    return 0;
  }
  double Degrees = *reinterpret_cast<const double *>(Data);
  if (!isfinite(Degrees)) {
    return 0;
  }
  const Magick::Blob blob(Data + sizeof(Degrees), Size - sizeof(Degrees));
  Magick::Image image;
  try {
    image.read(blob);
  } catch (Magick::Exception &e) {
    return 0;
  }
  image.rotate(Degrees);
  return 0;
}