Introductionโฏ
Vehereโs research wing, Moon Treader, continuously fuzzes various open source and closed source software and makes responsible disclosure to the concerned vendors. It helps in securing the software and the entire cyber eco-system and shows Moon Treaderโs technical expertise and commitment to ensuring a safer cyberspace.
Executive Summaryโฏ
ImageMagick is a popular image manipulation software that is widely used across the world. In April, Vehereโs Moon treader team responsibly reported couple of issues in ImageMagick software: https://github.com/ImageMagick/ImageMagickโฏ
One of them was assigned the CVE ID: CVE-2023-2157. This vulnerability was found through fuzzing and seems to be a variant of CVE-2020-27829.
This vulnerability occurs when ImageMagick does not properly allocate memory and later tries to read data past allocated memory buffer.โฏ
Fuzzing ImageMagickโฏ
Fuzzing ImageMagick is very well documented over the internet and here is a quick summary of this. We need to git clone ImageMagick and then compile it with afl-cc. The following commands are useful:โฏ
- git clone https://github.com/ImageMagick/ImageMagick.gitโฏ
- CC=afl-cc CXX=afl-cc CFLAGS=โ-ggdb -O0 -fsanitize=address,undefined -fno-omit-frame-pointerโ LDFLAGS=โ-ggdb -fsanitize=address,undefined -fno-omit-frame-pointerโ ./configureโฏ
- make โj8โฏ
- make installโฏ
This will compile and install ImageMagick on the system. Note that we are using address and undefined sanitizers which will help us quickly identify vulnerabilities.
After that, we need to collect corpus from the internet for various image files. Once that is done, we can minimize it with afl-cmin, as shown below:โฏ
afl-cmin -i <input directory> -o <output directory> โ magick @@ /dev/nullโฏ
After that we can start fuzzing, in this case we started 1 master and 8 slave instances:โฏ
afl-fuzz -i in โo out โm none โ magick @@ /dev/nullโฏโฏ
After running fuzzers for a few days, we observed a crash which was reported to the ImageMagick team.โฏโฏIt was assigned the CVE identifier: CVE-2023-2157.
Replicating the issue:
We can replicate the issue with the following command:
magick poc.tiff /dev/null
We can see the following call stack:

From the call stack, we can see that the program is crashing at the โPushQuantumPixelโ function inside โMagickCore/quantum-import.c:255โ.
If we look at this code, we can see that the program crashes when reading the value of pixels at (*pixels++):

โpixelsโ seems to be an array whose values are being read and stored in quantum_info->state.pixel inside a for loop. From the ASAN log above, we also notice that the program is crashing because it is trying to read memory, which is pointed by โpixelsโ beyond its allowed limit.
We need to figure out the following questions to understand the root cause of this vulnerability:
- How is this memory allocated?
- How much memory is allocated?
- Why is the program trying to read past the allocated memory?
If we look at the call stack, we can see the following calls to โAcquireQuantumMemoryโ:

If we look at the โcoders/tiff.cโ we can see the following:

We can see that there is a call to โAcquireQuantumMemoryโ which expects two parameters, extent and sizeof(*strip_pixels); extent is calculated by taking samples_per_pixel * strip_size.
Here Samples_per_pixel is 3, strip_size is 160.
If we debug and check, we can see that the value of extent is 480 and the value of sizeof(*strip_pixels) is 1.
So, the total memory that will be allocated is 480 bytes. The variable โstrip_pixelsโ will point to this newly allocated memory. Later, we can see that โpโ points to โstrip_pixelsโ, as mentioned in the above image. If we look at the allocated memory, we can see that bytes are 0xbe:

This is because of ASAN. By default, ASAN uses 0xbe to fill newly allocated memory.

We can see that there is a for loop that will be iterated for image->rows time, which is 32.

We observe that there is a call to โImportQuantumPixelsโ where one of the parameters is โpโ. As already mentioned, p points to newly allocated memory, or โstrip_pixelsโ.
We can also see that after calling โImportQuantumPixelsโ, value of โpโ is increased by โstrideโ. If we debug and check the value of โstrideโ, it is 20. So, after each loop iteration, the value of โpโ will increase by 20.

We can also see that there is a call to โTIFFReadEncodedStripโ: http://www.libtiff.org/man/TIFFReadEncodedStrip.3t.html
This function reads EncodedStrip data from an image file and keeps it in the user-supplied buffer. If we look at the memory, we will see the following:

It is observed that the start of the strip pixel contains 0x50e03f80.
If we check POC file, we will notice the following:

If we look at offset 8, we will notice that it contains, โ80 3F E0 50โ, whose little-endian order will be: 0x50e03f80. This is the pixel data that will be stored in โstrip_pixelsโ and โpโ.
Later, this will call the function โImportQuantumPixelsโ, where p is passed as an argument. Inside this function, we can see that there is a switch statement for various โQuantumTypeโ: http://www.graphicsmagick.org/api/types.html#quantumtype:

In our case, it is RGBAQuantum:

We can see that it calls another function, ImportRGBAQuantum; inside this function there is a for loop that runs for number_pixels, which is 32:

This will call โPushQuantumPixelโ with โpโ as an argument. If we look at the code, we can see the following:

We can see that there is a for loop that will read data from pixels and store it inside, quantum_info->state.pixel variable.
Now, if we recall the following code:

- The For loop will run for image->row, which is 32
- At each loop iteration, the value of p is increased by stride, which is 20
So total memory which is needed is 32*20 = 640 bytes.
But if you recall memory allocation, which was done using โAcquireQuantumMemoryโ, we passed two parameters, extent and sizeof(*strip_pixels).
extent is calculated by taking samples_per_pixel * strip_size.
Samples_per_pixel is 3, strip_size is 160; so, the total memory allocated was 480 bytes.
The total memory allocated was 480 bytes, but the actual program requirement was 640 bytes; this is why the program was trying to read out of bound in function โPushQuantumPixelโ and thus causing a crash.
Analysis of the fix
We reported this issue to the ImageMagick team, and they promptly fixed it within 24 hours. The following commit fixes the issue:
https://github.com/ImageMagick/ImageMagick/commit/9a9896fce95d09e5e47b86baccbe1ce1a2fca76b
If we look at the fix, we can notice that the extent is calculated as shown below:

extent=(size_t) ((samples_per_pixel+extra_samples)*strip_size);
It considers the memory allocation for extra_samples. Now with this change, the memory that will be allocated is:
Samples_per_pixel =3
Extra samples = 1
Strip_size= 160
So (3+1)*160 = 640 bytes, which is in alignment with the programโs requirement, thus fixing the issue.
Conclusion
ImageMagick is a popular software that is widely used for editing and manipulating images. A vulnerability like this can lead to crash and denial of service. Vehereโs research wing, Moon Treader, continuously fuzzes various software and discovers issues in it. The team collaborates with vendors to responsibly disclose and fix vulnerabilities. In this case, the Moon Treader team would like to thank the ImageMagick team for their efforts and for quickly fixing the issue.
It is our recommendation to the users to keep their software up-to-date and apply the latest vendor patch whenever applicable.


