Commit b2483716 authored by Takashi Iwai's avatar Takashi Iwai

ALSA: pcm: Fix potential deadlock in OSS emulation

There are potential deadlocks in PCM OSS emulation code while
accessing read/write and mmap concurrently.  This comes from the
infamous mmap_sem usage in copy_from/to_user().  Namely,

   snd_pcm_oss_write() ->
     &runtime->oss.params_lock ->
        copy_to_user() ->
          &mm->mmap_sem
  mmap() ->
    &mm->mmap_sem ->
      snd_pcm_oss_mmap() ->
        &runtime->oss.params_lock

Since we can't avoid taking params_lock from mmap code path, use
trylock variant and aborts with -EAGAIN as a workaround of this AB/BA
deadlock.

BugLink: http://lkml.kernel.org/r/CACT4Y+bVrBKDG0G2_AcUgUQa+X91VKTeS4v+wN7BSHwHtqn3kQ@mail.gmail.comReported-by: default avatarDmitry Vyukov <dvyukov@google.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent cc85f7a6
...@@ -835,7 +835,8 @@ static int choose_rate(struct snd_pcm_substream *substream, ...@@ -835,7 +835,8 @@ static int choose_rate(struct snd_pcm_substream *substream,
return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL); return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL);
} }
static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream) static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream,
bool trylock)
{ {
struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_pcm_hw_params *params, *sparams; struct snd_pcm_hw_params *params, *sparams;
...@@ -849,7 +850,10 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream) ...@@ -849,7 +850,10 @@ static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream)
struct snd_mask sformat_mask; struct snd_mask sformat_mask;
struct snd_mask mask; struct snd_mask mask;
if (mutex_lock_interruptible(&runtime->oss.params_lock)) if (trylock) {
if (!(mutex_trylock(&runtime->oss.params_lock)))
return -EAGAIN;
} else if (mutex_lock_interruptible(&runtime->oss.params_lock))
return -EINTR; return -EINTR;
sw_params = kzalloc(sizeof(*sw_params), GFP_KERNEL); sw_params = kzalloc(sizeof(*sw_params), GFP_KERNEL);
params = kmalloc(sizeof(*params), GFP_KERNEL); params = kmalloc(sizeof(*params), GFP_KERNEL);
...@@ -1092,7 +1096,7 @@ static int snd_pcm_oss_get_active_substream(struct snd_pcm_oss_file *pcm_oss_fil ...@@ -1092,7 +1096,7 @@ static int snd_pcm_oss_get_active_substream(struct snd_pcm_oss_file *pcm_oss_fil
if (asubstream == NULL) if (asubstream == NULL)
asubstream = substream; asubstream = substream;
if (substream->runtime->oss.params) { if (substream->runtime->oss.params) {
err = snd_pcm_oss_change_params(substream); err = snd_pcm_oss_change_params(substream, false);
if (err < 0) if (err < 0)
return err; return err;
} }
...@@ -1132,7 +1136,7 @@ static int snd_pcm_oss_make_ready(struct snd_pcm_substream *substream) ...@@ -1132,7 +1136,7 @@ static int snd_pcm_oss_make_ready(struct snd_pcm_substream *substream)
return 0; return 0;
runtime = substream->runtime; runtime = substream->runtime;
if (runtime->oss.params) { if (runtime->oss.params) {
err = snd_pcm_oss_change_params(substream); err = snd_pcm_oss_change_params(substream, false);
if (err < 0) if (err < 0)
return err; return err;
} }
...@@ -2163,7 +2167,7 @@ static int snd_pcm_oss_get_space(struct snd_pcm_oss_file *pcm_oss_file, int stre ...@@ -2163,7 +2167,7 @@ static int snd_pcm_oss_get_space(struct snd_pcm_oss_file *pcm_oss_file, int stre
runtime = substream->runtime; runtime = substream->runtime;
if (runtime->oss.params && if (runtime->oss.params &&
(err = snd_pcm_oss_change_params(substream)) < 0) (err = snd_pcm_oss_change_params(substream, false)) < 0)
return err; return err;
info.fragsize = runtime->oss.period_bytes; info.fragsize = runtime->oss.period_bytes;
...@@ -2804,7 +2808,12 @@ static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area) ...@@ -2804,7 +2808,12 @@ static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area)
return -EIO; return -EIO;
if (runtime->oss.params) { if (runtime->oss.params) {
if ((err = snd_pcm_oss_change_params(substream)) < 0) /* use mutex_trylock() for params_lock for avoiding a deadlock
* between mmap_sem and params_lock taken by
* copy_from/to_user() in snd_pcm_oss_write/read()
*/
err = snd_pcm_oss_change_params(substream, true);
if (err < 0)
return err; return err;
} }
#ifdef CONFIG_SND_PCM_OSS_PLUGINS #ifdef CONFIG_SND_PCM_OSS_PLUGINS
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment