glib2/SOURCES/gmain-corruption.patch
2021-10-08 11:22:59 +00:00

387 lines
14 KiB
Diff

From 2bad3cb3bf8f0cc3f45057061f9a538ecf7742b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= <sebastian@centricular.com>
Date: Thu, 14 Feb 2019 17:46:33 +0200
Subject: [PATCH 1/5] Use atomic reference counting for GSource
If attached to a context already it would use a mutex instead but at
least before that the reference counting is not thread-safe currently.
---
glib/gmain.c | 50 +++++++++++++++-----------------------------------
1 file changed, 15 insertions(+), 35 deletions(-)
diff --git a/glib/gmain.c b/glib/gmain.c
index 26e68823d..5b91c3117 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -374,15 +374,6 @@ typedef struct _GSourceIter
#define SOURCE_DESTROYED(source) (((source)->flags & G_HOOK_FLAG_ACTIVE) == 0)
#define SOURCE_BLOCKED(source) (((source)->flags & G_SOURCE_BLOCKED) != 0)
-#define SOURCE_UNREF(source, context) \
- G_STMT_START { \
- if ((source)->ref_count > 1) \
- (source)->ref_count--; \
- else \
- g_source_unref_internal ((source), (context), TRUE); \
- } G_STMT_END
-
-
/* Forward declarations */
static void g_source_unref_internal (GSource *source,
@@ -977,10 +968,10 @@ g_source_iter_next (GSourceIter *iter, GSource **source)
*/
if (iter->source && iter->may_modify)
- SOURCE_UNREF (iter->source, iter->context);
+ g_source_unref_internal (iter->source, iter->context, TRUE);
iter->source = next_source;
if (iter->source && iter->may_modify)
- iter->source->ref_count++;
+ g_source_ref (iter->source);
*source = iter->source;
return *source != NULL;
@@ -994,7 +985,7 @@ g_source_iter_clear (GSourceIter *iter)
{
if (iter->source && iter->may_modify)
{
- SOURCE_UNREF (iter->source, iter->context);
+ g_source_unref_internal (iter->source, iter->context, TRUE);
iter->source = NULL;
}
}
@@ -1135,7 +1126,7 @@ g_source_attach_unlocked (GSource *source,
source->context = context;
source->source_id = id;
- source->ref_count++;
+ g_source_ref (source);
g_hash_table_insert (context->sources, GUINT_TO_POINTER (id), source);
@@ -1675,7 +1666,7 @@ g_source_set_funcs (GSource *source,
{
g_return_if_fail (source != NULL);
g_return_if_fail (source->context == NULL);
- g_return_if_fail (source->ref_count > 0);
+ g_return_if_fail (g_atomic_int_get (&source->ref_count) > 0);
g_return_if_fail (funcs != NULL);
source->source_funcs = funcs;
@@ -2050,19 +2041,9 @@ g_source_set_name_by_id (guint tag,
GSource *
g_source_ref (GSource *source)
{
- GMainContext *context;
-
g_return_val_if_fail (source != NULL, NULL);
- context = source->context;
-
- if (context)
- LOCK_CONTEXT (context);
-
- source->ref_count++;
-
- if (context)
- UNLOCK_CONTEXT (context);
+ g_atomic_int_inc (&source->ref_count);
return source;
}
@@ -2078,12 +2059,11 @@ g_source_unref_internal (GSource *source,
GSourceCallbackFuncs *old_cb_funcs = NULL;
g_return_if_fail (source != NULL);
-
+
if (!have_lock && context)
LOCK_CONTEXT (context);
- source->ref_count--;
- if (source->ref_count == 0)
+ if (g_atomic_int_dec_and_test (&source->ref_count))
{
TRACE (GLIB_SOURCE_BEFORE_FREE (source, context,
source->source_funcs->finalize));
@@ -2107,20 +2087,20 @@ g_source_unref_internal (GSource *source,
{
/* Temporarily increase the ref count again so that GSource methods
* can be called from finalize(). */
- source->ref_count++;
+ g_atomic_int_inc (&source->ref_count);
if (context)
UNLOCK_CONTEXT (context);
source->source_funcs->finalize (source);
if (context)
LOCK_CONTEXT (context);
- source->ref_count--;
+ g_atomic_int_add (&source->ref_count, -1);
}
if (old_cb_funcs)
{
/* Temporarily increase the ref count again so that GSource methods
* can be called from callback_funcs.unref(). */
- source->ref_count++;
+ g_atomic_int_inc (&source->ref_count);
if (context)
UNLOCK_CONTEXT (context);
@@ -2128,7 +2108,7 @@ g_source_unref_internal (GSource *source,
if (context)
LOCK_CONTEXT (context);
- source->ref_count--;
+ g_atomic_int_add (&source->ref_count, -1);
}
g_free (source->name);
@@ -3201,7 +3181,7 @@ g_main_dispatch (GMainContext *context)
}
}
- SOURCE_UNREF (source, context);
+ g_source_unref_internal (source, context, TRUE);
}
g_ptr_array_set_size (context->pending_dispatches, 0);
@@ -3440,7 +3420,7 @@ g_main_context_prepare (GMainContext *context,
for (i = 0; i < context->pending_dispatches->len; i++)
{
if (context->pending_dispatches->pdata[i])
- SOURCE_UNREF ((GSource *)context->pending_dispatches->pdata[i], context);
+ g_source_unref_internal ((GSource *)context->pending_dispatches->pdata[i], context, TRUE);
}
g_ptr_array_set_size (context->pending_dispatches, 0);
@@ -3788,7 +3768,7 @@ g_main_context_check (GMainContext *context,
if (source->flags & G_SOURCE_READY)
{
- source->ref_count++;
+ g_source_ref (source);
g_ptr_array_add (context->pending_dispatches, source);
n_ready++;
--
2.31.1
From 323d0c7658a9a44efc327840c0667044a4b98f89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= <sebastian@centricular.com>
Date: Mon, 3 Feb 2020 15:38:28 +0200
Subject: [PATCH 2/5] GMainContext - Fix GSource iterator if iteration can
modify the list
We first have to ref the next source and then unref the previous one.
This might be the last reference to the previous source, and freeing the
previous source might unref and free the next one which would then leave
use with a dangling pointer here.
Fixes https://gitlab.gnome.org/GNOME/glib/issues/2031
---
glib/gmain.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/glib/gmain.c b/glib/gmain.c
index 5b91c3117..a3ea1d36c 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -965,13 +965,17 @@ g_source_iter_next (GSourceIter *iter, GSource **source)
* GSourceList to be removed from source_lists (if iter->source is
* the only source in its list, and it is destroyed), so we have to
* keep it reffed until after we advance iter->current_list, above.
+ *
+ * Also we first have to ref the next source before unreffing the
+ * previous one as unreffing the previous source can potentially
+ * free the next one.
*/
+ if (next_source && iter->may_modify)
+ g_source_ref (next_source);
if (iter->source && iter->may_modify)
g_source_unref_internal (iter->source, iter->context, TRUE);
iter->source = next_source;
- if (iter->source && iter->may_modify)
- g_source_ref (iter->source);
*source = iter->source;
return *source != NULL;
--
2.31.1
From fc051ec83d8894dd754bf364562ba9be9ff999fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= <sebastian@centricular.com>
Date: Mon, 3 Feb 2020 15:35:51 +0200
Subject: [PATCH 3/5] GMainContext - Fix memory leaks and memory corruption
when freeing sources while freeing a context
Instead of destroying sources directly while freeing the context, and
potentially freeing them if this was the last reference to them, collect
new references of all sources in a separate list before and at the same
time invalidate their context so that they can't access it anymore. Only
once all sources have their context invalidated, destroy them while
still keeping a reference to them. Once all sources are destroyed we get
rid of the additional references and free them if nothing else keeps a
reference to them anymore.
This fixes a regression introduced by 26056558be in 2012.
The previous code that invalidated the context of each source and then
destroyed it before going to the next source without keeping an
additional reference caused memory leaks or memory corruption depending
on the order of the sources in the sources lists.
If a source was destroyed it might happen that this was the last
reference to this source, and it would then be freed. This would cause
the finalize function to be called, which might destroy and unref
another source and potentially free it. This other source would then
either
- go through the normal free logic and change the intern linked list
between the sources, while other sources that are unreffed as part of
the main context freeing would not. As such the list would be in an
inconsistent state and we might dereference freed memory.
- go through the normal destroy and free logic but because the context
pointer was already invalidated it would simply mark the source as
destroyed without actually removing it from the context. This would
then cause a memory leak because the reference owned by the context is
not freed.
Fixes https://github.com/gtk-rs/glib/issues/583 while still keeping
https://bugzilla.gnome.org/show_bug.cgi?id=661767 fixes.
---
glib/gmain.c | 35 ++++++++++++++++++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/glib/gmain.c b/glib/gmain.c
index a3ea1d36c..1c249ad02 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -534,6 +534,7 @@ g_main_context_unref (GMainContext *context)
GSourceIter iter;
GSource *source;
GList *sl_iter;
+ GSList *s_iter, *remaining_sources = NULL;
GSourceList *list;
guint i;
@@ -553,10 +554,30 @@ g_main_context_unref (GMainContext *context)
/* g_source_iter_next() assumes the context is locked. */
LOCK_CONTEXT (context);
- g_source_iter_init (&iter, context, TRUE);
+
+ /* First collect all remaining sources from the sources lists and store a
+ * new reference in a separate list. Also set the context of the sources
+ * to NULL so that they can't access a partially destroyed context anymore.
+ *
+ * We have to do this first so that we have a strong reference to all
+ * sources and destroying them below does not also free them, and so that
+ * none of the sources can access the context from their finalize/dispose
+ * functions. */
+ g_source_iter_init (&iter, context, FALSE);
while (g_source_iter_next (&iter, &source))
{
source->context = NULL;
+ remaining_sources = g_slist_prepend (remaining_sources, g_source_ref (source));
+ }
+ g_source_iter_clear (&iter);
+
+ /* Next destroy all sources. As we still hold a reference to all of them,
+ * this won't cause any of them to be freed yet and especially prevents any
+ * source that unrefs another source from its finalize function to be freed.
+ */
+ for (s_iter = remaining_sources; s_iter; s_iter = s_iter->next)
+ {
+ source = s_iter->data;
g_source_destroy_internal (source, context, TRUE);
}
UNLOCK_CONTEXT (context);
@@ -581,6 +602,18 @@ g_main_context_unref (GMainContext *context)
g_cond_clear (&context->cond);
g_free (context);
+
+ /* And now finally get rid of our references to the sources. This will cause
+ * them to be freed unless something else still has a reference to them. Due
+ * to setting the context pointers in the sources to NULL above, this won't
+ * ever access the context or the internal linked list inside the GSource.
+ * We already removed the sources completely from the context above. */
+ for (s_iter = remaining_sources; s_iter; s_iter = s_iter->next)
+ {
+ source = s_iter->data;
+ g_source_unref_internal (source, NULL, FALSE);
+ }
+ g_slist_free (remaining_sources);
}
/* Helper function used by mainloop/overflow test.
--
2.31.1
From 1d16e92028f235ed9cd786070832d5bd71017661 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= <sebastian@centricular.com>
Date: Tue, 11 Feb 2020 09:34:38 +0200
Subject: [PATCH 4/5] GMainContext - Move mutex unlocking in destructor right
before freeing the mutex
This does not have any behaviour changes but is cleaner. The mutex is
only unlocked now after all operations on the context are done and right
before freeing the mutex and the context itself.
---
glib/gmain.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/glib/gmain.c b/glib/gmain.c
index 1c249ad02..44e6ed0c3 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -580,7 +580,6 @@ g_main_context_unref (GMainContext *context)
source = s_iter->data;
g_source_destroy_internal (source, context, TRUE);
}
- UNLOCK_CONTEXT (context);
for (sl_iter = context->source_lists; sl_iter; sl_iter = sl_iter->next)
{
@@ -591,6 +590,7 @@ g_main_context_unref (GMainContext *context)
g_hash_table_destroy (context->sources);
+ UNLOCK_CONTEXT (context);
g_mutex_clear (&context->mutex);
g_ptr_array_free (context->pending_dispatches, TRUE);
--
2.31.1
From 02ad7294ad5895178df73a6cd8546c6e67097493 Mon Sep 17 00:00:00 2001
From: Benjamin Berg <bberg@redhat.com>
Date: Tue, 13 Oct 2020 15:09:43 +0200
Subject: [PATCH 5/5] gmain: Fix possible locking issue in source unref
When unref'ing child sources, the lock is already held. But instead of
passing TRUE to g_source_unref_internal it currently passes whether the
lock was already held outside of the current invocation. Just pass TRUE
to fix this possible issue.
---
glib/gmain.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/glib/gmain.c b/glib/gmain.c
index 44e6ed0c3..95992253d 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -2164,7 +2164,7 @@ g_source_unref_internal (GSource *source,
g_slist_remove (source->priv->child_sources, child_source);
child_source->priv->parent_source = NULL;
- g_source_unref_internal (child_source, context, have_lock);
+ g_source_unref_internal (child_source, context, TRUE);
}
g_slice_free (GSourcePrivate, source->priv);
--
2.31.1