mirror of git://git.sysmocom.de/ofono
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).
This commit is contained in:
parent
2bc262b3af
commit
bb637a12c5
135
gatchat/gatmux.c
135
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;
|
||||
refs = NULL;
|
||||
|
||||
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;
|
||||
|
||||
p = l;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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 = s->callback_funcs;
|
||||
gpointer cb_data = s->callback_data;
|
||||
gboolean destroy;
|
||||
|
||||
debug(channel->mux, "dispatching source: %p", s);
|
||||
|
||||
if (cb_funcs) {
|
||||
cb_funcs->ref(cb_data);
|
||||
cb_funcs->get(cb_data, s, &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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove destroyed sources from channel->sources. During this
|
||||
* loop we are not invoking any callbacks, so the consistency is
|
||||
* guaranteed.
|
||||
*/
|
||||
|
||||
p = NULL;
|
||||
c = channel->sources;
|
||||
|
||||
while (c) {
|
||||
gboolean destroy = FALSE;
|
||||
|
||||
source = c->data;
|
||||
|
||||
debug(channel->mux, "checking source: %p", source);
|
||||
|
||||
if (condition & source->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);
|
||||
|
||||
dispatch = source->source.source_funcs->dispatch;
|
||||
cb_funcs = source->source.callback_funcs;
|
||||
cb_data = source->source.callback_data;
|
||||
|
||||
if (cb_funcs)
|
||||
cb_funcs->ref(cb_data);
|
||||
|
||||
if (cb_funcs)
|
||||
cb_funcs->get(cb_data, (GSource *) source,
|
||||
&callback, &user_data);
|
||||
|
||||
destroy = !dispatch((GSource *) source, callback,
|
||||
user_data);
|
||||
|
||||
if (cb_funcs)
|
||||
cb_funcs->unref(cb_data);
|
||||
}
|
||||
|
||||
if (destroy) {
|
||||
debug(channel->mux, "removing source: %p", source);
|
||||
|
||||
g_source_destroy((GSource *) source);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue