spa: support: loop: fix use-after-free when loop is reentered

The core of the issue is the following: what happens if an
active source is destroyed before it could be dispatched?

For loop-managed sources (`struct source_impl`) this was addressed
by storing all destroyed sources in a list, and only freeing them
after dispatching has been finished. (0eb73f0f06)
This approach works for both strictly single-threaded
and `pw_thread_loop` loops assuming the loop is not
reentered.

However, if the loop is reentered, there can still be issues.
Assume that in one iteration sources A and B are active,
and returned from the system call, and source B is destroyed
before the loop starts dispatching. Consider what happens when
"A" is dispatched first, and it reenters the loop with timeout 0.
Imagine there are no new events, so `loop_iterate()` will immediately
return, but it will first destroy everything in the destroy list
(this is done at the end of `loop_iterate()`).
And herein lies the problem. In the previous iteration,
there exists a `spa_poll_event` object which points to source "B",
but that has just been destroyed at the end of the recursive
iteration. This will trigger a use-after-free once the previous
iteration inspects it.

Fix that by processing the destroy list right after first
processing the returned `spa_poll_event` objects, and
"detach" the source from the loop and its iterations
in `process_destroy()` before the source is destroyed.

See #2114 #2147
This commit is contained in:
Barnabás Pőcze 2022-02-19 13:31:01 +01:00 committed by Wim Taymans
parent 529b6fd1b8
commit 7647ea7c83

View file

@ -132,23 +132,35 @@ static int loop_update_source(void *object, struct spa_source *source)
return spa_system_pollfd_mod(impl->system, impl->poll_fd, source->fd, source->mask, source); return spa_system_pollfd_mod(impl->system, impl->poll_fd, source->fd, source->mask, source);
} }
static int loop_remove_source(void *object, struct spa_source *source) static void detach_source(struct spa_source *source)
{ {
struct impl *impl = object;
struct spa_poll_event *e; struct spa_poll_event *e;
spa_assert(source->loop == &impl->loop); source->loop = NULL;
source->rmask = 0;
if ((e = source->priv)) { if ((e = source->priv)) {
/* active in an iteration of the loop, remove it from there */ /* active in an iteration of the loop, remove it from there */
e->data = NULL; e->data = NULL;
source->priv = NULL; source->priv = NULL;
} }
source->loop = NULL; }
source->rmask = 0;
static int remove_from_poll(struct impl *impl, struct spa_source *source)
{
spa_assert(source->loop == &impl->loop);
return spa_system_pollfd_del(impl->system, impl->poll_fd, source->fd); return spa_system_pollfd_del(impl->system, impl->poll_fd, source->fd);
} }
static int loop_remove_source(void *object, struct spa_source *source)
{
int res = remove_from_poll(object, source);
detach_source(source);
return res;
}
static void flush_items(struct impl *impl) static void flush_items(struct impl *impl)
{ {
uint32_t index; uint32_t index;
@ -337,11 +349,19 @@ static void loop_leave(void *object)
impl->thread = 0; impl->thread = 0;
} }
static inline void free_source(struct source_impl *s)
{
detach_source(&s->source);
free(s);
}
static inline void process_destroy(struct impl *impl) static inline void process_destroy(struct impl *impl)
{ {
struct source_impl *source, *tmp; struct source_impl *source, *tmp;
spa_list_for_each_safe(source, tmp, &impl->destroy_list, link) spa_list_for_each_safe(source, tmp, &impl->destroy_list, link)
free(source); free_source(source);
spa_list_init(&impl->destroy_list); spa_list_init(&impl->destroy_list);
} }
@ -371,11 +391,16 @@ static int loop_iterate(void *object, int timeout)
e->data = NULL; e->data = NULL;
s->priv = &ep[i]; s->priv = &ep[i];
} }
if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list)))
process_destroy(impl);
for (i = 0; i < nfds; i++) { for (i = 0; i < nfds; i++) {
struct spa_source *s = ep[i].data; struct spa_source *s = ep[i].data;
if (SPA_LIKELY(s && s->rmask && s->loop)) if (SPA_LIKELY(s && s->rmask))
s->func(s); s->func(s);
} }
for (i = 0; i < nfds; i++) { for (i = 0; i < nfds; i++) {
struct spa_source *s = ep[i].data; struct spa_source *s = ep[i].data;
if (SPA_LIKELY(s)) { if (SPA_LIKELY(s)) {
@ -383,8 +408,6 @@ static int loop_iterate(void *object, int timeout)
s->priv = NULL; s->priv = NULL;
} }
} }
if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list)))
process_destroy(impl);
return nfds; return nfds;
} }
@ -748,14 +771,18 @@ static void loop_destroy_source(void *object, struct spa_source *source)
if (s->fallback) if (s->fallback)
loop_destroy_source(s->impl, s->fallback); loop_destroy_source(s->impl, s->fallback);
else if (source->loop) else
loop_remove_source(s->impl, source); remove_from_poll(s->impl, source);
if (source->fd != -1 && s->close) { if (source->fd != -1 && s->close) {
spa_system_close(s->impl->system, source->fd); spa_system_close(s->impl->system, source->fd);
source->fd = -1; source->fd = -1;
} }
spa_list_insert(&s->impl->destroy_list, &s->link);
if (!s->impl->polling)
free_source(s);
else
spa_list_insert(&s->impl->destroy_list, &s->link);
} }
static const struct spa_loop_methods impl_loop = { static const struct spa_loop_methods impl_loop = {
@ -826,8 +853,6 @@ static int impl_clear(struct spa_handle *handle)
spa_list_consume(source, &impl->source_list, link) spa_list_consume(source, &impl->source_list, link)
loop_destroy_source(impl, &source->source); loop_destroy_source(impl, &source->source);
process_destroy(impl);
spa_system_close(impl->system, impl->ack_fd); spa_system_close(impl->system, impl->ack_fd);
spa_system_close(impl->system, impl->poll_fd); spa_system_close(impl->system, impl->poll_fd);