diff options
author | Takashi Iwai <tiwai@suse.de> | 2016-01-14 10:30:58 -0500 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2016-01-15 09:50:34 -0500 |
commit | b5a663aa426f4884c71cd8580adae73f33570f0d (patch) | |
tree | 87f6d8a3994ca42949130ddb53cb51603fea704f /sound | |
parent | cf52103a218744f3fd18111325c28e95aa9cd226 (diff) |
ALSA: timer: Harden slave timer list handling
A slave timer instance might be still accessible in a racy way while
operating the master instance as it lacks of locking. Since the
master operation is mostly protected with timer->lock, we should cope
with it while changing the slave instance, too. Also, some linked
lists (active_list and ack_list) of slave instances aren't unlinked
immediately at stopping or closing, and this may lead to unexpected
accesses.
This patch tries to address these issues. It adds spin lock of
timer->lock (either from master or slave, which is equivalent) in a
few places. For avoiding a deadlock, we ensure that the global
slave_active_lock is always locked at first before each timer lock.
Also, ack and active_list of slave instances are properly unlinked at
snd_timer_stop() and snd_timer_close().
Last but not least, remove the superfluous call of _snd_timer_stop()
at removing slave links. This is a noop, and calling it may confuse
readers wrt locking. Further cleanup will follow in a later patch.
Actually we've got reports of use-after-free by syzkaller fuzzer, and
this hopefully fixes these issues.
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/core/timer.c | 18 |
1 files changed, 14 insertions, 4 deletions
diff --git a/sound/core/timer.c b/sound/core/timer.c index 3810ee8f1205..4e8d7bfffff6 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c | |||
@@ -215,11 +215,13 @@ static void snd_timer_check_master(struct snd_timer_instance *master) | |||
215 | slave->slave_id == master->slave_id) { | 215 | slave->slave_id == master->slave_id) { |
216 | list_move_tail(&slave->open_list, &master->slave_list_head); | 216 | list_move_tail(&slave->open_list, &master->slave_list_head); |
217 | spin_lock_irq(&slave_active_lock); | 217 | spin_lock_irq(&slave_active_lock); |
218 | spin_lock(&master->timer->lock); | ||
218 | slave->master = master; | 219 | slave->master = master; |
219 | slave->timer = master->timer; | 220 | slave->timer = master->timer; |
220 | if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) | 221 | if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) |
221 | list_add_tail(&slave->active_list, | 222 | list_add_tail(&slave->active_list, |
222 | &master->slave_active_head); | 223 | &master->slave_active_head); |
224 | spin_unlock(&master->timer->lock); | ||
223 | spin_unlock_irq(&slave_active_lock); | 225 | spin_unlock_irq(&slave_active_lock); |
224 | } | 226 | } |
225 | } | 227 | } |
@@ -346,15 +348,18 @@ int snd_timer_close(struct snd_timer_instance *timeri) | |||
346 | timer->hw.close) | 348 | timer->hw.close) |
347 | timer->hw.close(timer); | 349 | timer->hw.close(timer); |
348 | /* remove slave links */ | 350 | /* remove slave links */ |
351 | spin_lock_irq(&slave_active_lock); | ||
352 | spin_lock(&timer->lock); | ||
349 | list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, | 353 | list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, |
350 | open_list) { | 354 | open_list) { |
351 | spin_lock_irq(&slave_active_lock); | ||
352 | _snd_timer_stop(slave, 1, SNDRV_TIMER_EVENT_RESOLUTION); | ||
353 | list_move_tail(&slave->open_list, &snd_timer_slave_list); | 355 | list_move_tail(&slave->open_list, &snd_timer_slave_list); |
354 | slave->master = NULL; | 356 | slave->master = NULL; |
355 | slave->timer = NULL; | 357 | slave->timer = NULL; |
356 | spin_unlock_irq(&slave_active_lock); | 358 | list_del_init(&slave->ack_list); |
359 | list_del_init(&slave->active_list); | ||
357 | } | 360 | } |
361 | spin_unlock(&timer->lock); | ||
362 | spin_unlock_irq(&slave_active_lock); | ||
358 | mutex_unlock(®ister_mutex); | 363 | mutex_unlock(®ister_mutex); |
359 | } | 364 | } |
360 | out: | 365 | out: |
@@ -441,9 +446,12 @@ static int snd_timer_start_slave(struct snd_timer_instance *timeri) | |||
441 | 446 | ||
442 | spin_lock_irqsave(&slave_active_lock, flags); | 447 | spin_lock_irqsave(&slave_active_lock, flags); |
443 | timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; | 448 | timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; |
444 | if (timeri->master) | 449 | if (timeri->master && timeri->timer) { |
450 | spin_lock(&timeri->timer->lock); | ||
445 | list_add_tail(&timeri->active_list, | 451 | list_add_tail(&timeri->active_list, |
446 | &timeri->master->slave_active_head); | 452 | &timeri->master->slave_active_head); |
453 | spin_unlock(&timeri->timer->lock); | ||
454 | } | ||
447 | spin_unlock_irqrestore(&slave_active_lock, flags); | 455 | spin_unlock_irqrestore(&slave_active_lock, flags); |
448 | return 1; /* delayed start */ | 456 | return 1; /* delayed start */ |
449 | } | 457 | } |
@@ -489,6 +497,8 @@ static int _snd_timer_stop(struct snd_timer_instance * timeri, | |||
489 | if (!keep_flag) { | 497 | if (!keep_flag) { |
490 | spin_lock_irqsave(&slave_active_lock, flags); | 498 | spin_lock_irqsave(&slave_active_lock, flags); |
491 | timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; | 499 | timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; |
500 | list_del_init(&timeri->ack_list); | ||
501 | list_del_init(&timeri->active_list); | ||
492 | spin_unlock_irqrestore(&slave_active_lock, flags); | 502 | spin_unlock_irqrestore(&slave_active_lock, flags); |
493 | } | 503 | } |
494 | goto __end; | 504 | goto __end; |