From a39331867335d4a94b6165e306265c9e24aca073 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 30 Oct 2019 22:42:57 +0100 Subject: ALSA: timer: Fix mutex deadlock at releasing card When a card is disconnected while in use, the system waits until all opened files are closed then releases the card. This is done via put_device() of the card device in each device release code. The recently reported mutex deadlock bug happens in this code path; snd_timer_close() for the timer device deals with the global register_mutex and it calls put_device() there. When this timer device is the last one, the card gets freed and it eventually calls snd_timer_free(), which has again the protection with the global register_mutex -- boom. Basically put_device() call itself is race-free, so a relative simple workaround is to move this put_device() call out of the mutex. For achieving that, in this patch, snd_timer_close_locked() got a new argument to store the card device pointer in return, and each caller invokes put_device() with the returned object after the mutex unlock. Reported-and-tested-by: Kirill A. Shutemov Cc: Signed-off-by: Takashi Iwai --- sound/core/timer.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'sound/core/timer.c') diff --git a/sound/core/timer.c b/sound/core/timer.c index 5c9fbf3f4340..6b724d2ee2de 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -226,7 +226,8 @@ static int snd_timer_check_master(struct snd_timer_instance *master) return 0; } -static int snd_timer_close_locked(struct snd_timer_instance *timeri); +static int snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put); /* * open a timer instance @@ -238,6 +239,7 @@ int snd_timer_open(struct snd_timer_instance **ti, { struct snd_timer *timer; struct snd_timer_instance *timeri = NULL; + struct device *card_dev_to_put = NULL; int err; mutex_lock(®ister_mutex); @@ -261,7 +263,7 @@ int snd_timer_open(struct snd_timer_instance **ti, list_add_tail(&timeri->open_list, &snd_timer_slave_list); err = snd_timer_check_slave(timeri); if (err < 0) { - snd_timer_close_locked(timeri); + snd_timer_close_locked(timeri, &card_dev_to_put); timeri = NULL; } goto unlock; @@ -313,7 +315,7 @@ int snd_timer_open(struct snd_timer_instance **ti, timeri = NULL; if (timer->card) - put_device(&timer->card->card_dev); + card_dev_to_put = &timer->card->card_dev; module_put(timer->module); goto unlock; } @@ -323,12 +325,15 @@ int snd_timer_open(struct snd_timer_instance **ti, timer->num_instances++; err = snd_timer_check_master(timeri); if (err < 0) { - snd_timer_close_locked(timeri); + snd_timer_close_locked(timeri, &card_dev_to_put); timeri = NULL; } unlock: mutex_unlock(®ister_mutex); + /* put_device() is called after unlock for avoiding deadlock */ + if (card_dev_to_put) + put_device(card_dev_to_put); *ti = timeri; return err; } @@ -338,7 +343,8 @@ EXPORT_SYMBOL(snd_timer_open); * close a timer instance * call this with register_mutex down. */ -static int snd_timer_close_locked(struct snd_timer_instance *timeri) +static int snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put) { struct snd_timer *timer = timeri->timer; struct snd_timer_instance *slave, *tmp; @@ -395,7 +401,7 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri) timer->hw.close(timer); /* release a card refcount for safe disconnection */ if (timer->card) - put_device(&timer->card->card_dev); + *card_devp_to_put = &timer->card->card_dev; module_put(timer->module); } @@ -407,14 +413,18 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri) */ int snd_timer_close(struct snd_timer_instance *timeri) { + struct device *card_dev_to_put = NULL; int err; if (snd_BUG_ON(!timeri)) return -ENXIO; mutex_lock(®ister_mutex); - err = snd_timer_close_locked(timeri); + err = snd_timer_close_locked(timeri, &card_dev_to_put); mutex_unlock(®ister_mutex); + /* put_device() is called after unlock for avoiding deadlock */ + if (card_dev_to_put) + put_device(card_dev_to_put); return err; } EXPORT_SYMBOL(snd_timer_close); -- cgit v1.2.3 From fdea53fe5de532969a332d6e5e727f2ad8bf084d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 6 Nov 2019 16:42:57 +0100 Subject: ALSA: timer: Limit max amount of slave instances The fuzzer tries to open the timer instances as much as possible, and this may cause a system hiccup easily. We've already introduced the cap for the max number of available instances for the h/w timers, and we should put such a limit also to the slave timers, too. This patch introduces the limit to the multiple opened slave timers. The upper limit is hard-coded to 1000 for now, which should suffice for any practical usages up to now. Link: https://lore.kernel.org/r/20191106154257.5853-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/timer.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'sound/core/timer.c') diff --git a/sound/core/timer.c b/sound/core/timer.c index 5c9fbf3f4340..219249627cc3 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -74,6 +74,9 @@ static LIST_HEAD(snd_timer_slave_list); /* lock for slave active lists */ static DEFINE_SPINLOCK(slave_active_lock); +#define MAX_SLAVE_INSTANCES 1000 +static int num_slaves; + static DEFINE_MUTEX(register_mutex); static int snd_timer_free(struct snd_timer *timer); @@ -250,6 +253,10 @@ int snd_timer_open(struct snd_timer_instance **ti, err = -EINVAL; goto unlock; } + if (num_slaves >= MAX_SLAVE_INSTANCES) { + err = -EBUSY; + goto unlock; + } timeri = snd_timer_instance_new(owner, NULL); if (!timeri) { err = -ENOMEM; @@ -259,6 +266,7 @@ 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); + num_slaves++; err = snd_timer_check_slave(timeri); if (err < 0) { snd_timer_close_locked(timeri); @@ -350,6 +358,8 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri) } list_del(&timeri->open_list); + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + num_slaves--; /* force to stop the timer */ snd_timer_stop(timeri); -- cgit v1.2.3 From e7af6307a8a54f0b873960b32b6a644f2d0fbd97 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 6 Nov 2019 17:55:47 +0100 Subject: ALSA: timer: Fix incorrectly assigned timer instance The clean up commit 41672c0c24a6 ("ALSA: timer: Simplify error path in snd_timer_open()") unified the error handling code paths with the standard goto, but it introduced a subtle bug: the timer instance is stored in snd_timer_open() incorrectly even if it returns an error. This may eventually lead to UAF, as spotted by fuzzer. The culprit is the snd_timer_open() code checks the SNDRV_TIMER_IFLG_EXCLUSIVE flag with the common variable timeri. This variable is supposed to be the newly created instance, but we (ab-)used it for a temporary check before the actual creation of a timer instance. After that point, there is another check for the max number of instances, and it bails out if over the threshold. Before the refactoring above, it worked fine because the code returned directly from that point. After the refactoring, however, it jumps to the unified error path that stores the timeri variable in return -- even if it returns an error. Unfortunately this stored value is kept in the caller side (snd_timer_user_tselect()) in tu->timeri. This causes inconsistency later, as if the timer was successfully assigned. In this patch, we fix it by not re-using timeri variable but a temporary variable for testing the exclusive connection, so timeri remains NULL at that point. Fixes: 41672c0c24a6 ("ALSA: timer: Simplify error path in snd_timer_open()") Reported-and-tested-by: Tristan Madani Cc: Link: https://lore.kernel.org/r/20191106165547.23518-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/timer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/core/timer.c') diff --git a/sound/core/timer.c b/sound/core/timer.c index 6b724d2ee2de..59ae21b0bb93 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -284,11 +284,11 @@ int snd_timer_open(struct snd_timer_instance **ti, goto unlock; } if (!list_empty(&timer->open_list_head)) { - timeri = list_entry(timer->open_list_head.next, + struct snd_timer_instance *t = + list_entry(timer->open_list_head.next, struct snd_timer_instance, open_list); - if (timeri->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) { + if (t->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) { err = -EBUSY; - timeri = NULL; goto unlock; } } -- cgit v1.2.3 From ebfc6de29ae8e48f91e280fd37539188b5252b38 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 7 Nov 2019 20:20:06 +0100 Subject: ALSA: timer: Unify master/slave linking code The code in both snd_timer_check_master() and snd_timer_check_slave() are almost identical, both check whether the master/slave link and does linkage. Factor out the common code and call it from both functions for readability. Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/r/20191107192008.32331-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/timer.c | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) (limited to 'sound/core/timer.c') diff --git a/sound/core/timer.c b/sound/core/timer.c index 013f0e69ff0f..21c2d25e4074 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -163,6 +163,28 @@ static void snd_timer_request(struct snd_timer_id *tid) #endif +/* move the slave if it belongs to the master; return 1 if match */ +static int check_matching_master_slave(struct snd_timer_instance *master, + struct snd_timer_instance *slave) +{ + if (slave->slave_class != master->slave_class || + slave->slave_id != master->slave_id) + return 0; + 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; + slave->timer = master->timer; + if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) + list_add_tail(&slave->active_list, &master->slave_active_head); + spin_unlock(&master->timer->lock); + spin_unlock_irq(&slave_active_lock); + return 1; +} + /* * look for a master instance matching with the slave id of the given slave. * when found, relink the open_link of the slave. @@ -173,27 +195,18 @@ static int snd_timer_check_slave(struct snd_timer_instance *slave) { struct snd_timer *timer; struct snd_timer_instance *master; + int err = 0; /* FIXME: it's really dumb to look up all entries.. */ list_for_each_entry(timer, &snd_timer_list, device_list) { 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 0; - } + err = check_matching_master_slave(master, slave); + if (err != 0) /* match found or error */ + goto out; } } - return 0; + out: + return err < 0 ? err : 0; } /* @@ -205,28 +218,15 @@ static int snd_timer_check_slave(struct snd_timer_instance *slave) static int snd_timer_check_master(struct snd_timer_instance *master) { struct snd_timer_instance *slave, *tmp; + int err = 0; /* check all pending slaves */ 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; - slave->timer = master->timer; - if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) - list_add_tail(&slave->active_list, - &master->slave_active_head); - spin_unlock(&master->timer->lock); - spin_unlock_irq(&slave_active_lock); - } + err = check_matching_master_slave(master, slave); + if (err < 0) + break; } - return 0; + return err < 0 ? err : 0; } static int snd_timer_close_locked(struct snd_timer_instance *timeri, -- cgit v1.2.3 From 33bbb8a0ecd142fb5e78f26f3a0b927ae77c30e7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 7 Nov 2019 20:20:07 +0100 Subject: ALSA: timer: Make snd_timer_close() returning void The function doesn't return any useful value, so let's make it void to be clearer. Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/r/20191107192008.32331-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/timer.h | 2 +- sound/core/timer.c | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) (limited to 'sound/core/timer.c') diff --git a/include/sound/timer.h b/include/sound/timer.h index 199c36295a0d..8a13c09c85f7 100644 --- a/include/sound/timer.h +++ b/include/sound/timer.h @@ -119,7 +119,7 @@ int snd_timer_global_free(struct snd_timer *timer); int snd_timer_global_register(struct snd_timer *timer); int snd_timer_open(struct snd_timer_instance **ti, char *owner, struct snd_timer_id *tid, unsigned int slave_id); -int snd_timer_close(struct snd_timer_instance *timeri); +void snd_timer_close(struct snd_timer_instance *timeri); unsigned long snd_timer_resolution(struct snd_timer_instance *timeri); int snd_timer_start(struct snd_timer_instance *timeri, unsigned int ticks); int snd_timer_stop(struct snd_timer_instance *timeri); diff --git a/sound/core/timer.c b/sound/core/timer.c index 21c2d25e4074..c0a73913ec62 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -229,8 +229,8 @@ static int snd_timer_check_master(struct snd_timer_instance *master) return err < 0 ? err : 0; } -static int snd_timer_close_locked(struct snd_timer_instance *timeri, - struct device **card_devp_to_put); +static void snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put); /* * open a timer instance @@ -351,8 +351,8 @@ EXPORT_SYMBOL(snd_timer_open); * close a timer instance * call this with register_mutex down. */ -static int snd_timer_close_locked(struct snd_timer_instance *timeri, - struct device **card_devp_to_put) +static void snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put) { struct snd_timer *timer = timeri->timer; struct snd_timer_instance *slave, *tmp; @@ -414,28 +414,24 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri, *card_devp_to_put = &timer->card->card_dev; module_put(timer->module); } - - return 0; } /* * close a timer instance */ -int snd_timer_close(struct snd_timer_instance *timeri) +void snd_timer_close(struct snd_timer_instance *timeri) { struct device *card_dev_to_put = NULL; - int err; if (snd_BUG_ON(!timeri)) - return -ENXIO; + return; mutex_lock(®ister_mutex); - err = snd_timer_close_locked(timeri, &card_dev_to_put); + snd_timer_close_locked(timeri, &card_dev_to_put); mutex_unlock(®ister_mutex); /* put_device() is called after unlock for avoiding deadlock */ if (card_dev_to_put) put_device(card_dev_to_put); - return err; } EXPORT_SYMBOL(snd_timer_close); -- cgit v1.2.3 From 6a34367e52caea1413eb0a0dcbb524f0c9b67e82 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 7 Nov 2019 20:20:08 +0100 Subject: ALSA: timer: Fix possible race at assigning a timer instance When a new timer instance is created and assigned to the active link in snd_timer_open(), the caller still doesn't (can't) set its callback and callback data. In both the user-timer and the sequencer-timer code, they do manually set up the callbacks after calling snd_timer_open(). This has a potential risk of race when the timer instance is added to the already running timer target, as the callback might get triggered during setting up the callback itself. This patch tries to address it by changing the API usage slightly: - An empty timer instance is created at first via the new function snd_timer_instance_new(). This object isn't linked to the timer list yet. - The caller sets up the callbacks and others stuff for the new timer instance. - The caller invokes snd_timer_open() with this instance, so that it's linked to the target timer. For closing, do similarly: - Call snd_timer_close(). This unlinks the timer instance from the timer list. - Free the timer instance via snd_timer_instance_free() after that. Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/r/20191107192008.32331-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/timer.h | 4 +- sound/core/seq/seq_timer.c | 18 ++++++--- sound/core/timer.c | 97 +++++++++++++++++++++++----------------------- 3 files changed, 63 insertions(+), 56 deletions(-) (limited to 'sound/core/timer.c') diff --git a/include/sound/timer.h b/include/sound/timer.h index 8a13c09c85f7..a53e37bcd746 100644 --- a/include/sound/timer.h +++ b/include/sound/timer.h @@ -118,7 +118,9 @@ int snd_timer_global_new(char *id, int device, struct snd_timer **rtimer); int snd_timer_global_free(struct snd_timer *timer); int snd_timer_global_register(struct snd_timer *timer); -int snd_timer_open(struct snd_timer_instance **ti, char *owner, struct snd_timer_id *tid, unsigned int slave_id); +struct snd_timer_instance *snd_timer_instance_new(const char *owner); +void snd_timer_instance_free(struct snd_timer_instance *timeri); +int snd_timer_open(struct snd_timer_instance *timeri, struct snd_timer_id *tid, unsigned int slave_id); void snd_timer_close(struct snd_timer_instance *timeri); unsigned long snd_timer_resolution(struct snd_timer_instance *timeri); int snd_timer_start(struct snd_timer_instance *timeri, unsigned int ticks); diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c index 161f3170bd7e..63dc7bdb622d 100644 --- a/sound/core/seq/seq_timer.c +++ b/sound/core/seq/seq_timer.c @@ -272,7 +272,13 @@ int snd_seq_timer_open(struct snd_seq_queue *q) return -EINVAL; if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; - err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue); + t = snd_timer_instance_new(str); + if (!t) + return -ENOMEM; + t->callback = snd_seq_timer_interrupt; + t->callback_data = q; + t->flags |= SNDRV_TIMER_IFLG_AUTO; + err = snd_timer_open(t, &tmr->alsa_id, q->queue); if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) { if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL || tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) { @@ -282,16 +288,14 @@ int snd_seq_timer_open(struct snd_seq_queue *q) tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; tid.card = -1; tid.device = SNDRV_TIMER_GLOBAL_SYSTEM; - err = snd_timer_open(&t, str, &tid, q->queue); + err = snd_timer_open(t, &tid, q->queue); } } if (err < 0) { pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err); + snd_timer_instance_free(t); return err; } - t->callback = snd_seq_timer_interrupt; - t->callback_data = q; - t->flags |= SNDRV_TIMER_IFLG_AUTO; spin_lock_irq(&tmr->lock); tmr->timeri = t; spin_unlock_irq(&tmr->lock); @@ -310,8 +314,10 @@ int snd_seq_timer_close(struct snd_seq_queue *q) t = tmr->timeri; tmr->timeri = NULL; spin_unlock_irq(&tmr->lock); - if (t) + if (t) { snd_timer_close(t); + snd_timer_instance_free(t); + } return 0; } diff --git a/sound/core/timer.c b/sound/core/timer.c index c0a73913ec62..9091030f4a89 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -88,12 +88,11 @@ static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_l /* * create a timer instance with the given owner string. - * when timer is not NULL, increments the module counter */ -static struct snd_timer_instance *snd_timer_instance_new(char *owner, - struct snd_timer *timer) +struct snd_timer_instance *snd_timer_instance_new(const char *owner) { struct snd_timer_instance *timeri; + timeri = kzalloc(sizeof(*timeri), GFP_KERNEL); if (timeri == NULL) return NULL; @@ -108,15 +107,20 @@ static struct snd_timer_instance *snd_timer_instance_new(char *owner, INIT_LIST_HEAD(&timeri->slave_list_head); INIT_LIST_HEAD(&timeri->slave_active_head); - timeri->timer = timer; - if (timer && !try_module_get(timer->module)) { + return timeri; +} +EXPORT_SYMBOL(snd_timer_instance_new); + +void snd_timer_instance_free(struct snd_timer_instance *timeri) +{ + if (timeri) { + if (timeri->private_free) + timeri->private_free(timeri); kfree(timeri->owner); kfree(timeri); - return NULL; } - - return timeri; } +EXPORT_SYMBOL(snd_timer_instance_free); /* * find a timer instance from the given timer id @@ -236,12 +240,11 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri, * open a timer instance * when opening a master, the slave id must be here given. */ -int snd_timer_open(struct snd_timer_instance **ti, - char *owner, struct snd_timer_id *tid, +int snd_timer_open(struct snd_timer_instance *timeri, + struct snd_timer_id *tid, unsigned int slave_id) { struct snd_timer *timer; - struct snd_timer_instance *timeri = NULL; struct device *card_dev_to_put = NULL; int err; @@ -259,22 +262,14 @@ int snd_timer_open(struct snd_timer_instance **ti, err = -EBUSY; goto unlock; } - timeri = snd_timer_instance_new(owner, NULL); - if (!timeri) { - err = -ENOMEM; - goto unlock; - } timeri->slave_class = tid->dev_sclass; timeri->slave_id = tid->device; timeri->flags |= SNDRV_TIMER_IFLG_SLAVE; list_add_tail(&timeri->open_list, &snd_timer_slave_list); num_slaves++; err = snd_timer_check_slave(timeri); - if (err < 0) { - snd_timer_close_locked(timeri, &card_dev_to_put); - timeri = NULL; - } - goto unlock; + if (err < 0) + goto close; } /* open a master instance */ @@ -304,45 +299,40 @@ int snd_timer_open(struct snd_timer_instance **ti, err = -EBUSY; goto unlock; } - timeri = snd_timer_instance_new(owner, timer); - if (!timeri) { - err = -ENOMEM; + if (!try_module_get(timer->module)) { + err = -EBUSY; goto unlock; } /* take a card refcount for safe disconnection */ - if (timer->card) + if (timer->card) { get_device(&timer->card->card_dev); - timeri->slave_class = tid->dev_sclass; - timeri->slave_id = slave_id; + card_dev_to_put = &timer->card->card_dev; + } if (list_empty(&timer->open_list_head) && timer->hw.open) { err = timer->hw.open(timer); if (err) { - kfree(timeri->owner); - kfree(timeri); - timeri = NULL; - - if (timer->card) - card_dev_to_put = &timer->card->card_dev; module_put(timer->module); goto unlock; } } + timeri->timer = timer; + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = slave_id; + list_add_tail(&timeri->open_list, &timer->open_list_head); timer->num_instances++; err = snd_timer_check_master(timeri); - if (err < 0) { + close: + if (err < 0) snd_timer_close_locked(timeri, &card_dev_to_put); - timeri = NULL; - } unlock: mutex_unlock(®ister_mutex); /* put_device() is called after unlock for avoiding deadlock */ - if (card_dev_to_put) + if (err < 0 && card_dev_to_put) put_device(card_dev_to_put); - *ti = timeri; return err; } EXPORT_SYMBOL(snd_timer_open); @@ -363,9 +353,11 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri, spin_unlock_irq(&timer->lock); } - list_del(&timeri->open_list); - if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) - num_slaves--; + if (!list_empty(&timeri->open_list)) { + list_del_init(&timeri->open_list); + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + num_slaves--; + } /* force to stop the timer */ snd_timer_stop(timeri); @@ -384,6 +376,7 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri, /* remove slave links */ spin_lock_irq(&slave_active_lock); spin_lock(&timer->lock); + timeri->timer = NULL; list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, open_list) { list_move_tail(&slave->open_list, &snd_timer_slave_list); @@ -401,11 +394,6 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri, timer = NULL; } - if (timeri->private_free) - timeri->private_free(timeri); - kfree(timeri->owner); - kfree(timeri); - if (timer) { if (list_empty(&timer->open_list_head) && timer->hw.close) timer->hw.close(timer); @@ -1480,8 +1468,10 @@ static int snd_timer_user_release(struct inode *inode, struct file *file) tu = file->private_data; file->private_data = NULL; mutex_lock(&tu->ioctl_lock); - if (tu->timeri) + if (tu->timeri) { snd_timer_close(tu->timeri); + snd_timer_instance_free(tu->timeri); + } mutex_unlock(&tu->ioctl_lock); kfree(tu->queue); kfree(tu->tqueue); @@ -1722,6 +1712,7 @@ static int snd_timer_user_tselect(struct file *file, tu = file->private_data; if (tu->timeri) { snd_timer_close(tu->timeri); + snd_timer_instance_free(tu->timeri); tu->timeri = NULL; } if (copy_from_user(&tselect, _tselect, sizeof(tselect))) { @@ -1731,9 +1722,11 @@ static int snd_timer_user_tselect(struct file *file, sprintf(str, "application %i", current->pid); if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE) tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION; - err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid); - if (err < 0) + tu->timeri = snd_timer_instance_new(str); + if (!tu->timeri) { + err = -ENOMEM; goto __err; + } tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST; tu->timeri->callback = tu->tread @@ -1742,6 +1735,12 @@ static int snd_timer_user_tselect(struct file *file, tu->timeri->callback_data = (void *)tu; tu->timeri->disconnect = snd_timer_user_disconnect; + err = snd_timer_open(tu->timeri, &tselect.id, current->pid); + if (err < 0) { + snd_timer_instance_free(tu->timeri); + tu->timeri = NULL; + } + __err: return err; } -- cgit v1.2.3 From 0c4f09ceec3d7afbef6ad4077d96e36e0fad4028 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 11 Nov 2019 18:36:42 +0100 Subject: ALSA: timer: Fix the breakage of slave link open A silly mistake was made while applying the fix for potential races in commit 6a34367e52ca ("ALSA: timer: Fix possible race at assigning a timer instance"): when a slave PCM is opened and succeeds, it doesn't return but proceeds to the master timer open code instead. Plug the hole and beautify a bit. Fixes: 6a34367e52ca ("ALSA: timer: Fix possible race at assigning a timer instance") Reported-by: syzbot+4476917c053f60112c99@syzkaller.appspotmail.com Link: https://lore.kernel.org/r/20191111173642.6093-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/timer.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'sound/core/timer.c') diff --git a/sound/core/timer.c b/sound/core/timer.c index 9091030f4a89..24fed5c78273 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -268,8 +268,7 @@ int snd_timer_open(struct snd_timer_instance *timeri, list_add_tail(&timeri->open_list, &snd_timer_slave_list); num_slaves++; err = snd_timer_check_slave(timeri); - if (err < 0) - goto close; + goto list_added; } /* open a master instance */ @@ -324,7 +323,7 @@ int snd_timer_open(struct snd_timer_instance *timeri, list_add_tail(&timeri->open_list, &timer->open_list_head); timer->num_instances++; err = snd_timer_check_master(timeri); - close: +list_added: if (err < 0) snd_timer_close_locked(timeri, &card_dev_to_put); -- cgit v1.2.3