| From: Takashi Iwai <tiwai@suse.de> |
| Date: Mon, 8 Feb 2016 17:26:58 +0100 |
| Subject: ALSA: timer: Fix race at concurrent reads |
| |
| commit 4dff5c7b7093b19c19d3a100f8a3ad87cb7cd9e7 upstream. |
| |
| snd_timer_user_read() has a potential race among parallel reads, as |
| qhead and qused are updated outside the critical section due to |
| copy_to_user() calls. Move them into the critical section, and also |
| sanitize the relevant code a bit. |
| |
| Signed-off-by: Takashi Iwai <tiwai@suse.de> |
| [bwh: Backported to 3.2: there's no check for tu->connected to fix up] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| --- a/sound/core/timer.c |
| +++ b/sound/core/timer.c |
| @@ -1890,6 +1890,7 @@ static ssize_t snd_timer_user_read(struc |
| { |
| struct snd_timer_user *tu; |
| long result = 0, unit; |
| + int qhead; |
| int err = 0; |
| |
| tu = file->private_data; |
| @@ -1901,7 +1902,7 @@ static ssize_t snd_timer_user_read(struc |
| |
| if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { |
| err = -EAGAIN; |
| - break; |
| + goto _error; |
| } |
| |
| set_current_state(TASK_INTERRUPTIBLE); |
| @@ -1916,38 +1917,33 @@ static ssize_t snd_timer_user_read(struc |
| |
| if (signal_pending(current)) { |
| err = -ERESTARTSYS; |
| - break; |
| + goto _error; |
| } |
| } |
| |
| + qhead = tu->qhead++; |
| + tu->qhead %= tu->queue_size; |
| spin_unlock_irq(&tu->qlock); |
| - if (err < 0) |
| - goto _error; |
| |
| if (tu->tread) { |
| - if (copy_to_user(buffer, &tu->tqueue[tu->qhead++], |
| - sizeof(struct snd_timer_tread))) { |
| + if (copy_to_user(buffer, &tu->tqueue[qhead], |
| + sizeof(struct snd_timer_tread))) |
| err = -EFAULT; |
| - goto _error; |
| - } |
| } else { |
| - if (copy_to_user(buffer, &tu->queue[tu->qhead++], |
| - sizeof(struct snd_timer_read))) { |
| + if (copy_to_user(buffer, &tu->queue[qhead], |
| + sizeof(struct snd_timer_read))) |
| err = -EFAULT; |
| - goto _error; |
| - } |
| } |
| |
| - tu->qhead %= tu->queue_size; |
| - |
| - result += unit; |
| - buffer += unit; |
| - |
| spin_lock_irq(&tu->qlock); |
| tu->qused--; |
| + if (err < 0) |
| + goto _error; |
| + result += unit; |
| + buffer += unit; |
| } |
| - spin_unlock_irq(&tu->qlock); |
| _error: |
| + spin_unlock_irq(&tu->qlock); |
| return result > 0 ? result : err; |
| } |
| |