Skip to content

Commit

Permalink
ALSA: timer: Fix possible race at assigning a timer instance
Browse files Browse the repository at this point in the history
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 <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Takashi Iwai <[email protected]>
  • Loading branch information
tiwai committed Nov 8, 2019
1 parent 33bbb8a commit 6a34367
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 56 deletions.
4 changes: 3 additions & 1 deletion include/sound/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 12 additions & 6 deletions sound/core/seq/seq_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down
97 changes: 48 additions & 49 deletions sound/core/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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 */
Expand Down Expand Up @@ -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(&register_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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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))) {
Expand All @@ -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
Expand All @@ -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;
}
Expand Down

0 comments on commit 6a34367

Please sign in to comment.