[PATCH wayland v4 07/11] client: Plug a race in proxy destruction vs. dispatch
Derek Foreman
derekf at osg.samsung.com
Wed Jan 3 22:00:19 UTC 2018
On 2017-12-28 01:53 PM, Daniel Stone wrote:
> Closures created to hold events which will be dispatched on the client,
> take a reference to the proxy for the object the event was sent to, as
> well as the proxies for all objects referenced in that event.
>
> These references are dropped immediately before dispatch, with the
> display lock also being released. This leaves the potential for a
> vanishingly small race, where another thread drops the last reference
> on one of the proxies used in an event as it is being dispatched.
>
> Fix this by splitting decrease_closure_args_refcount into two functions:
> one which validates the objects (to ensure that clients are not returned
> objects which they have destroyed), and another which unrefs all proxies
> on the closure (object event was sent to, all referenced objects) as
> well as the closure itself. For symmetry, increase_closure_args_refcount
> is now the place where the refcount for the proxy for the object the
> event was sent to, is increased.
>
> This also happens to fix a bug: previously, if an event was sent to a
> client-destroyed object, and the event had object arguments, a reference
> would be leaked on the proxy for each of the object arguments.
>
> Found by inspection whilst reviewing the zombie-FD-leak series.
>
> Signed-off-by: Daniel Stone <daniels at collabora.com>
> Cc: Jonas Ã…dahl <jadahl at gmail.com>
> Cc: Derek Foreman <derekf at osg.samsung.com>
> Cc: Pekka Paalanen <pekka.paalanen at collabora.co.uk>
> ---
> src/wayland-client.c | 57 +++++++++++++++++++++++++++++++++++++---------------
> 1 file changed, 41 insertions(+), 16 deletions(-)
>
> diff --git a/src/wayland-client.c b/src/wayland-client.c
> index f3d71b0e..987418a1 100644
> --- a/src/wayland-client.c
> +++ b/src/wayland-client.c
> @@ -236,7 +236,7 @@ wl_proxy_unref(struct wl_proxy *proxy)
> }
>
> static void
> -decrease_closure_args_refcount(struct wl_closure *closure)
> +validate_closure_objects(struct wl_closure *closure)
> {
> const char *signature;
> struct argument_details arg;
> @@ -251,16 +251,44 @@ decrease_closure_args_refcount(struct wl_closure *closure)
> case 'n':
> case 'o':
> proxy = (struct wl_proxy *) closure->args[i].o;
> - if (proxy) {
> - if (proxy->flags & WL_PROXY_FLAG_DESTROYED)
> - closure->args[i].o = NULL;
> + if (proxy && proxy->flags & WL_PROXY_FLAG_DESTROYED)
> + closure->args[i].o = NULL;
> + break;
> + default:
> + break;
> + }
> + }
> +}
> +
> +/* Destroys a closure which was demarshaled for dispatch; unrefs all the
> + * proxies in its arguments, as well as its own proxy, and destroys the
> + * closure itself. */
> +static void
> +destroy_queued_closure(struct wl_closure *closure)
> +{
> + const char *signature;
> + struct argument_details arg;
> + struct wl_proxy *proxy;
> + int i, count;
> +
> + signature = closure->message->signature;
> + count = arg_count_for_signature(signature);
> + for (i = 0; i < count; i++) {
> + signature = get_next_argument(signature, &arg);
> + switch (arg.type) {
> + case 'n':
> + case 'o':
> + proxy = (struct wl_proxy *) closure->args[i].o;
> + if (proxy)
> wl_proxy_unref(proxy);
> - }
> break;
> default:
> break;
> }
> }
> +
> + wl_proxy_unref(closure->proxy);
> + wl_closure_destroy(closure);
> }
>
> static void
> @@ -272,9 +300,7 @@ wl_event_queue_release(struct wl_event_queue *queue)
> closure = container_of(queue->event_list.next,
> struct wl_closure, link);
> wl_list_remove(&closure->link);
> - decrease_closure_args_refcount(closure);
> - wl_proxy_unref(closure->proxy);
> - wl_closure_destroy(closure);
> + destroy_queued_closure(closure);
> }
> }
>
> @@ -1232,6 +1258,8 @@ increase_closure_args_refcount(struct wl_closure *closure)
> break;
> }
> }
> +
> + closure->proxy->refcount++;
> }
>
> static int
> @@ -1273,9 +1301,8 @@ queue_event(struct wl_display *display, int len)
> return -1;
> }
>
> - increase_closure_args_refcount(closure);
> - proxy->refcount++;
> closure->proxy = proxy;
> + increase_closure_args_refcount(closure);
Ok, queue_event() is always called with display lock held so there's no
similar increase_closure_args race.
I guess I'd have done the proxy->refcount++ move into
increase_closure_args_refcount() in a separate patch.
>
> if (proxy == &display->proxy)
> queue = &display->display_queue;
> @@ -1302,13 +1329,11 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue)
>
> /* Verify that the receiving object is still valid by checking if has
> * been destroyed by the application. */
> -
> - decrease_closure_args_refcount(closure);
> + validate_closure_objects(closure);
> proxy = closure->proxy;
> proxy_destroyed = !!(proxy->flags & WL_PROXY_FLAG_DESTROYED);
> - wl_proxy_unref(proxy);
> if (proxy_destroyed) {
> - wl_closure_destroy(closure);
> + destroy_queued_closure(closure);
> return;
> }
>
> @@ -1328,9 +1353,9 @@ dispatch_event(struct wl_display *display, struct wl_event_queue *queue)
> &proxy->object, opcode, proxy->user_data);
> }
>
> - wl_closure_destroy(closure);
> -
This (wl_closure_destroy occurring with lock held) was bothering me when
I noticed it while reviewing your other patches - though I'd probably
have moved it in a separate patch.
Nice catch.
Reviewed-by: Derek Foreman <derekf at osg.samsung.com>
> pthread_mutex_lock(&display->mutex);
> +
> + destroy_queued_closure(closure);
> }
>
> static int
>
More information about the wayland-devel
mailing list