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 ...
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.