mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
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:
parent
529b6fd1b8
commit
7647ea7c83
1 changed files with 39 additions and 14 deletions
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue