diff options
author | Slava Monich <slava.monich@jolla.com> | 2017-10-23 12:17:30 +0300 |
---|---|---|
committer | Denis Kenzior <denkenz@gmail.com> | 2017-10-23 15:25:20 -0500 |
commit | bb637a12c58f8a3a74a775a7773fc855d0cd0cce (patch) | |
tree | bccf133c9e7fa697575b7c986ed8508d69d198a8 /gatchat | |
parent | 2bc262b3af1894e218a281b379fd1f9218e38281 (diff) | |
download | ofono-bb637a12c58f8a3a74a775a7773fc855d0cd0cce.tar.gz |
gatmux: Remove finalized watches from the list
Leaving them there may result in invalid reads like this:
==2312== Invalid read of size 4
==2312== at 0xAB8C0: dispatch_sources (gatmux.c:134)
==2312== by 0xAC5D3: channel_close (gatmux.c:479)
==2312== by 0x4AE8885: g_io_channel_shutdown (giochannel.c:523)
==2312== by 0x4AE8A1D: g_io_channel_unref (giochannel.c:240)
==2312== by 0xAC423: watch_finalize (gatmux.c:426)
==2312== by 0x4AF2CC9: g_source_unref_internal (gmain.c:2048)
==2312== by 0x4AF44E1: g_source_destroy_internal (gmain.c:1230)
==2312== by 0x4AF44E1: g_source_destroy (gmain.c:1256)
==2312== by 0x4AF5257: g_source_remove (gmain.c:2282)
==2312== by 0xAB5CB: io_shutdown (gatio.c:325)
==2312== by 0xAB667: g_at_io_unref (gatio.c:345)
==2312== by 0xA72C7: at_chat_unref (gatchat.c:972)
==2312== by 0xA829B: g_at_chat_unref (gatchat.c:1446)
==2312== Address 0x51420f0 is 56 bytes inside a block of size 60 free'd
==2312== at 0x4840B28: free (vg_replace_malloc.c:530)
==2312== by 0x4AF2D33: g_source_unref_internal (gmain.c:2075)
==2312== by 0x4AF44E1: g_source_destroy_internal (gmain.c:1230)
==2312== by 0x4AF44E1: g_source_destroy (gmain.c:1256)
==2312== by 0x4AF5257: g_source_remove (gmain.c:2282)
==2312== by 0xAB46B: g_at_io_set_write_handler (gatio.c:283)
==2312== by 0xA713F: at_chat_suspend (gatchat.c:938)
==2312== by 0xA72B7: at_chat_unref (gatchat.c:971)
==2312== by 0xA829B: g_at_chat_unref (gatchat.c:1446)
==2312== Block was alloc'd at
==2312== at 0x4841BF0: calloc (vg_replace_malloc.c:711)
==2312== by 0x4AFB117: g_malloc0 (gmem.c:124)
==2312== by 0x4AF401F: g_source_new (gmain.c:892)
==2312== by 0xAC6A7: channel_create_watch (gatmux.c:506)
==2312== by 0x4AE7C4F: g_io_add_watch_full (giochannel.c:649)
==2312== by 0xAB4EB: g_at_io_set_write_handler (gatio.c:297)
==2312== by 0xA7103: chat_wakeup_writer (gatchat.c:931)
==2312== by 0xA753F: at_chat_send_common (gatchat.c:1045)
==2312== by 0xA850F: g_at_chat_send (gatchat.c:1502)
It's also necessary to add additional references to the sources
for the duration of the dispatch_sources loop because any source
can be removed when any callback is invoked (and not necessarily
the one being dispatched).
Diffstat (limited to 'gatchat')
-rw-r--r-- | gatchat/gatmux.c | 109 |
1 files changed, 77 insertions, 32 deletions
diff --git a/gatchat/gatmux.c b/gatchat/gatmux.c index 0e275b53..909eca62 100644 --- a/gatchat/gatmux.c +++ b/gatchat/gatmux.c @@ -116,66 +116,109 @@ static inline void debug(GAtMux *mux, const char *format, ...) static void dispatch_sources(GAtMuxChannel *channel, GIOCondition condition) { - GAtMuxWatch *source; GSList *c; GSList *p; - GSList *t; + GSList *refs; + + /* + * Don't reference destroyed sources, they may have zero reference + * count if this function is invoked from the source's finalize + * callback, in which case incrementing and then decrementing + * the count would result in double free (first when we decrement + * the reference count and then when we return from the finalize + * callback). + */ p = NULL; - c = channel->sources; + refs = NULL; - while (c) { - gboolean destroy = FALSE; + for (c = channel->sources; c; c = c->next) { + GSource *s = c->data; + + if (!g_source_is_destroyed(s)) { + GSList *l = g_slist_append(NULL, g_source_ref(s)); + + if (p) + p->next = l; + else + refs = l; - source = c->data; + p = l; + } + } - debug(channel->mux, "checking source: %p", source); + /* + * Keep the references to all sources for the duration of the loop. + * Callbacks may add and remove the sources, i.e. channel->sources + * may keep changing during the loop. + */ - if (condition & source->condition) { + for (c = refs; c; c = c->next) { + GAtMuxWatch *w = c->data; + GSource *s = &w->source; + + if (g_source_is_destroyed(s)) + continue; + + debug(channel->mux, "checking source: %p", s); + + if (condition & w->condition) { gpointer user_data = NULL; GSourceFunc callback = NULL; - GSourceCallbackFuncs *cb_funcs; - gpointer cb_data; - gboolean (*dispatch) (GSource *, GSourceFunc, gpointer); - - debug(channel->mux, "dispatching source: %p", source); + GSourceCallbackFuncs *cb_funcs = s->callback_funcs; + gpointer cb_data = s->callback_data; + gboolean destroy; - dispatch = source->source.source_funcs->dispatch; - cb_funcs = source->source.callback_funcs; - cb_data = source->source.callback_data; + debug(channel->mux, "dispatching source: %p", s); - if (cb_funcs) + if (cb_funcs) { cb_funcs->ref(cb_data); + cb_funcs->get(cb_data, s, &callback, + &user_data); + } - if (cb_funcs) - cb_funcs->get(cb_data, (GSource *) source, - &callback, &user_data); - - destroy = !dispatch((GSource *) source, callback, - user_data); + destroy = !s->source_funcs->dispatch(s, callback, + user_data); if (cb_funcs) cb_funcs->unref(cb_data); + + if (destroy) { + debug(channel->mux, "removing source: %p", s); + g_source_destroy(s); + } } + } - if (destroy) { - debug(channel->mux, "removing source: %p", source); + /* + * Remove destroyed sources from channel->sources. During this + * loop we are not invoking any callbacks, so the consistency is + * guaranteed. + */ - g_source_destroy((GSource *) source); + p = NULL; + c = channel->sources; + while (c) { + GSList *n = c->next; + GSource *s = c->data; + + if (g_source_is_destroyed(s)) { if (p) - p->next = c->next; + p->next = n; else - channel->sources = c->next; + channel->sources = n; - t = c; - c = c->next; - g_slist_free_1(t); + g_slist_free_1(c); } else { p = c; - c = c->next; } + + c = n; } + + /* Release temporary references */ + g_slist_free_full(refs, (GDestroyNotify) g_source_unref); } static gboolean received_data(GIOChannel *channel, GIOCondition cond, @@ -422,7 +465,9 @@ static gboolean watch_dispatch(GSource *source, GSourceFunc callback, static void watch_finalize(GSource *source) { GAtMuxWatch *watch = (GAtMuxWatch *) source; + GAtMuxChannel *dlc = (GAtMuxChannel *) watch->channel; + dlc->sources = g_slist_remove(dlc->sources, watch); g_io_channel_unref(watch->channel); } |