xorl %eax, %eax

CVE-2010-2065: LibTIFF Integer Overflow

leave a comment »

This issue affects LibTIFF prior to 3.9.3 release. The vulnerable code can be found at libtiff/tif_read.c:

/*
 * Read the specified strip and setup for decoding. The data buffer is
 * expanded, as necessary, to hold the strip's data.
 */
int
TIFFFillStrip(TIFF* tif, tstrip_t strip)
{
        static const char module[] = "TIFFFillStrip";
        TIFFDirectory *td = &tif->tif_dir;

        if ((tif->tif_flags&TIFF_NOREADRAW)==0)
        {

This just checks if it has ‘TIFF_NOREADRAW’ flag set. If this is the case the code path will be…

                if (isMapped(tif) &&
                    (isFillOrder(tif, td->td_fillorder)
                    || (tif->tif_flags & TIFF_NOBITREV))) {
      ...
                } else {
                        /*
                         * Expand raw data buffer, if needed, to hold data
                         * strip coming from file (perhaps should set upper
                         * bound on the size of a buffer we'll use?).
                         */
                        if (bytecount > (uint32)tif->tif_rawdatasize) {
      ...
                                if (!TIFFReadBufferSetup(tif, 0,
                                    TIFFroundup(bytecount, 1024)))
                                        return (0);
                        }
                        if ((uint32)TIFFReadRawStrip1(tif, strip,
                                (unsigned char *)tif->tif_rawdata,
                                bytecount, module) != bytecount)
                                return (0);
      ...
        return (TIFFStartStrip(tif, strip));
}

The first ‘if’ clause affects TIFF images that are already mapped, are filled in order and do not require and bit flip. In any other case, the ‘else’ code will be reached. This will do exactly what the comment says. So, after checking that ‘bytecount’ is greater than the TIFF image’s raw data size it will invoke TIFFReadBufferSetup() to allocate the appropriate buffer.
In the same source code file we can find this routine too.

int
TIFFReadBufferSetup(TIFF* tif, tdata_t bp, tsize_t size)
{
        static const char module[] = "TIFFReadBufferSetup";
    ...
        if (bp) {
    ...
        } else {
                tif->tif_rawdatasize = TIFFroundup(size, 1024);
                tif->tif_rawdata = (tidata_t) _TIFFmalloc(tif->tif_rawdatasize);
                tif->tif_flags |= TIFF_MYBUFFER;
        }
        if (tif->tif_rawdata == NULL) {
                TIFFErrorExt(tif->tif_clientdata, module,
                    "%s: No space for data buffer at scanline %ld",
                    tif->tif_name, (long) tif->tif_row);
                tif->tif_rawdatasize = 0;
                return (0);
        }
        return (1);
}

As you can see it’s nothing more than a simple allocation code using _TIFFmalloc() wrapper function. If there is no buffer ‘bp’ set, it will attempt to allocate some heap space using the _TIFFmalloc() wrapper routine. As you can read, before allocating the appropriate memory, it uses a C macro named TIFFroundup() which is defined at libtiff/tiffiop.h:

/* NB: the uint32 casts are to silence certain ANSI-C compilers */
#define TIFFhowmany(x, y) ((((uint32)(x))+(((uint32)(y))-1))/((uint32)(y)))
#define TIFFhowmany8(x) (((x)&0x07)?((uint32)(x)>>3)+1:(uint32)(x)>>3)
#define TIFFroundup(x, y) (TIFFhowmany(x,y)*(y))

To round-up the value. However, a large raw data size could result in an integer overflow. This means that the call to _TIFFmalloc() will almost certainly return a valid pointer for a small portion of memory because of the overflowed integer that could wrap around zero. Since there is no check of its return value the subsequent attempt of TIFFReadRawStrip1() routine…

static tsize_t
TIFFReadRawStrip1(TIFF* tif,
    tstrip_t strip, tdata_t buf, tsize_t size, const char* module)
{
        TIFFDirectory *td = &tif->tif_dir;

        assert((tif->tif_flags&TIFF_NOREADRAW)==0);
   ...
                cc = TIFFReadFile(tif, buf, size);
                if (cc != size) {
   ...

will end up to heap memory corruption inside TIFFReadFile()…

#define TIFFReadFile(tif, buf, size) \
        ((*(tif)->tif_readproc)((tif)->tif_clientdata,buf,size))

When it attempts to execute tif_readproc() callback function on the incorrect buffer ‘buf’. As we can read at libtiff/tif_open.c…

TIFF*
TIFFClientOpen(
        const char* name, const char* mode,
        thandle_t clientdata,
        TIFFReadWriteProc readproc,
        TIFFReadWriteProc writeproc,
        TIFFSeekProc seekproc,
        TIFFCloseProc closeproc,
        TIFFSizeProc sizeproc,
        TIFFMapFileProc mapproc,
        TIFFUnmapFileProc unmapproc
)
{
    ...
        tif->tif_readproc = readproc;
    ...
        return ((TIFF*)0);
}

This means that depending on how TIFFClientOpen() is called you get the equivalent function assigned to tif_readproc() callback function. These cases are:

TIFF 2 PDF
Found at tools/tiff2pdf.c and it will do nothing…

static tsize_t 
t2p_readproc(thandle_t handle, tdata_t data, tsize_t size) 
{
        (void) handle, (void) data, (void) size;
        return -1;
}

FAX 2 TIFF
Which resides at tools/fax2tiff.c has nothing important either since…

int
main(int argc, char* argv[])
{
     ...
        faxTIFF = TIFFClientOpen("(FakeInput)", "w",
        /* TIFFClientOpen() fails if we don't set existing value here */
                                 TIFFClientdata(out),
                                 TIFFGetReadProc(out), TIFFGetWriteProc(out),
                                 TIFFGetSeekProc(out), TIFFGetCloseProc(out),
                                 TIFFGetSizeProc(out), TIFFGetMapFileProc(out),
                                 TIFFGetUnmapFileProc(out));
     ...
        return (EXIT_SUCCESS);
}

The TIFFGetReadProc() used as the rif_readproc() handler is part of the libtiff/tif_open.c and simply returns a pointer to the callback function

/*
 * Return pointer to file read method.
 */
TIFFReadWriteProc
TIFFGetReadProc(TIFF* tif)
{
        return (tif->tif_readproc);
}

TIFF Sream

Which can be seen at libtiff/tif_stream.cxx

static TIFF*
_tiffStreamOpen(const char* name, const char* mode, void *fd)
{
        TIFF*   tif;

        if( strchr(mode, 'w') ) {
                tiffos_data     *data = new tiffos_data;
                data->myOS = (ostream *)fd;
                data->myStreamStartPos = data->myOS->tellp();

                // Open for writing.
                tif = TIFFClientOpen(name, mode,
                                (thandle_t) data,
                                _tiffosReadProc, _tiffosWriteProc,
                                _tiffosSeekProc, _tiffosCloseProc,
                                _tiffosSizeProc,
                                _tiffDummyMapProc, _tiffDummyUnmapProc);
        } else {
                tiffis_data     *data = new tiffis_data;
                data->myIS = (istream *)fd;
                data->myStreamStartPos = data->myIS->tellg();
                // Open for reading.
                tif = TIFFClientOpen(name, mode,
                                (thandle_t) data,
                                _tiffisReadProc, _tiffisWriteProc,
                                _tiffisSeekProc, _tiffisCloseProc,
                                _tiffisSizeProc,
                                _tiffDummyMapProc, _tiffDummyUnmapProc);
        }

        return (tif);
}

So it uses either _tiffosReadProc() or _tiffisReadProc() which you can see here:

static tsize_t
_tiffosReadProc(thandle_t, tdata_t, tsize_t)
{
        return 0;
}

static tsize_t
_tiffisReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        tiffis_data     *data = (tiffis_data *)fd;

        data->myIS->read((char *)buf, (int)size);

        return data->myIS->gcount();
}

This means that in TIFF stream, and if it’s opened for read, the data->myIS->read() callback function will attempt to read data beyond the bounds of ‘buf’ buffer.

TIFF MS-DOS
As you might have guessed, this code is part of libtiff/tif_msdos.c. So, here it is…

TIFF*
TIFFFdOpen(int fd, const char* name, const char* mode)
{
        TIFF* tif;

        tif = TIFFClientOpen(name, mode,
            (void*) fd,
            _tiffReadProc, _tiffWriteProc, _tiffSeekProc, _tiffCloseProc,
            _tiffSizeProc, _tiffMapProc, _tiffUnmapProc);
        if (tif)
                tif->tif_fd = fd;
        return (tif);
}

Which is this:

static tsize_t 
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        return (read((int) fd, buf, size));
}

This means that MS-DOS version will also trigger the out of bounds read.

TIFF Acorn
Obviously available at libtiff/tif_acorn.c which is the implementation for RISC specific routines.

TIFF*
TIFFFdOpen(int fd, const char* name, const char* mode)
{
        TIFF* tif;

        tif = TIFFClientOpen(name, mode,
                (thandle_t) fd,
                _tiffReadProc, _tiffWriteProc,
                _tiffSeekProc, _tiffCloseProc, _tiffSizeProc,
                _tiffMapProc, _tiffUnmapProc);
        if (tif)
        {
                tif->tif_fd = fd;
        }
        return (tif);
}

That will result in…

static tsize_t
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        return ((tsize_t) read((int) fd, buf, (size_t) size));
}

Which also triggers the bug.

TIFF UNIX
Yea, this one is at libtiff/tif_unix.c and…

TIFF*
TIFFFdOpen(int fd, const char* name, const char* mode)
{
        TIFF* tif;

        tif = TIFFClientOpen(name, mode,
            (thandle_t) fd,
            _tiffReadProc, _tiffWriteProc,
            _tiffSeekProc, _tiffCloseProc, _tiffSizeProc,
            _tiffMapProc, _tiffUnmapProc);
        if (tif)
                tif->tif_fd = fd;
        return (tif);
}

we have _tiffReadProc() which in this case is…

static tsize_t
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        return ((tsize_t) read((int) fd, buf, (size_t) size));
}

Also able to trigger the bug.

TIFF Apple
You can find this one at libtiff/tif_apple.c:142 like this:

TIFF*
TIFFFdOpen(int fd, const char* name, const char* mode)
{
        TIFF* tif;

        tif = TIFFClientOpen(name, mode, (thandle_t) fd,
            _tiffReadProc, _tiffWriteProc, _tiffSeekProc, _tiffCloseProc,
            _tiffSizeProc, _tiffMapProc, _tiffUnmapProc);
        if (tif)
                tif->tif_fd = fd;
        return (tif);
}

Which ends up calling:

static tsize_t
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        return (FSRead((short) fd, (long*) &size, (char*) buf) == noErr ?
            size : (tsize_t) -1);
}

Which triggers the bug too.

TIFF Win 3.x
Found at libtiff/tif_win3.c initializes it as shown below..

TIFF*
TIFFFdOpen(int fd, const char* name, const char* mode)
{
        TIFF* tif;

        tif = TIFFClientOpen(name, mode,
            (thandle_t) fd,
            _tiffReadProc, _tiffWriteProc, _tiffSeekProc, _tiffCloseProc,
            _tiffSizeProc, _tiffMapProc, _tiffUnmapProc);
        if (tif)
                tif->tif_fd = fd;
        return (tif);
}

That leads us to:

static tsize_t 
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        return (_hread(fd, buf, size));
}

That triggers the bug as well.

TIFF ATARI
You can see that code at libtiff/tif_atari.c:

TIFF*
TIFFFdOpen(int fd, const char* name, const char* mode)
{
        TIFF* tif;

        tif = TIFFClientOpen(name, mode,
                (thandle_t) fd,
                _tiffReadProc, _tiffWriteProc, _tiffSeekProc, _tiffCloseProc,
                _tiffSizeProc, _tiffMapProc, _tiffUnmapProc);
        if (tif)
                tif->tif_fd = fd;
        return (tif);
}

and this time _tiffReadProc() is:

static tsize_t
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        long r;

        r = Fread((int) fd, size, buf);
        if (r < 0) {
                errno = (int)-r;
                r = -1;
        }
        return r;
}

That triggers it just fine.

TIFF Win32
This is the final one and it resides at libtiff/tif_win32.c…

TIFF*
TIFFFdOpen(int ifd, const char* name, const char* mode)
{
        TIFF* tif;
        BOOL fSuppressMap = (mode[1] == 'u' || (mode[1]!=0 && mode[2] == 'u'));

        tif = TIFFClientOpen(name, mode, (thandle_t)ifd,
                        _tiffReadProc, _tiffWriteProc,
                        _tiffSeekProc, _tiffCloseProc, _tiffSizeProc,
                        fSuppressMap ? _tiffDummyMapProc : _tiffMapProc,
                        fSuppressMap ? _tiffDummyUnmapProc : _tiffUnmapProc);
        if (tif)
                tif->tif_fd = ifd;
        return (tif);
}

And this case is also just fine to trigger the bug:

static tsize_t
_tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        DWORD dwSizeRead;
        if (!ReadFile(fd, buf, size, &dwSizeRead, NULL))
                return(0);
        return ((tsize_t) dwSizeRead);
}

So, to fix this bug the patch was to simply check that raw data’s size is greater than zero before attempting to allocate the space using _TIFFmalloc()

-		tif->tif_rawdata = (tidata_t) _TIFFmalloc(tif->tif_rawdatasize);
+		if (tif->tif_rawdatasize > 0)
+			tif->tif_rawdata = (tidata_t) _TIFFmalloc(tif->tif_rawdatasize);
 		tif->tif_flags |= TIFF_MYBUFFER;
 	}
-	if (tif->tif_rawdata == NULL) {
+	if ((tif->tif_rawdata == NULL) || (tif->tif_rawdatasize == 0)) {

and then also check that raw data’s size isn’t zero.
To make this even better, a TIFF image is available and you can download it here.
Have fun! :)

Written by xorl

October 15, 2010 at 16:50

Posted in bugs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s