CVE-2011-0991: Resource Management Errors (CWE-399)
value: Use-after-free vulnerability in Mono, when Moonlight 2.x before 2.4.1 or 3.x before 3.99.3 is used, allows remote attackers to cause a denial of service or possibly have unspecified other impact via vectors related to finalizing and then resurrecting a DynamicMethod instance.
CWE description: Weaknesses in this category are related to improper management of system resources.
Published: 2011-04-13T21:55Z
Repository: https://github.com/mono/mono
Language: C
Files changed: mono/metadata/gc-internal.h, mono/metadata/gc.c
Commit date: 2011-02-01 16:52:43 +0100
Commit message: Implement a reference queue API.

	* gc.c: A reference queue allows one to queue
	callbcks for when objects are collected.
	It allows for safe cleanup of objects that can
	only be done when it is effectively collected.
	The major difference with regular finalization
	is that the collector makes sure the object
	was collected - and can't be resurrected.

	* gc-internal.h: Export entrypoints for the
	new API.

Diff: diff --git a/mono/metadata/gc-internal.h b/mono/metadata/gc-internal.h
index 16d31f09e0a..fb2af22e497 100644
--- a/mono/metadata/gc-internal.h
+++ b/mono/metadata/gc-internal.h
@@ -333,7 +333,30 @@ gboolean mono_gc_parse_environment_string_extract_number (const char *str, glong
 
 gboolean mono_gc_precise_stack_mark_enabled (void) MONO_INTERNAL;
 
 FILE *mono_gc_get_logfile (void) MONO_INTERNAL;
 
+typedef void (*mono_reference_queue_callback) (void *user_data);
+
+typedef struct _MonoReferenceQueue MonoReferenceQueue;
+typedef struct _RefQueueEntry RefQueueEntry;
+
+struct _RefQueueEntry {
+	void *dis_link;
+	void *user_data;
+	RefQueueEntry *next;
+};
+
+struct _MonoReferenceQueue {
+	RefQueueEntry *queue;
+	mono_reference_queue_callback callback;
+	MonoReferenceQueue *next;
+	gboolean should_be_deleted;
+};
+
+MonoReferenceQueue* mono_gc_reference_queue_new (mono_reference_queue_callback callback) MONO_INTERNAL;
+void mono_gc_reference_queue_free (MonoReferenceQueue *queue) MONO_INTERNAL;
+gboolean mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data) MONO_INTERNAL;
+
+
 #endif /* __MONO_METADATA_GC_INTERNAL_H__ */
 
diff --git a/mono/metadata/gc.c b/mono/metadata/gc.c
index fd29cd9dfe7..2208a7a7ff6 100644
--- a/mono/metadata/gc.c
+++ b/mono/metadata/gc.c
@@ -52,20 +52,22 @@ static gboolean gc_disabled = FALSE;
 static gboolean finalizing_root_domain = FALSE;
 
 #define mono_finalizer_lock() EnterCriticalSection (&finalizer_mutex)
 #define mono_finalizer_unlock() LeaveCriticalSection (&finalizer_mutex)
 static CRITICAL_SECTION finalizer_mutex;
+static CRITICAL_SECTION reference_queue_mutex;
 
 static GSList *domains_to_finalize= NULL;
 static MonoMList *threads_to_finalize = NULL;
 
 static MonoInternalThread *gc_thread;
 
 static void object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*));
 
 static void mono_gchandle_set_target (guint32 gchandle, MonoObject *obj);
 
+static void reference_queue_proccess_all (void);
 #ifndef HAVE_NULL_GC
 static HANDLE pending_done_event;
 static HANDLE shutdown_event;
 #endif
 
@@ -1063,10 +1065,12 @@ finalizer_thread (gpointer unused)
 
 #ifndef DISABLE_ATTACH
 		mono_attach_maybe_start ();
 #endif
 
+		reference_queue_proccess_all ();
+
 		if (domains_to_finalize) {
 			mono_finalizer_lock ();
 			if (domains_to_finalize) {
 				DomainFinalizationReq *req = domains_to_finalize->data;
 				domains_to_finalize = g_slist_remove (domains_to_finalize, req);
@@ -1081,10 +1085,11 @@ finalizer_thread (gpointer unused)
 		/* If finished == TRUE, mono_gc_cleanup has been called (from mono_runtime_cleanup),
 		 * before the domain is unloaded.
 		 */
 		mono_gc_invoke_finalizers ();
 
+
 		SetEvent (pending_done_event);
 	}
 
 	SetEvent (shutdown_event);
 	return 0;
@@ -1095,10 +1100,11 @@ mono_gc_init (void)
 {
 	InitializeCriticalSection (&handle_section);
 	InitializeCriticalSection (&allocator_section);
 
 	InitializeCriticalSection (&finalizer_mutex);
+	InitializeCriticalSection (&reference_queue_mutex);
 
 	MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_NORMAL].entries);
 	MONO_GC_REGISTER_ROOT_FIXED (gc_handles [HANDLE_PINNED].entries);
 
 	mono_gc_base_init ();
@@ -1172,10 +1178,11 @@ mono_gc_cleanup (void)
 	}
 
 	DeleteCriticalSection (&handle_section);
 	DeleteCriticalSection (&allocator_section);
 	DeleteCriticalSection (&finalizer_mutex);
+	DeleteCriticalSection (&reference_queue_mutex);
 }
 
 #else
 
 /* Null GC dummy functions */
@@ -1290,5 +1297,139 @@ void*
 mono_gc_alloc_mature (MonoVTable *vtable)
 {
 	return mono_object_new_specific (vtable);
 }
 #endif
+
+
+static MonoReferenceQueue *ref_queues;
+
+static void
+ref_list_remove_element (RefQueueEntry **prev, RefQueueEntry *element)
+{
+	do {
+		/* Guard if head is changed concurrently. */
+		while (*prev != element)
+			prev = &(*prev)->next;
+	} while (prev && InterlockedCompareExchangePointer ((void*)prev, element->next, element) != element);
+}
+
+static void
+ref_list_push (RefQueueEntry **head, RefQueueEntry *value)
+{
+	RefQueueEntry *current;
+	do {
+		current = *head;
+		value->next = current;
+	} while (InterlockedCompareExchangePointer ((void*)head, value, current) != current);
+}
+
+static void
+reference_queue_proccess (MonoReferenceQueue *queue)
+{
+	RefQueueEntry **iter = &queue->queue;
+	RefQueueEntry *entry;
+	while ((entry = *iter)) {
+		if (queue->should_be_deleted || !mono_gc_weak_link_get (&entry->dis_link)) {
+			ref_list_remove_element (iter, entry);
+			mono_gc_weak_link_remove (&entry->dis_link);
+			queue->callback (entry->user_data);
+			g_free (entry);
+		} else {
+			iter = &entry->next;
+		}
+	}
+}
+
+static void
+reference_queue_proccess_all (void)
+{
+	MonoReferenceQueue **iter;
+	MonoReferenceQueue *queue = ref_queues;
+	for (; queue; queue = queue->next)
+		reference_queue_proccess (queue);
+
+restart:
+	EnterCriticalSection (&reference_queue_mutex);
+	for (iter = &ref_queues; *iter;) {
+		queue = *iter;
+		if (!queue->should_be_deleted) {
+			iter = &queue->next;
+			continue;
+		}
+		if (queue->queue) {
+			LeaveCriticalSection (&reference_queue_mutex);
+			reference_queue_proccess (queue);
+			goto restart;
+		}
+		*iter = queue->next;
+		g_free (queue);
+	}
+	LeaveCriticalSection (&reference_queue_mutex);
+}
+
+/**
+ * mono_gc_reference_queue_new:
+ * @callback callback used when processing dead entries.
+ *
+ * Create a new reference queue used to process collected objects.
+ * A reference queue let you queue the pair (managed object, user data).
+ * Once the managed object is collected @callback will be called
+ * in the finalizer thread with 'user data' as argument.
+ *
+ * The callback is called without any locks held.
+ */
+MonoReferenceQueue*
+mono_gc_reference_queue_new (mono_reference_queue_callback callback)
+{
+	MonoReferenceQueue *res = g_new0 (MonoReferenceQueue, 1);
+	res->callback = callback;
+
+	EnterCriticalSection (&reference_queue_mutex);
+	res->next = ref_queues;
+	ref_queues = res;
+	LeaveCriticalSection (&reference_queue_mutex);
+
+	return res;
+}
+
+/**
+ * mono_gc_reference_queue_add:
+ * @queue the queue to add the reference to.
+ * @obj the object to be watched for collection
+ * @user_data parameter to be passed to the queue callback
+ *
+ * Queue an object to be watched for collection.
+ *
+ * @returns false if the queue is scheduled to be freed.
+ */
+gboolean
+mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data)
+{
+	RefQueueEntry *head;
+	RefQueueEntry *entry;
+	if (queue->should_be_deleted)
+		return FALSE;
+
+	entry = g_new0 (RefQueueEntry, 1);
+	entry->user_data = user_data;
+	mono_gc_weak_link_add (&entry->dis_link, obj, TRUE);
+	ref_list_push (&queue->queue, entry);
+	return TRUE;
+}
+
+/**
+ * mono_gc_reference_queue_free:
+ * @queue the queue that should be deleted.
+ *
+ * This operation signals that @queue should be deleted. This operation is deferred
+ * as it happens on the finalizer thread.
+ *
+ * After this call, no further objects can be queued. It's the responsibility of the
+ * caller to make sure that no further attempt to access queue will be made.
+ */
+void
+mono_gc_reference_queue_free (MonoReferenceQueue *queue)
+{
+	queue->should_be_deleted = TRUE;
+}
+

Fixed code: typedef void (*mono_reference_queue_callback) (void *user_data);

typedef struct _MonoReferenceQueue MonoReferenceQueue;
typedef struct _RefQueueEntry RefQueueEntry;

struct _RefQueueEntry {
	void *dis_link;
	void *user_data;
	RefQueueEntry *next;
};

struct _MonoReferenceQueue {
	RefQueueEntry *queue;
	mono_reference_queue_callback callback;
	MonoReferenceQueue *next;
	gboolean should_be_deleted;
};

MonoReferenceQueue* mono_gc_reference_queue_new (mono_reference_queue_callback callback) MONO_INTERNAL;
void mono_gc_reference_queue_free (MonoReferenceQueue *queue) MONO_INTERNAL;
gboolean mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data) MONO_INTERNAL;


static CRITICAL_SECTION reference_queue_mutex;
static void reference_queue_proccess_all (void);
		reference_queue_proccess_all ();


	InitializeCriticalSection (&reference_queue_mutex);
	DeleteCriticalSection (&reference_queue_mutex);


static MonoReferenceQueue *ref_queues;

static void
ref_list_remove_element (RefQueueEntry **prev, RefQueueEntry *element)
{
	do {
		/* Guard if head is changed concurrently. */
		while (*prev != element)
			prev = &(*prev)->next;
	} while (prev && InterlockedCompareExchangePointer ((void*)prev, element->next, element) != element);
}

static void
ref_list_push (RefQueueEntry **head, RefQueueEntry *value)
{
	RefQueueEntry *current;
	do {
		current = *head;
		value->next = current;
	} while (InterlockedCompareExchangePointer ((void*)head, value, current) != current);
}

static void
reference_queue_proccess (MonoReferenceQueue *queue)
{
	RefQueueEntry **iter = &queue->queue;
	RefQueueEntry *entry;
	while ((entry = *iter)) {
		if (queue->should_be_deleted || !mono_gc_weak_link_get (&entry->dis_link)) {
			ref_list_remove_element (iter, entry);
			mono_gc_weak_link_remove (&entry->dis_link);
			queue->callback (entry->user_data);
			g_free (entry);
		} else {
			iter = &entry->next;
		}
	}
}

static void
reference_queue_proccess_all (void)
{
	MonoReferenceQueue **iter;
	MonoReferenceQueue *queue = ref_queues;
	for (; queue; queue = queue->next)
		reference_queue_proccess (queue);

restart:
	EnterCriticalSection (&reference_queue_mutex);
	for (iter = &ref_queues; *iter;) {
		queue = *iter;
		if (!queue->should_be_deleted) {
			iter = &queue->next;
			continue;
		}
		if (queue->queue) {
			LeaveCriticalSection (&reference_queue_mutex);
			reference_queue_proccess (queue);
			goto restart;
		}
		*iter = queue->next;
		g_free (queue);
	}
	LeaveCriticalSection (&reference_queue_mutex);
}

/**
 * mono_gc_reference_queue_new:
 * @callback callback used when processing dead entries.
 *
 * Create a new reference queue used to process collected objects.
 * A reference queue let you queue the pair (managed object, user data).
 * Once the managed object is collected @callback will be called
 * in the finalizer thread with 'user data' as argument.
 *
 * The callback is called without any locks held.
 */
MonoReferenceQueue*
mono_gc_reference_queue_new (mono_reference_queue_callback callback)
{
	MonoReferenceQueue *res = g_new0 (MonoReferenceQueue, 1);
	res->callback = callback;

	EnterCriticalSection (&reference_queue_mutex);
	res->next = ref_queues;
	ref_queues = res;
	LeaveCriticalSection (&reference_queue_mutex);

	return res;
}

/**
 * mono_gc_reference_queue_add:
 * @queue the queue to add the reference to.
 * @obj the object to be watched for collection
 * @user_data parameter to be passed to the queue callback
 *
 * Queue an object to be watched for collection.
 *
 * @returns false if the queue is scheduled to be freed.
 */
gboolean
mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data)
{
	RefQueueEntry *head;
	RefQueueEntry *entry;
	if (queue->should_be_deleted)
		return FALSE;

	entry = g_new0 (RefQueueEntry, 1);
	entry->user_data = user_data;
	mono_gc_weak_link_add (&entry->dis_link, obj, TRUE);
	ref_list_push (&queue->queue, entry);
	return TRUE;
}

/**
 * mono_gc_reference_queue_free:
 * @queue the queue that should be deleted.
 *
 * This operation signals that @queue should be deleted. This operation is deferred
 * as it happens on the finalizer thread.
 *
 * After this call, no further objects can be queued. It's the responsibility of the
 * caller to make sure that no further attempt to access queue will be made.
 */
void
mono_gc_reference_queue_free (MonoReferenceQueue *queue)
{
	queue->should_be_deleted = TRUE;
}