CVE-2011-0991 (published 2011-04-13T21:55Z)

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.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)

Weakness information:
cwe_id: CWE-399
cwe_name: Resource Management Errors
description: Weaknesses in this category are related to improper management of system resources.

Fix information:
repo_url: https://github.com/mono/mono
commit_details: repo_url: https://github.com/mono/mono
author: Rodrigo Kumpera
author_date: 2011-02-01 16:50:24+01:00
msg: Don't use finalization to cleanup dynamic methods.

	* reflection.c: Use a reference queue to cleanup
	dynamic methods instead of finalization.

	* runtime.c: Shutdown the dynamic method queue
	before runtime cleanup begins.

	* DynamicMethod.cs: No longer finalizable.

	* icall-def.h: Remove unused dynamic method icall.

	Fixes #660422
repository: repo_url: https://github.com/mono/mono
repo_name: mono/mono
description: Mono open source ECMA CLI, C# and .NET implementation.
repo_language: C#
owner: mono
file_changes: filename: DynamicMethod.cs
change_type: ModificationType.MODIFY
diff: @@ -127,9 +127,6 @@ public DynamicMethod (string name, Type returnType, Type[] parameterTypes, bool
 		[MethodImplAttribute(MethodImplOptions.InternalCall)]
 		private extern void create_dynamic_method (DynamicMethod m);
 
-		[MethodImplAttribute(MethodImplOptions.InternalCall)]
-		private extern void destroy_dynamic_method (DynamicMethod m);
-
 		private void CreateDynMethod () {
 			if (mhandle.Value == IntPtr.Zero) {
 				if (ilgen == null || ilgen.ILOffset == 0)
@@ -158,11 +155,6 @@ public DynamicMethod (string name, Type returnType, Type[] parameterTypes, bool
 			}
 		}
 
-		~DynamicMethod ()
-		{
-			destroy_dynamic_method (this);
-		}
-
 		[ComVisible (true)]
 		public Delegate CreateDelegate (Type delegateType)
 		{

programming_language: C#
filename: icall-def.h
change_type: ModificationType.MODIFY
diff: @@ -524,7 +524,6 @@ ICALL(DERIVEDTYPE_1, "create_unmanaged_type", mono_reflection_create_unmanaged_t
 
 ICALL_TYPE(DYNM, "System.Reflection.Emit.DynamicMethod", DYNM_1)
 ICALL(DYNM_1, "create_dynamic_method", mono_reflection_create_dynamic_method)
-ICALL(DYNM_2, "destroy_dynamic_method", mono_reflection_destroy_dynamic_method)
 
 ICALL_TYPE(ENUMB, "System.Reflection.Emit.EnumBuilder", ENUMB_1)
 ICALL(ENUMB_1, "setup_enum_type", ves_icall_EnumBuilder_setup_enum_type)

programming_language: C
filename: object-internals.h
change_type: ModificationType.MODIFY
diff: @@ -1539,6 +1539,9 @@ mono_object_new_pinned (MonoDomain *domain, MonoClass *klass) MONO_INTERNAL;
 void
 mono_field_static_get_value_for_thread (MonoInternalThread *thread, MonoVTable *vt, MonoClassField *field, void *value) MONO_INTERNAL;
 
+void
+mono_reflection_shutdown (void) MONO_INTERNAL;
+
 #endif /* __MONO_OBJECT_INTERNALS_H__ */
 
 

programming_language: C
filename: reflection.c
change_type: ModificationType.MODIFY
diff: @@ -11441,15 +11441,56 @@ mono_reflection_sighelper_get_signature_field (MonoReflectionSigHelper *sig)
 	return result;
 }
 
+typedef struct {
+	MonoMethod *handle;
+	MonoDomain *domain;
+} DynamicMethodReleaseData;
+	
+static MonoReferenceQueue *dynamic_method_queue;
+
+void
+mono_reflection_shutdown (void)
+{
+	MonoReferenceQueue *queue;
+	mono_loader_lock ();
+	queue = dynamic_method_queue;
+	dynamic_method_queue = NULL;
+	if (queue)
+		mono_gc_reference_queue_free (queue);
+	mono_loader_unlock ();
+}
+
+static void
+free_dynamic_method (void *dynamic_method)
+{
+	DynamicMethodReleaseData *data = dynamic_method;
+
+	mono_runtime_free_method (data->domain, data->handle);
+	g_free (data);
+}
+
 void 
 mono_reflection_create_dynamic_method (MonoReflectionDynamicMethod *mb)
 {
+	MonoReferenceQueue *queue;
+	MonoMethod *handle;
+	DynamicMethodReleaseData *release_data;
 	ReflectionMethodBuilder rmb;
 	MonoMethodSignature *sig;
 	MonoClass *klass;
 	GSList *l;
 	int i;
 
+	if (mono_runtime_is_shutting_down ())
+		mono_raise_exception (mono_get_exception_invalid_operation (""));
+
+	if (!(queue = dynamic_method_queue)) {
+		mono_loader_lock ();
+		if (!(queue = dynamic_method_queue))
+			queue = dynamic_method_queue = mono_gc_reference_queue_new (free_dynamic_method);
+		mono_loader_unlock ();
+	}
+
 	sig = dynamic_method_to_signature (mb);
 
 	reflection_methodbuilder_from_dynamic_method (&rmb, mb);
@@ -11507,7 +11548,12 @@ mono_reflection_create_dynamic_method (MonoReflectionDynamicMethod *mb)
 
 	klass = mb->owner ? mono_class_from_mono_type (mono_reflection_type_get_handle ((MonoReflectionType*)mb->owner)) : mono_defaults.object_class;
 
-	mb->mhandle = reflection_methodbuilder_to_mono_method (klass, &rmb, sig);
+	mb->mhandle = handle = reflection_methodbuilder_to_mono_method (klass, &rmb, sig);
+	release_data = g_new (DynamicMethodReleaseData, 1);
+	release_data->handle = handle;
+	release_data->domain = mono_object_get_domain ((MonoObject*)mb);
+	if (!mono_gc_reference_queue_add (queue, (MonoObject*)mb, release_data))
+		g_free (release_data);
 
 	/* Fix up refs entries pointing at us */
 	for (l = mb->referenced_by; l; l = l->next) {
@@ -11533,16 +11579,6 @@ mono_reflection_create_dynamic_method (MonoReflectionDynamicMethod *mb)
 
 #endif /* DISABLE_REFLECTION_EMIT */
 
-void
-mono_reflection_destroy_dynamic_method (MonoReflectionDynamicMethod *mb)
-{
-	g_assert (mb);
-
-	if (mb->mhandle)
-		mono_runtime_free_method (
-			mono_object_get_domain ((MonoObject*)mb), mb->mhandle);
-}
-
 /**
  * 
  * mono_reflection_is_valid_dynamic_token:

programming_language: C
filename: runtime.c
change_type: ModificationType.MODIFY
diff: @@ -39,5 +39,8 @@ void
 mono_runtime_shutdown (void)
 {
 	mono_domain_foreach (fire_process_exit_event, NULL);
+
+	/*From this point on, no more DM methods can be created. */
+	mono_reflection_shutdown ();
 }
 

programming_language: C
repo_url: https://github.com/mono/mono
commit_details: repo_url: https://github.com/mono/mono
author: Rodrigo Kumpera
author_date: 2011-02-01 16:15:59+01:00
msg: 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.
repository: repo_url: https://github.com/mono/mono
repo_name: mono/mono
description: Mono open source ECMA CLI, C# and .NET implementation.
repo_language: C#
owner: mono
file_changes: filename: gc-internal.h
change_type: ModificationType.MODIFY
diff: @@ -335,5 +335,28 @@ 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__ */
 

programming_language: C
filename: gc.c
change_type: ModificationType.MODIFY
diff: @@ -54,6 +54,7 @@ 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;
@@ -64,6 +65,7 @@ static void object_register_finalizer (MonoObject *obj, void (*callback)(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;
@@ -1065,6 +1067,8 @@ finalizer_thread (gpointer unused)
 		mono_attach_maybe_start ();
 #endif
 
+		reference_queue_proccess_all ();
+
 		if (domains_to_finalize) {
 			mono_finalizer_lock ();
 			if (domains_to_finalize) {
@@ -1083,6 +1087,7 @@ finalizer_thread (gpointer unused)
 		 */
 		mono_gc_invoke_finalizers ();
 
+
 		SetEvent (pending_done_event);
 	}
 
@@ -1097,6 +1102,7 @@ mono_gc_init (void)
 	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);
@@ -1174,6 +1180,7 @@ mono_gc_cleanup (void)
 	DeleteCriticalSection (&handle_section);
 	DeleteCriticalSection (&allocator_section);
 	DeleteCriticalSection (&finalizer_mutex);
+	DeleteCriticalSection (&reference_queue_mutex);
 }
 
 #else
@@ -1292,3 +1299,137 @@ 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;
+}
+

programming_language: C