pulse-server: detach clients from server

When the server or client goes away, detach the client
from the server to avoid potential use-after-free issues
that might occur if the client causes the
unloading of the server it is connected to.

E.g.:

  pactl load-module module-protocol-native-tcp port=4713
  pactl -s localhost:4713 unload-module module-native-protocol-tcp

See #1240.
This commit is contained in:
Barnabás Pőcze 2021-06-01 21:38:46 +02:00
parent 3889ea5277
commit 4c13eced55

View file

@ -5336,6 +5336,24 @@ static int client_free_stream(void *item, void *data)
return 0; return 0;
} }
/*
* tries to detach the client from the server,
* but it does not drop the server's reference
*/
static bool client_detach(struct client *client)
{
if (client->server == NULL)
return false;
pw_log_info(NAME" %p: client %p detaching", client->server, client);
/* remove from the `server->clients` list */
spa_list_remove(&client->link);
client->server = NULL;
return true;
}
static void client_disconnect(struct client *client) static void client_disconnect(struct client *client)
{ {
struct impl *impl = client->impl; struct impl *impl = client->impl;
@ -5343,8 +5361,10 @@ static void client_disconnect(struct client *client)
if (client->disconnect) if (client->disconnect)
return; return;
/* the client must be detached from the server to disconnect */
spa_assert(client->server == NULL);
client->disconnect = true; client->disconnect = true;
spa_list_remove(&client->link);
spa_list_append(&impl->cleanup_clients, &client->link); spa_list_append(&impl->cleanup_clients, &client->link);
pw_map_for_each(&client->streams, client_free_stream, client); pw_map_for_each(&client->streams, client_free_stream, client);
@ -5353,6 +5373,7 @@ static void client_disconnect(struct client *client)
pw_loop_destroy_source(impl->loop, client->source); pw_loop_destroy_source(impl->loop, client->source);
if (client->manager) if (client->manager)
pw_manager_destroy(client->manager); pw_manager_destroy(client->manager);
} }
static void client_free(struct client *client) static void client_free(struct client *client)
@ -5364,8 +5385,10 @@ static void client_free(struct client *client)
pw_log_info(NAME" %p: client %p free", impl, client); pw_log_info(NAME" %p: client %p free", impl, client);
client_detach(client);
client_disconnect(client); client_disconnect(client);
/* remove from the `impl->cleanup_clients` list */
spa_list_remove(&client->link); spa_list_remove(&client->link);
spa_list_consume(p, &client->pending_samples, link) spa_list_consume(p, &client->pending_samples, link)
@ -5632,6 +5655,7 @@ on_client_data(void *data, int fd, uint32_t mask)
goto error; goto error;
} }
done: done:
/* drop the reference that was acquired at the beginning of the function */
client_unref(client); client_unref(client);
return; return;
@ -5641,8 +5665,18 @@ error:
pw_log_info(NAME" %p: client:%p [%s] disconnected", impl, client, client->name); pw_log_info(NAME" %p: client:%p [%s] disconnected", impl, client, client->name);
SPA_FALLTHROUGH; SPA_FALLTHROUGH;
case -EPROTO: case -EPROTO:
client_disconnect(client); /*
* drop the server's reference to the client
* (if it hasn't been dropped already),
* it is guaranteed that this will not call `client_free()`
* since at the beginning of this function an extra reference
* has been acquired which will keep the client alive
*/
if (client_detach(client))
client_unref(client); client_unref(client);
/* then disconnect the client */
client_disconnect(client);
break; break;
default: default:
pw_log_error(NAME" %p: client:%p [%s] error %d (%s)", impl, pw_log_error(NAME" %p: client:%p [%s] error %d (%s)", impl,
@ -5856,8 +5890,12 @@ void server_free(struct server *server)
pw_log_debug(NAME" %p: free server %p", impl, server); pw_log_debug(NAME" %p: free server %p", impl, server);
spa_list_remove(&server->link); spa_list_remove(&server->link);
spa_list_for_each_safe(c, t, &server->clients, link)
spa_list_for_each_safe(c, t, &server->clients, link) {
spa_assert_se(client_detach(c));
client_unref(c); client_unref(c);
}
if (server->source) if (server->source)
pw_loop_destroy_source(impl->loop, server->source); pw_loop_destroy_source(impl->loop, server->source);
if (server->addr.ss_family == AF_UNIX && !server->activated) if (server->addr.ss_family == AF_UNIX && !server->activated)