Talos Vulnerability Report

TALOS-2019-0878

xcftools flattenIncrementally tiles walk code execution vulnerability

November 21, 2019
CVE Number

CVE-2019-5086

Summary

An exploitable integer overflow vulnerability exists in the flattenIncrementally function in the xcf2png and xcf2pnm binaries of xcftools, version 1.0.7. An integer overflow can occur while walking through tiles that could be exploited to corrupt memory and execute arbitrary code. In order to trigger this vulnerability, a victim would need to open a specially crafted XCF file.

Tested Versions

xcftools 1.0.7

Product URLs

http://henning.makholm.net/xcftools/

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-680: Integer Overflow to Buffer Overflow

Details

Xcftools is a set of tools for handling Gimp's XCF files. It provides an xcfinfo tool for extracting information from an XCF file, and xcf2png and xcf2pnm for converting an XCF to PNG or PNM format.

When converting an XCF, the utilities first parse the file using the function getBasicXcfInfo. For each XCF layer, the function computeDimensions is called:

struct tileDimensions {
  struct rect c ;
  unsigned width, height ;
  unsigned tilesx, tilesy ;
  unsigned ntiles ;
};

struct rect {
  int t, b, l, r ;
};

void
computeDimensions(struct tileDimensions *d)
{
  d->c.r = d->c.l + d->width ;                            // [1]
  d->c.b = d->c.t + d->height ;                           // [2]
  d->tilesx = (d->width+TILE_WIDTH-1)/TILE_WIDTH ;
  d->tilesy = (d->height+TILE_HEIGHT-1)/TILE_HEIGHT ;
  d->ntiles = d->tilesx * d->tilesy ;
}

The function takes a structure containing some information about the layer: width, height, horizontal and vertical offset. These values are all gathered from the input file. The code calculates the bounds of the layer by filling the rect structure.

As we can see at [1], the "horizontal offset" (d->c.l) is used together with the layer width, to calculate the "right" field of the rectangle. Similarly, the code calculates the "bottom" position at [2].

Note that when autocrop is enabled (command line switch "-C"), the window's dimensions are computed from the layers rather than from the canvas itself. When the flattenIncrementally function is called, rows are populated by reading the spec structure (previously populated by complete_flatspec):

#define TILE_LEFT(x) ((x) & -TILE_WIDTH)       // & 0xffffffc0
#define TILE_WIDTH (1<<TILE_SHIFT)             // 64
#define TILE_SHIFT 6

flattenIncrementally(struct FlattenSpec *spec,lineCallback callback)
{
  rgba *rows[TILE_HEIGHT] ;
  unsigned i, y, nrows, ncols ;
  struct rect where ;
  struct Tile *tile ;
  static struct Tile toptile ;

  toptile.count = TILE_HEIGHT * TILE_WIDTH ;
  fillTile(&toptile,0);

  for( where.t = spec->dim.c.t; where.t < spec->dim.c.b; where.t=where.b ) {     // [3]
    where.b = TILE_TOP(where.t)+TILE_HEIGHT ;
    if( where.b > spec->dim.c.b ) where.b = spec->dim.c.b ;
    nrows = where.b - where.t ;
    for( y = 0; y < nrows ; y++ )
      rows[y] = xcfmalloc(4*(spec->dim.c.r-spec->dim.c.l));

    for( where.l = spec->dim.c.l; where.l < spec->dim.c.r; where.l=where.r ) {   // [4]
      where.r = TILE_LEFT(where.l)+TILE_WIDTH ;                                  // [5]
      if( where.r > spec->dim.c.r ) where.r = spec->dim.c.r ;
      ncols = where.r - where.l ;
      ...
      for( y = 0 ; y < nrows ; y++ )
        memcpy(rows[y] + (where.l - spec->dim.c.l),                              // [6]
               tile->pixels + y * ncols, ncols*4);
      ...

At [3] and [4], the code loops for each tile in the layer and copies the data in rows. Since tiles are aligned, at [5] the horizontal limit ("right") of the current tile is computed. This command however doesn't take integer overflows into account: for example, assuming that all dimensions in spec are positive, if where.l was 0x7fffffe0, where.r would become 0x80000000, which is negative since the fields in the rect structure are signed. At this point, when the cycle loops at [4], where.l will have the value 0x80000000 assigned, and the loop termination condition where.l < spec->dim.c.r won't be met, since where.l is negative.

This makes the for loop at [4] to cycle unexpectedly, which could be exploited to corrupt the heap at [6], leading to arbitrary code execution.

Crash Information

$ ./xcf2png -o test poc -C
Warning: XCF version 3 not supported (trying anyway...)
=================================================================
==13592==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf2e02e7c at pc 0xf797543b bp 0xfffddc98 sp 0xfffdd868
WRITE of size 128 at 0xf2e02e7c thread T0
    #0 0xf797543a in __interceptor_memcpy /build/gcc/src/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790
    #1 0x56672c26 in flattenIncrementally /src_1.0.7/flatten.c:647
    #2 0x56653a2b in main /src_1.0.7/xcf2png.c:417
    #3 0xf76da7d0 in __libc_start_main (/usr/lib32/libc.so.6+0x1e7d0)
    #4 0x56656e40 in _start (/src_1.0.7/xcf2png+0xae40)

0xf2e02e7c is located 0 bytes to the right of 124-byte region [0xf2e02e00,0xf2e02e7c)
allocated by thread T0 here:
    #0 0xf79ed3e9 in __interceptor_malloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cc:144
    #1 0x56661468 in xcfmalloc /src_1.0.7/utils.c:103

SUMMARY: AddressSanitizer: heap-buffer-overflow /build/gcc/src/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:790 in __interceptor_memcpy
Shadow bytes around the buggy address:
  0x3e5c0570: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c0580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c0590: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c05a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c05b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x3e5c05c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00[04]
  0x3e5c05d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c05e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c05f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c0600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3e5c0610: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==13592==ABORTING

Timeline

2019-07-31 - Initial contact
2019-08-07 - Plain text file sent
2019-10-02 - 60+ day follow up
2019-10-21 - 90 day notice
2019-11-21 - Public release

Credit

Discovered by Claudio Bozzato of Cisco Talos.