$OpenBSD: patch-libsoup_soup-cache_c,v 1.1 2011/06/29 19:21:46 jasper Exp $

From fdab6aa450d59fdea765c50e8d176f9b963518c9 Mon Sep 17 00:00:00 2001
From: Sergio Villar Senin <svillar@igalia.com>
Date: Mon, 13 Jun 2011 16:52:35 +0000
Subject: soup-cache: fix a use after free

Store a list of SoupBuffers to write data to cache instead of using
a GString. The reason is that g_string_append might reallocate the str
pointer, which is not guaranteed to keep pointing to the same area, and
which would cause the original area that was pointed to to be freed, leading
to the buffer passed to write() to be invalid.

https://bugzilla.gnome.org/show_bug.cgi?id=650620

--- libsoup/soup-cache.c.orig	Mon May 23 21:50:05 2011
+++ libsoup/soup-cache.c	Wed Jun 29 21:17:59 2011
@@ -56,12 +56,10 @@ typedef struct _SoupCacheEntry {
 	char *filename;
 	guint freshness_lifetime;
 	gboolean must_revalidate;
-	GString *data;
-	gsize pos;
 	gsize length;
 	time_t corrected_initial_age;
 	time_t response_time;
-	gboolean writing;
+	SoupBuffer *current_writing_buffer;
 	gboolean dirty;
 	gboolean got_body;
 	gboolean being_validated;
@@ -91,6 +89,7 @@ typedef struct {
 	gulong got_chunk_handler;
 	gulong got_body_handler;
 	gulong restarted_handler;
+	GQueue *buffer_queue;
 } SoupCacheWritingFixture;
 
 enum {
@@ -108,6 +107,7 @@ G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE
 static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry);
 static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
 static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
+static gboolean write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture);
 
 static SoupCacheability
 get_cacheability (SoupCache *cache, SoupMessage *msg)
@@ -233,15 +233,15 @@ soup_cache_entry_free (SoupCacheEntry *entry, gboolean
 	g_free (entry->key);
 	entry->key = NULL;
 
+	if (entry->current_writing_buffer) {
+		soup_buffer_free (entry->current_writing_buffer);
+		entry->current_writing_buffer = NULL;
+	}
+
 	if (entry->headers) {
 		soup_message_headers_free (entry->headers);
 		entry->headers = NULL;
 	}
-
-	if (entry->data) {
-		g_string_free (entry->data, TRUE);
-		entry->data = NULL;
-	}
 	if (entry->error) {
 		g_error_free (entry->error);
 		entry->error = NULL;
@@ -419,11 +419,9 @@ soup_cache_entry_new (SoupCache *cache, SoupMessage *m
 
 	entry = g_slice_new0 (SoupCacheEntry);
 	entry->dirty = FALSE;
-	entry->writing = FALSE;
+	entry->current_writing_buffer = NULL;
 	entry->got_body = FALSE;
 	entry->being_validated = FALSE;
-	entry->data = g_string_new (NULL);
-	entry->pos = 0;
 	entry->error = NULL;
 
 	/* key & filename */
@@ -486,6 +484,8 @@ soup_cache_writing_fixture_free (SoupCacheWritingFixtu
 		g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler);
 	if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler))
 		g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler);
+	g_queue_foreach (fixture->buffer_queue, (GFunc) soup_buffer_free, NULL);
+	g_queue_free (fixture->buffer_queue);
 	g_object_unref (fixture->msg);
 	g_object_unref (fixture->cache);
 	g_slice_free (SoupCacheWritingFixture, fixture);
@@ -550,17 +550,14 @@ close_ready_cb (GObject *source, GAsyncResult *result,
 	}
 
 	if (entry) {
-		/* Get rid of the GString in memory for the resource now */
-		if (entry->data) {
-			g_string_free (entry->data, TRUE);
-			entry->data = NULL;
-		}
-
 		entry->dirty = FALSE;
-		entry->writing = FALSE;
 		entry->got_body = FALSE;
-		entry->pos = 0;
 
+		if (entry->current_writing_buffer) {
+			soup_buffer_free (entry->current_writing_buffer);
+			entry->current_writing_buffer = NULL;
+		}
+
 		g_object_unref (entry->cancellable);
 		entry->cancellable = NULL;
 	}
@@ -600,20 +597,13 @@ write_ready_cb (GObject *source, GAsyncResult *result,
 		/* FIXME: We should completely stop caching the
 		   resource at this point */
 	} else {
-		entry->pos += write_size;
-
 		/* Are we still writing and is there new data to write
 		   already ? */
-		if (entry->data && entry->pos < entry->data->len) {
-			g_output_stream_write_async (entry->stream,
-						     entry->data->str + entry->pos,
-						     entry->data->len - entry->pos,
-						     G_PRIORITY_LOW,
-						     entry->cancellable,
-						     (GAsyncReadyCallback)write_ready_cb,
-						     fixture);
-		} else {
-			entry->writing = FALSE;
+		if (fixture->buffer_queue->length > 0)
+			write_next_buffer (entry, fixture);
+		else {
+			soup_buffer_free (entry->current_writing_buffer);
+			entry->current_writing_buffer = NULL;
 
 			if (entry->got_body) {
 				/* If we already received 'got-body'
@@ -629,18 +619,37 @@ write_ready_cb (GObject *source, GAsyncResult *result,
 	}
 }
 
+static gboolean
+write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture)
+{
+	SoupBuffer *buffer = g_queue_pop_head (fixture->buffer_queue);
+
+	if (buffer == NULL)
+		return FALSE;
+
+	/* Free the old buffer */
+	if (entry->current_writing_buffer) {
+		soup_buffer_free (entry->current_writing_buffer);
+		entry->current_writing_buffer = NULL;
+	}
+	entry->current_writing_buffer = buffer;
+
+	g_output_stream_write_async (entry->stream, buffer->data, buffer->length,
+				     G_PRIORITY_LOW, entry->cancellable,
+				     (GAsyncReadyCallback) write_ready_cb,
+				     fixture);
+	return TRUE;
+}
+
 static void
 msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, SoupCacheWritingFixture *fixture)
 {
 	SoupCacheEntry *entry = fixture->entry;
 
-	g_return_if_fail (chunk->data && chunk->length);
-	g_return_if_fail (entry);
-
 	/* Ignore this if the writing or appending was cancelled */
 	if (!g_cancellable_is_cancelled (entry->cancellable)) {
-		g_string_append_len (entry->data, chunk->data, chunk->length);
-		entry->length = entry->data->len;
+		g_queue_push_tail (fixture->buffer_queue, soup_buffer_copy (chunk));
+		entry->length += chunk->length;
 
 		if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) {
 			/* Quickly cancel the caching of the resource */
@@ -651,17 +660,8 @@ msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk,
 	/* FIXME: remove the error check when we cancel the caching at
 	   the first write error */
 	/* Only write if the entry stream is ready */
-	if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
-		GString *data = entry->data;
-		entry->writing = TRUE;
-		g_output_stream_write_async (entry->stream,
-					     data->str + entry->pos,
-					     data->len - entry->pos,
-					     G_PRIORITY_LOW,
-					     entry->cancellable,
-					     (GAsyncReadyCallback)write_ready_cb,
-					     fixture);
-	}
+	if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream)
+		write_next_buffer (entry, fixture);
 }
 
 static void
@@ -672,29 +672,22 @@ msg_got_body_cb (SoupMessage *msg, SoupCacheWritingFix
 
 	entry->got_body = TRUE;
 
-	if (!entry->stream && entry->pos != entry->length)
+	if (!entry->stream && fixture->buffer_queue->length > 0)
 		/* The stream is not ready to be written but we still
 		   have data to write, we'll write it when the stream
 		   is opened for writing */
 		return;
 
 
-	if (entry->pos != entry->length) {
+	if (fixture->buffer_queue->length > 0) {
 		/* If we still have data to write, write it,
 		   write_ready_cb will close the stream */
-		if (entry->writing == FALSE && entry->error == NULL && entry->stream) {
-			g_output_stream_write_async (entry->stream,
-						     entry->data->str + entry->pos,
-						     entry->data->len - entry->pos,
-						     G_PRIORITY_LOW,
-						     entry->cancellable,
-						     (GAsyncReadyCallback)write_ready_cb,
-						     fixture);
-		}
+		if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream)
+			write_next_buffer (entry, fixture);
 		return;
 	}
 
-	if (entry->stream && !entry->writing)
+	if (entry->stream && entry->current_writing_buffer == NULL)
 		g_output_stream_close_async (entry->stream,
 					     G_PRIORITY_LOW,
 					     entry->cancellable,
@@ -868,16 +861,11 @@ replace_cb (GObject *source, GAsyncResult *result, Sou
 	 * was completed before this happens. In that case
 	 * there is no data
 	 */
-	if (entry->data) {
-		entry->writing = TRUE;
-		g_output_stream_write_async (entry->stream,
-					     entry->data->str + entry->pos,
-					     entry->data->len - entry->pos,
-					     G_PRIORITY_LOW,
-					     entry->cancellable,
-					     (GAsyncReadyCallback)write_ready_cb,
+	if (!write_next_buffer (entry, fixture))
+		/* Could happen if the resource is empty */
+		g_output_stream_close_async (stream, G_PRIORITY_LOW, entry->cancellable,
+					     (GAsyncReadyCallback) close_ready_cb,
 					     fixture);
-	}
 }
 
 typedef struct {
@@ -937,6 +925,7 @@ msg_got_headers_cb (SoupMessage *msg, gpointer user_da
 		fixture->cache = g_object_ref (cache);
 		fixture->entry = entry;
 		fixture->msg = g_object_ref (msg);
+		fixture->buffer_queue = g_queue_new ();
 
 		/* We connect now to these signals and buffer the data
 		   if it comes before the file is ready for writing */
@@ -1561,7 +1550,7 @@ pack_entry (gpointer data,
 	GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
 
 	/* Do not store non-consolidated entries */
-	if (entry->dirty || entry->writing || !entry->key)
+	if (entry->dirty || entry->current_writing_buffer != NULL || !entry->key)
 		return;
 
 	/* Pack headers */
