ALSA: seq: Fix double port list deletion
authorTakashi Iwai <tiwai@suse.de>
Tue, 16 Feb 2016 13:15:59 +0000 (14:15 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 25 Feb 2016 19:57:46 +0000 (11:57 -0800)
commit 13d5e5d4725c64ec06040d636832e78453f477b7 upstream.

The commit [7f0973e973cd: ALSA: seq: Fix lockdep warnings due to
double mutex locks] split the management of two linked lists (source
and destination) into two individual calls for avoiding the AB/BA
deadlock.  However, this may leave the possible double deletion of one
of two lists when the counterpart is being deleted concurrently.
It ends up with a list corruption, as revealed by syzkaller fuzzer.

This patch fixes it by checking the list emptiness and skipping the
deletion and the following process.

BugLink: http://lkml.kernel.org/r/CACT4Y+bay9qsrz6dQu31EcGaH9XwfW7o3oBzSQUG9fMszoh=Sg@mail.gmail.com
Fixes: 7f0973e973cd ('ALSA: seq: Fix lockdep warnings due to 'double mutex locks)
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Tested-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
sound/core/seq/seq_ports.c

index 67c91d22655213a952db16ee36c1a62114f302c6..ee0522a8f730e1116a99dffba57a39ec72af50a4 100644 (file)
@@ -540,19 +540,22 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client,
                                        bool is_src, bool ack)
 {
        struct snd_seq_port_subs_info *grp;
+       struct list_head *list;
+       bool empty;
 
        grp = is_src ? &port->c_src : &port->c_dest;
+       list = is_src ? &subs->src_list : &subs->dest_list;
        down_write(&grp->list_mutex);
        write_lock_irq(&grp->list_lock);
-       if (is_src)
-               list_del(&subs->src_list);
-       else
-               list_del(&subs->dest_list);
+       empty = list_empty(list);
+       if (!empty)
+               list_del_init(list);
        grp->exclusive = 0;
        write_unlock_irq(&grp->list_lock);
        up_write(&grp->list_mutex);
 
-       unsubscribe_port(client, port, grp, &subs->info, ack);
+       if (!empty)
+               unsubscribe_port(client, port, grp, &subs->info, ack);
 }
 
 /* connect two ports */