Commit 97367c97 authored by Takashi Iwai's avatar Takashi Iwai

ALSA: seq: Fix racy deletion of subscriber

It turned out that the current implementation of the port subscription
is racy.  The subscription contains two linked lists, and we have to
add to or delete from both lists.  Since both connection and
disconnection procedures perform the same order for those two lists
(i.e. src list, then dest list), when a deletion happens during a
connection procedure, the src list may be deleted before the dest list
addition completes, and this may lead to a use-after-free or an Oops,
even though the access to both lists are protected via mutex.

The simple workaround for this race is to change the access order for
the disconnection, namely, dest list, then src list.  This assures
that the connection has been established when disconnecting, and also
the concurrent deletion can be avoided.
Reported-and-tested-by: default avatarfolkert <folkert@vanheusden.com>
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/20210801182754.GP890690@belle.intranet.vanheusden.com
Link: https://lore.kernel.org/r/20210803114312.2536-1-tiwai@suse.deSigned-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent eda80d7c
...@@ -514,10 +514,11 @@ static int check_and_subscribe_port(struct snd_seq_client *client, ...@@ -514,10 +514,11 @@ static int check_and_subscribe_port(struct snd_seq_client *client,
return err; return err;
} }
static void delete_and_unsubscribe_port(struct snd_seq_client *client, /* called with grp->list_mutex held */
struct snd_seq_client_port *port, static void __delete_and_unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_subscribers *subs, struct snd_seq_client_port *port,
bool is_src, bool ack) struct snd_seq_subscribers *subs,
bool is_src, bool ack)
{ {
struct snd_seq_port_subs_info *grp; struct snd_seq_port_subs_info *grp;
struct list_head *list; struct list_head *list;
...@@ -525,7 +526,6 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, ...@@ -525,7 +526,6 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client,
grp = is_src ? &port->c_src : &port->c_dest; grp = is_src ? &port->c_src : &port->c_dest;
list = is_src ? &subs->src_list : &subs->dest_list; list = is_src ? &subs->src_list : &subs->dest_list;
down_write(&grp->list_mutex);
write_lock_irq(&grp->list_lock); write_lock_irq(&grp->list_lock);
empty = list_empty(list); empty = list_empty(list);
if (!empty) if (!empty)
...@@ -535,6 +535,18 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, ...@@ -535,6 +535,18 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client,
if (!empty) if (!empty)
unsubscribe_port(client, port, grp, &subs->info, ack); unsubscribe_port(client, port, grp, &subs->info, ack);
}
static void delete_and_unsubscribe_port(struct snd_seq_client *client,
struct snd_seq_client_port *port,
struct snd_seq_subscribers *subs,
bool is_src, bool ack)
{
struct snd_seq_port_subs_info *grp;
grp = is_src ? &port->c_src : &port->c_dest;
down_write(&grp->list_mutex);
__delete_and_unsubscribe_port(client, port, subs, is_src, ack);
up_write(&grp->list_mutex); up_write(&grp->list_mutex);
} }
...@@ -590,27 +602,30 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, ...@@ -590,27 +602,30 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector,
struct snd_seq_client_port *dest_port, struct snd_seq_client_port *dest_port,
struct snd_seq_port_subscribe *info) struct snd_seq_port_subscribe *info)
{ {
struct snd_seq_port_subs_info *src = &src_port->c_src; struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
struct snd_seq_subscribers *subs; struct snd_seq_subscribers *subs;
int err = -ENOENT; int err = -ENOENT;
down_write(&src->list_mutex); /* always start from deleting the dest port for avoiding concurrent
* deletions
*/
down_write(&dest->list_mutex);
/* look for the connection */ /* look for the connection */
list_for_each_entry(subs, &src->list_head, src_list) { list_for_each_entry(subs, &dest->list_head, dest_list) {
if (match_subs_info(info, &subs->info)) { if (match_subs_info(info, &subs->info)) {
atomic_dec(&subs->ref_count); /* mark as not ready */ __delete_and_unsubscribe_port(dest_client, dest_port,
subs, false,
connector->number != dest_client->number);
err = 0; err = 0;
break; break;
} }
} }
up_write(&src->list_mutex); up_write(&dest->list_mutex);
if (err < 0) if (err < 0)
return err; return err;
delete_and_unsubscribe_port(src_client, src_port, subs, true, delete_and_unsubscribe_port(src_client, src_port, subs, true,
connector->number != src_client->number); connector->number != src_client->number);
delete_and_unsubscribe_port(dest_client, dest_port, subs, false,
connector->number != dest_client->number);
kfree(subs); kfree(subs);
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment