Skip to content

Commit 97367c9

Browse files
committed
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: folkert <[email protected]> Cc: <[email protected]> Link: https://lore.kernel.org/r/[email protected] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent eda80d7 commit 97367c9

File tree

1 file changed

+27
-12
lines changed

1 file changed

+27
-12
lines changed

sound/core/seq/seq_ports.c

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -514,18 +514,18 @@ static int check_and_subscribe_port(struct snd_seq_client *client,
514514
return err;
515515
}
516516

517-
static void delete_and_unsubscribe_port(struct snd_seq_client *client,
518-
struct snd_seq_client_port *port,
519-
struct snd_seq_subscribers *subs,
520-
bool is_src, bool ack)
517+
/* called with grp->list_mutex held */
518+
static void __delete_and_unsubscribe_port(struct snd_seq_client *client,
519+
struct snd_seq_client_port *port,
520+
struct snd_seq_subscribers *subs,
521+
bool is_src, bool ack)
521522
{
522523
struct snd_seq_port_subs_info *grp;
523524
struct list_head *list;
524525
bool empty;
525526

526527
grp = is_src ? &port->c_src : &port->c_dest;
527528
list = is_src ? &subs->src_list : &subs->dest_list;
528-
down_write(&grp->list_mutex);
529529
write_lock_irq(&grp->list_lock);
530530
empty = list_empty(list);
531531
if (!empty)
@@ -535,6 +535,18 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client,
535535

536536
if (!empty)
537537
unsubscribe_port(client, port, grp, &subs->info, ack);
538+
}
539+
540+
static void delete_and_unsubscribe_port(struct snd_seq_client *client,
541+
struct snd_seq_client_port *port,
542+
struct snd_seq_subscribers *subs,
543+
bool is_src, bool ack)
544+
{
545+
struct snd_seq_port_subs_info *grp;
546+
547+
grp = is_src ? &port->c_src : &port->c_dest;
548+
down_write(&grp->list_mutex);
549+
__delete_and_unsubscribe_port(client, port, subs, is_src, ack);
538550
up_write(&grp->list_mutex);
539551
}
540552

@@ -590,27 +602,30 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector,
590602
struct snd_seq_client_port *dest_port,
591603
struct snd_seq_port_subscribe *info)
592604
{
593-
struct snd_seq_port_subs_info *src = &src_port->c_src;
605+
struct snd_seq_port_subs_info *dest = &dest_port->c_dest;
594606
struct snd_seq_subscribers *subs;
595607
int err = -ENOENT;
596608

597-
down_write(&src->list_mutex);
609+
/* always start from deleting the dest port for avoiding concurrent
610+
* deletions
611+
*/
612+
down_write(&dest->list_mutex);
598613
/* look for the connection */
599-
list_for_each_entry(subs, &src->list_head, src_list) {
614+
list_for_each_entry(subs, &dest->list_head, dest_list) {
600615
if (match_subs_info(info, &subs->info)) {
601-
atomic_dec(&subs->ref_count); /* mark as not ready */
616+
__delete_and_unsubscribe_port(dest_client, dest_port,
617+
subs, false,
618+
connector->number != dest_client->number);
602619
err = 0;
603620
break;
604621
}
605622
}
606-
up_write(&src->list_mutex);
623+
up_write(&dest->list_mutex);
607624
if (err < 0)
608625
return err;
609626

610627
delete_and_unsubscribe_port(src_client, src_port, subs, true,
611628
connector->number != src_client->number);
612-
delete_and_unsubscribe_port(dest_client, dest_port, subs, false,
613-
connector->number != dest_client->number);
614629
kfree(subs);
615630
return 0;
616631
}

0 commit comments

Comments
 (0)