Commit message: GIF: Don't return a partially initialized pixbuf structure

It was found that gdk-pixbuf GIF image loader gdk_pixbuf__gif_image_load()
routine did not properly handle certain return values from their subroutines.
A remote attacker could provide a specially-crafted GIF image, which once
opened in an application, linked against gdk-pixbuf would lead to gdk-pixbuf
to return partially initialized pixbuf structure, possibly having huge
width and height, leading to that particular application termination due
excessive memory use.

The CVE identifier of CVE-2011-2485 has been assigned to this issue.
CVE: CVE-2011-2485
CWE: NVD-CWE-Other
Vulnerability description: The gdk_pixbuf__gif_image_load function in gdk-pixbuf/io-gif.c in gdk-pixbuf before 2.23.5 does not properly handle certain return values, which allows remote attackers to cause a denial of service (memory consumption) via a crafted GIF image file.

Project: gdk-pixbuf
File: io-gif.c

gdk_pixbuf__gif_image_begin_load (GdkPixbufModuleSizeFunc size_func,
                                  GdkPixbufModulePreparedFunc prepare_func,
				  GdkPixbufModuleUpdatedFunc update_func,
				  gpointer user_data,
                                  GError **error)
{
	GifContext *context;

#ifdef IO_GIFDEBUG
	count = 0;
#endif
	context = new_context ();

        if (context == NULL) {
                g_set_error_literal (error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                     _("Not enough memory to load GIF file"));
                return NULL;
        }
        
        context->error = error;
	context->prepare_func = prepare_func;
	context->update_func = update_func;
	context->user_data = user_data;

	return (gpointer) context;
}

Project: gdk-pixbuf
File: io-gif.c

gif_init (GifContext *context)
{
	unsigned char buf[16];
	char version[4];

	if (!gif_read (context, buf, 6)) {
		/* Unable to read magic number,
                 * gif_read() should have set error
                 */
		return -1;
	}

	if (strncmp ((char *) buf, "GIF", 3) != 0) {
		/* Not a GIF file */
                g_set_error_literal (context->error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                     _("File does not appear to be a GIF file"));
		return -2;
	}

	strncpy (version, (char *) buf + 3, 3);
	version[3] = '\0';

	if ((strcmp (version, "87a") != 0) && (strcmp (version, "89a") != 0)) {
		/* bad version number, not '87a' or '89a' */
                g_set_error (context->error,
                             GDK_PIXBUF_ERROR,
                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                             _("Version %s of the GIF file format is not supported"),
                             version);
		return -2;
	}

	/* read the screen descriptor */
	if (!gif_read (context, buf, 7)) {
		/* Failed to read screen descriptor, error set */
		return -1;
	}

	context->width = LM_to_uint (buf[0], buf[1]);
	context->height = LM_to_uint (buf[2], buf[3]);
        /* The 4th byte is
         * high bit: whether to use the background index
         * next 3:   color resolution
         * next:     whether colormap is sorted by priority of allocation
         * last 3:   size of colormap
         */
	context->global_bit_pixel = 2 << (buf[4] & 0x07);
	context->global_color_resolution = (((buf[4] & 0x70) >> 3) + 1);
        context->has_global_cmap = (buf[4] & 0x80) != 0;
	context->background_index = buf[5];
	context->aspect_ratio = buf[6];

        /* Use background of transparent black as default, though if
         * one isn't set explicitly no one should ever use it.
         */
        context->animation->bg_red = 0;
        context->animation->bg_green = 0;
        context->animation->bg_blue = 0;

        context->animation->width = context->width;
        context->animation->height = context->height;

	if (context->has_global_cmap) {
		gif_set_get_colormap (context);
	} else {
		context->state = GIF_GET_NEXT_STEP;
	}

#ifdef DUMP_IMAGE_DETAILS
        g_print (">Image width: %d height: %d global_cmap: %d background: %d\n",
                 context->width, context->height, context->has_global_cmap, context->background_index);
#endif
        
	return 0;
}

Project: gdk-pixbuf
File: io-gif.c

gif_get_lzw (GifContext *context)
{
	guchar *dest, *temp;
	gint lower_bound, upper_bound; /* bounds for emitting the area_updated signal */
	gboolean bound_flag;
	gint first_pass; /* bounds for emitting the area_updated signal */
	gint v;

	if (context->frame == NULL) {
                context->frame = g_new (GdkPixbufFrame, 1);

                context->frame->composited = NULL;
                context->frame->revert = NULL;
                
                if (context->frame_len == 0 || context->frame_height == 0) {
                        /* An empty frame, we just output a single transparent
                         * pixel at (0, 0).
                         */
                        context->x_offset = 0;
                        context->y_offset = 0;
                        context->frame_len = 1;
                        context->frame_height = 1;
                        context->frame->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
                        if (context->frame->pixbuf) {
                                guchar *pixels;

                                pixels = gdk_pixbuf_get_pixels (context->frame->pixbuf);
                                pixels[0] = 0;
                                pixels[1] = 0;
                                pixels[2] = 0;
                                pixels[3] = 0;
                        }
                } else
                        context->frame->pixbuf =
                                gdk_pixbuf_new (GDK_COLORSPACE_RGB,
                                                TRUE,
                                                8,
                                                context->frame_len,
                                                context->frame_height);
                if (!context->frame->pixbuf) {
                        g_free (context->frame);
                        g_set_error_literal (context->error,
                                             GDK_PIXBUF_ERROR,
                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                             _("Not enough memory to load GIF file"));
                        return -2;
                }

                context->frame->x_offset = context->x_offset;
                context->frame->y_offset = context->y_offset;
                context->frame->need_recomposite = TRUE;
                
                /* GIF delay is in hundredths, we want thousandths */
                context->frame->delay_time = context->gif89.delay_time * 10;

                /* GIFs with delay time 0 are mostly broken, but they
                 * just want a default, "not that fast" delay.
                 */
                if (context->frame->delay_time == 0)
                        context->frame->delay_time = 100;

                /* No GIFs gets to play faster than 50 fps. They just
                 * lock up poor gtk.
                 */
                if (context->frame->delay_time < 20)
                        context->frame->delay_time = 20; /* 20 = "fast" */
                
                context->frame->elapsed = context->animation->total_time;
                context->animation->total_time += context->frame->delay_time;                
                
                switch (context->gif89.disposal) {
                case 0:
                case 1:
                        context->frame->action = GDK_PIXBUF_FRAME_RETAIN;
                        break;
                case 2:
                        context->frame->action = GDK_PIXBUF_FRAME_DISPOSE;
                        break;
                case 3:
                        context->frame->action = GDK_PIXBUF_FRAME_REVERT;
                        break;
                default:
                        context->frame->action = GDK_PIXBUF_FRAME_RETAIN;
                        break;
                }

                context->frame->bg_transparent = (context->gif89.transparent == context->background_index);
                
                context->animation->n_frames ++;
                context->animation->frames = g_list_append (context->animation->frames, context->frame);

                /* Only call prepare_func for the first frame */
		if (context->animation->frames->next == NULL) { 
                        if (context->animation->width == 0 )
                                context->animation->width = gdk_pixbuf_get_width(context->frame->pixbuf);
                        if (context->animation->height == 0)
                                context->animation->height = gdk_pixbuf_get_height (context->frame->pixbuf);

                        if (context->prepare_func)
                                (* context->prepare_func) (context->frame->pixbuf,
                                                           GDK_PIXBUF_ANIMATION (context->animation),
                                                           context->user_data);
                } else {
                        /* Otherwise init frame with last frame */
                        GList *link;
                        GdkPixbufFrame *prev_frame;
                        gint x, y, w, h;
                        
                        link = g_list_find (context->animation->frames, context->frame);

                        prev_frame = link->prev->data;

                        gdk_pixbuf_gif_anim_frame_composite (context->animation, prev_frame);

                        /* Composite failed */
                        if (prev_frame->composited == NULL) {
                                GdkPixbufFrame *frame = NULL;
                                link = g_list_first (context->animation->frames);
                                while (link != NULL) {
                                        frame = (GdkPixbufFrame *)link->data;
                                        if (frame != NULL) {
                                                if (frame->pixbuf != NULL)
                                                        g_object_unref (frame->pixbuf);
                                                if (frame->composited != NULL)
                                                        g_object_unref (frame->composited);
                                                if (frame->revert != NULL)
                                                        g_object_unref (frame->revert);
                                                g_free (frame);
                                        }
                                        link = link->next;
                                }
                                
                                g_list_free (context->animation->frames);
                                context->animation->frames = NULL;
                                
                                g_set_error_literal (context->error,
                                                     GDK_PIXBUF_ERROR,
                                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
                                                     _("Not enough memory to composite a frame in GIF file"));
                                return -2;
                        }
                    
                        x = context->frame->x_offset;
                        y = context->frame->y_offset;
                        w = gdk_pixbuf_get_width (context->frame->pixbuf);
                        h = gdk_pixbuf_get_height (context->frame->pixbuf);
                        if (clip_frame (context, &x, &y, &w, &h))
                                gdk_pixbuf_copy_area (prev_frame->composited,
                                                      x, y, w, h,
                                                      context->frame->pixbuf,
                                                      0, 0);
                }
        }

	dest = gdk_pixbuf_get_pixels (context->frame->pixbuf);

	bound_flag = FALSE;
	lower_bound = upper_bound = context->draw_ypos;
	first_pass = context->draw_pass;

	while (TRUE) {
                guchar (*cmap)[MAXCOLORMAPSIZE];

                if (context->frame_cmap_active)
                        cmap = context->frame_color_map;
                else
                        cmap = context->global_color_map;
                
		v = lzw_read_byte (context);
		if (v < 0) {
			goto finished_data;
		}
		bound_flag = TRUE;

                g_assert (gdk_pixbuf_get_has_alpha (context->frame->pixbuf));
                
                temp = dest + context->draw_ypos * gdk_pixbuf_get_rowstride (context->frame->pixbuf) + context->draw_xpos * 4;
                *temp = cmap [0][(guchar) v];
                *(temp+1) = cmap [1][(guchar) v];
                *(temp+2) = cmap [2][(guchar) v];
                *(temp+3) = (guchar) ((v == context->gif89.transparent) ? 0 : 255);

		if (context->prepare_func && context->frame_interlace)
			gif_fill_in_lines (context, dest, v);

		context->draw_xpos++;
                
		if (context->draw_xpos == context->frame_len) {
			context->draw_xpos = 0;
			if (context->frame_interlace) {
				switch (context->draw_pass) {
				case 0:
				case 1:
					context->draw_ypos += 8;
					break;
				case 2:
					context->draw_ypos += 4;
					break;
				case 3:
					context->draw_ypos += 2;
					break;
				}

				if (context->draw_ypos >= context->frame_height) {
					context->draw_pass++;
					switch (context->draw_pass) {
					case 1:
						context->draw_ypos = 4;
						break;
					case 2:
						context->draw_ypos = 2;
						break;
					case 3:
						context->draw_ypos = 1;
						break;
					default:
						goto done;
					}
				}
			} else {
				context->draw_ypos++;
			}
			if (context->draw_pass != first_pass) {
				if (context->draw_ypos > lower_bound) {
					lower_bound = 0;
					upper_bound = context->frame_height;
				} else {
                                        
				}
			} else
				upper_bound = context->draw_ypos;
		}
		if (context->draw_ypos >= context->frame_height)
			break;
	}

 done:

        context->state = GIF_GET_NEXT_STEP;

        v = 0;

 finished_data:
        
        if (bound_flag)
                context->frame->need_recomposite = TRUE;
        
	if (bound_flag && context->update_func) {
		if (lower_bound <= upper_bound && first_pass == context->draw_pass) {
                        maybe_update (context, 
                                      context->frame->x_offset,
                                      context->frame->y_offset + lower_bound,
                                      gdk_pixbuf_get_width (context->frame->pixbuf),
                                      upper_bound - lower_bound);
		} else {
			if (lower_bound <= upper_bound) {
                                maybe_update (context,
                                              context->frame->x_offset,
                                              context->frame->y_offset,
                                              gdk_pixbuf_get_width (context->frame->pixbuf),
                                              gdk_pixbuf_get_height (context->frame->pixbuf));
			} else {
                                maybe_update (context,
                                              context->frame->x_offset,
                                              context->frame->y_offset,
                                              gdk_pixbuf_get_width (context->frame->pixbuf),
                                              upper_bound);
                                maybe_update (context,
                                              context->frame->x_offset,
                                              context->frame->y_offset + lower_bound,
                                              gdk_pixbuf_get_width (context->frame->pixbuf),
                                              gdk_pixbuf_get_height (context->frame->pixbuf) - lower_bound);
			}
		}
	}

	if (context->state == GIF_GET_NEXT_STEP) {
                /* Will be freed with context->animation, we are just
                 * marking that we're done with it (no current frame)
                 */
		context->frame = NULL;
                context->frame_cmap_active = FALSE;

                if (context->stop_after_first_frame)
                        context->state =  GIF_DONE;
	}
	
	return v;
}

Project: gdk-pixbuf
File: io-gif.c

gdk_pixbuf__gif_image_stop_load (gpointer data, GError **error)
{
	GifContext *context = (GifContext *) data;
        gboolean retval = TRUE;
        
        if (context->state != GIF_DONE || context->animation->frames == NULL) {
                g_set_error_literal (error,
                                     GDK_PIXBUF_ERROR,
                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
                                     _("GIF image was truncated or incomplete."));

                retval = FALSE;
        }
        
        g_object_unref (context->animation);

  	g_free (context->buf);
	g_free (context);

        return retval;
}

Project: gdk-pixbuf
File: io-gif.c

new_context (void)
{
	GifContext *context;

	context = g_try_malloc (sizeof (GifContext));
        if (context == NULL)
                return NULL;

        memset (context, 0, sizeof (GifContext));
        
        context->animation = g_object_new (GDK_TYPE_PIXBUF_GIF_ANIM, NULL);
	context->frame = NULL;
	context->file = NULL;
	context->state = GIF_START;
	context->prepare_func = NULL;
	context->update_func = NULL;
	context->user_data = NULL;
	context->buf = NULL;
	context->amount_needed = 0;
	context->gif89.transparent = -1;
	context->gif89.delay_time = -1;
	context->gif89.input_flag = -1;
	context->gif89.disposal = -1;
        context->animation->loop = 1;
        context->in_loop_extension = FALSE;
        context->stop_after_first_frame = FALSE;

	return context;
}

Project: gdk-pixbuf
File: io-gif.c

gif_read (GifContext *context, guchar *buffer, size_t len)
{
	gboolean retval;
#ifdef IO_GIFDEBUG
	gint i;
#endif
	if (context->file) {
#ifdef IO_GIFDEBUG
		count += len;
		g_print ("Fsize :%d\tcount :%d\t", len, count);
#endif
		retval = (fread(buffer, len, 1, context->file) != 0);

                if (!retval && ferror (context->file)) {
                        gint save_errno = errno;
                        g_set_error (context->error,
                                     G_FILE_ERROR,
                                     g_file_error_from_errno (save_errno),
                                     _("Failure reading GIF: %s"), 
                                     g_strerror (save_errno));
                }
                
#ifdef IO_GIFDEBUG
		if (len < 100) {
			for (i = 0; i < len; i++)
				g_print ("%d ", buffer[i]);
		}
		g_print ("\n");
#endif
                
		return retval;
	} else {
#ifdef IO_GIFDEBUG
/*  		g_print ("\tlooking for %d bytes.  size == %d, ptr == %d\n", len, context->size, context->ptr); */
#endif
		if ((context->size - context->ptr) >= len) {
#ifdef IO_GIFDEBUG
			count += len;
#endif
			memcpy (buffer, context->buf + context->ptr, len);
			context->ptr += len;
			context->amount_needed = 0;
#ifdef IO_GIFDEBUG
			g_print ("Psize :%d\tcount :%d\t", len, count);
			if (len < 100) {
				for (i = 0; i < len; i++)
					g_print ("%d ", buffer[i]);
			}
			g_print ("\n");
#endif
			return TRUE;
		}
		context->amount_needed = len - (context->size - context->ptr);
	}
	return FALSE;
}

Project: gdk-pixbuf
File: io-gif.c

MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
{
        static GdkPixbufModulePattern signature[] = {
                { "GIF8", NULL, 100 },
                { NULL, NULL, 0 }
        };
	static gchar * mime_types[] = {
		"image/gif",
		NULL
	};
	static gchar * extensions[] = {
		"gif",
		NULL
	};

	info->name = "gif";
        info->signature = signature;
	info->description = N_("The GIF image format");
	info->mime_types = mime_types;
	info->extensions = extensions;
	info->flags = GDK_PIXBUF_FORMAT_THREADSAFE;
	info->license = "LGPL";
}

Project: gdk-pixbuf
File: io-gif.c

gif_main_loop (GifContext *context)
{
	gint retval = 0;

	do {
		switch (context->state) {
		case GIF_START:
                        LOG("start\n");
			retval = gif_init (context);
			break;

		case GIF_GET_COLORMAP:
                        LOG("get_colormap\n");
			retval = gif_get_colormap (context);
			if (retval == 0)
				context->state = GIF_GET_NEXT_STEP;
			break;

		case GIF_GET_NEXT_STEP:
                        LOG("next_step\n");
			retval = gif_get_next_step (context);
			break;

		case GIF_GET_FRAME_INFO:
                        LOG("frame_info\n");
			retval = gif_get_frame_info (context);
			break;

		case GIF_GET_EXTENSION:
                        LOG("get_extension\n");
			retval = gif_get_extension (context);
			if (retval == 0)
				context->state = GIF_GET_NEXT_STEP;
			break;

		case GIF_GET_COLORMAP2:
                        LOG("get_colormap2\n");
			retval = gif_get_colormap2 (context);
			if (retval == 0)
				gif_set_prepare_lzw (context);
			break;

		case GIF_PREPARE_LZW:
                        LOG("prepare_lzw\n");
			retval = gif_prepare_lzw (context);
			break;

		case GIF_LZW_FILL_BUFFER:
                        LOG("fill_buffer\n");
			retval = gif_lzw_fill_buffer (context);
			break;

		case GIF_LZW_CLEAR_CODE:
                        LOG("clear_code\n");
			retval = gif_lzw_clear_code (context);
			break;

		case GIF_GET_LZW:
                        LOG("get_lzw\n");
			retval = gif_get_lzw (context);
			break;

		case GIF_DONE:
                        LOG("done\n");
		default:
			retval = 0;
			goto done;
		};
	} while ((retval == 0) || (retval == -3));
 done:
	return retval;
}