ALSA: timer: Limit max instances per timer
authorTakashi Iwai <tiwai@suse.de>
Sun, 5 Nov 2017 09:07:43 +0000 (10:07 +0100)
committerTakashi Iwai <tiwai@suse.de>
Mon, 6 Nov 2017 09:41:24 +0000 (10:41 +0100)
Currently we allow unlimited number of timer instances, and it may
bring the system hogging way too much CPU when too many timer
instances are opened and processed concurrently.  This may end up with
a soft-lockup report as triggered by syzkaller, especially when
hrtimer backend is deployed.

Since such insane number of instances aren't demanded by the normal
use case of ALSA sequencer and it merely  opens a risk only for abuse,
this patch introduces the upper limit for the number of instances per
timer backend.  As default, it's set to 1000, but for the fine-grained
timer like hrtimer, it's set to 100.

Reported-by: syzbot
Tested-by: Jérôme Glisse <jglisse@redhat.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
include/sound/timer.h
sound/core/hrtimer.c
sound/core/timer.c

index c4d76ff056c6efd3431a4ed7aa3c6f6888c69e80..7ae226ab6990832d64c43935dded32463e739790 100644 (file)
@@ -90,6 +90,8 @@ struct snd_timer {
        struct list_head ack_list_head;
        struct list_head sack_list_head; /* slow ack list head */
        struct tasklet_struct task_queue;
+       int max_instances;      /* upper limit of timer instances */
+       int num_instances;      /* current number of timer instances */
 };
 
 struct snd_timer_instance {
index 1ac0c423903e7f3f41a2045fd20b9707df4ac00f..6e47b823bcaa3efd1ab9325117527a03f80c231f 100644 (file)
@@ -159,6 +159,7 @@ static int __init snd_hrtimer_init(void)
        timer->hw = hrtimer_hw;
        timer->hw.resolution = resolution;
        timer->hw.ticks = NANO_SEC / resolution;
+       timer->max_instances = 100; /* lower the limit */
 
        err = snd_timer_global_register(timer);
        if (err < 0) {
index 6cdd04a459626c86f185742d099ab16729b7d517..15e82a656d9622c5b0291dd0830813f77934393c 100644 (file)
@@ -180,7 +180,7 @@ static void snd_timer_request(struct snd_timer_id *tid)
  *
  * call this with register_mutex down.
  */
-static void snd_timer_check_slave(struct snd_timer_instance *slave)
+static int snd_timer_check_slave(struct snd_timer_instance *slave)
 {
        struct snd_timer *timer;
        struct snd_timer_instance *master;
@@ -190,16 +190,21 @@ static void snd_timer_check_slave(struct snd_timer_instance *slave)
                list_for_each_entry(master, &timer->open_list_head, open_list) {
                        if (slave->slave_class == master->slave_class &&
                            slave->slave_id == master->slave_id) {
+                               if (master->timer->num_instances >=
+                                   master->timer->max_instances)
+                                       return -EBUSY;
                                list_move_tail(&slave->open_list,
                                               &master->slave_list_head);
+                               master->timer->num_instances++;
                                spin_lock_irq(&slave_active_lock);
                                slave->master = master;
                                slave->timer = master->timer;
                                spin_unlock_irq(&slave_active_lock);
-                               return;
+                               return 0;
                        }
                }
        }
+       return 0;
 }
 
 /*
@@ -208,7 +213,7 @@ static void snd_timer_check_slave(struct snd_timer_instance *slave)
  *
  * call this with register_mutex down.
  */
-static void snd_timer_check_master(struct snd_timer_instance *master)
+static int snd_timer_check_master(struct snd_timer_instance *master)
 {
        struct snd_timer_instance *slave, *tmp;
 
@@ -216,7 +221,11 @@ static void snd_timer_check_master(struct snd_timer_instance *master)
        list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {
                if (slave->slave_class == master->slave_class &&
                    slave->slave_id == master->slave_id) {
+                       if (master->timer->num_instances >=
+                           master->timer->max_instances)
+                               return -EBUSY;
                        list_move_tail(&slave->open_list, &master->slave_list_head);
+                       master->timer->num_instances++;
                        spin_lock_irq(&slave_active_lock);
                        spin_lock(&master->timer->lock);
                        slave->master = master;
@@ -228,8 +237,11 @@ static void snd_timer_check_master(struct snd_timer_instance *master)
                        spin_unlock_irq(&slave_active_lock);
                }
        }
+       return 0;
 }
 
+static int snd_timer_close_locked(struct snd_timer_instance *timeri);
+
 /*
  * open a timer instance
  * when opening a master, the slave id must be here given.
@@ -240,6 +252,7 @@ int snd_timer_open(struct snd_timer_instance **ti,
 {
        struct snd_timer *timer;
        struct snd_timer_instance *timeri = NULL;
+       int err;
 
        if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) {
                /* open a slave instance */
@@ -259,10 +272,14 @@ int snd_timer_open(struct snd_timer_instance **ti,
                timeri->slave_id = tid->device;
                timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;
                list_add_tail(&timeri->open_list, &snd_timer_slave_list);
-               snd_timer_check_slave(timeri);
+               err = snd_timer_check_slave(timeri);
+               if (err < 0) {
+                       snd_timer_close_locked(timeri);
+                       timeri = NULL;
+               }
                mutex_unlock(&register_mutex);
                *ti = timeri;
-               return 0;
+               return err;
        }
 
        /* open a master instance */
@@ -288,6 +305,10 @@ int snd_timer_open(struct snd_timer_instance **ti,
                        return -EBUSY;
                }
        }
+       if (timer->num_instances >= timer->max_instances) {
+               mutex_unlock(&register_mutex);
+               return -EBUSY;
+       }
        timeri = snd_timer_instance_new(owner, timer);
        if (!timeri) {
                mutex_unlock(&register_mutex);
@@ -314,25 +335,27 @@ int snd_timer_open(struct snd_timer_instance **ti,
        }
 
        list_add_tail(&timeri->open_list, &timer->open_list_head);
-       snd_timer_check_master(timeri);
+       timer->num_instances++;
+       err = snd_timer_check_master(timeri);
+       if (err < 0) {
+               snd_timer_close_locked(timeri);
+               timeri = NULL;
+       }
        mutex_unlock(&register_mutex);
        *ti = timeri;
-       return 0;
+       return err;
 }
 EXPORT_SYMBOL(snd_timer_open);
 
 /*
  * close a timer instance
+ * call this with register_mutex down.
  */
-int snd_timer_close(struct snd_timer_instance *timeri)
+static int snd_timer_close_locked(struct snd_timer_instance *timeri)
 {
        struct snd_timer *timer = NULL;
        struct snd_timer_instance *slave, *tmp;
 
-       if (snd_BUG_ON(!timeri))
-               return -ENXIO;
-
-       mutex_lock(&register_mutex);
        list_del(&timeri->open_list);
 
        /* force to stop the timer */
@@ -340,6 +363,7 @@ int snd_timer_close(struct snd_timer_instance *timeri)
 
        timer = timeri->timer;
        if (timer) {
+               timer->num_instances--;
                /* wait, until the active callback is finished */
                spin_lock_irq(&timer->lock);
                while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) {
@@ -355,6 +379,7 @@ int snd_timer_close(struct snd_timer_instance *timeri)
                list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,
                                         open_list) {
                        list_move_tail(&slave->open_list, &snd_timer_slave_list);
+                       timer->num_instances--;
                        slave->master = NULL;
                        slave->timer = NULL;
                        list_del_init(&slave->ack_list);
@@ -382,9 +407,24 @@ int snd_timer_close(struct snd_timer_instance *timeri)
                module_put(timer->module);
        }
 
-       mutex_unlock(&register_mutex);
        return 0;
 }
+
+/*
+ * close a timer instance
+ */
+int snd_timer_close(struct snd_timer_instance *timeri)
+{
+       int err;
+
+       if (snd_BUG_ON(!timeri))
+               return -ENXIO;
+
+       mutex_lock(&register_mutex);
+       err = snd_timer_close_locked(timeri);
+       mutex_unlock(&register_mutex);
+       return err;
+}
 EXPORT_SYMBOL(snd_timer_close);
 
 unsigned long snd_timer_resolution(struct snd_timer_instance *timeri)
@@ -856,6 +896,7 @@ int snd_timer_new(struct snd_card *card, char *id, struct snd_timer_id *tid,
        spin_lock_init(&timer->lock);
        tasklet_init(&timer->task_queue, snd_timer_tasklet,
                     (unsigned long)timer);
+       timer->max_instances = 1000; /* default limit per timer */
        if (card != NULL) {
                timer->module = card->module;
                err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops);