ALSA: pcm: Return -EBUSY for OSS ioctls changing busy streams
authorTakashi Iwai <tiwai@suse.de>
Fri, 23 Mar 2018 07:03:26 +0000 (08:03 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 24 Apr 2018 07:34:15 +0000 (09:34 +0200)
commit 40cab6e88cb0b6c56d3f30b7491a20e803f948f6 upstream.

OSS PCM stream management isn't modal but it allows ioctls issued at
any time for changing the parameters.  In the previous hardening
patch ("ALSA: pcm: Avoid potential races between OSS ioctls and
read/write"), we covered these races and prevent the corruption by
protecting the concurrent accesses via params_lock mutex.  However,
this means that some ioctls that try to change the stream parameter
(e.g. channels or format) would be blocked until the read/write
finishes, and it may take really long.

Basically changing the parameter while reading/writing is an invalid
operation, hence it's even more user-friendly from the API POV if it
returns -EBUSY in such a situation.

This patch adds such checks in the relevant ioctls with the addition
of read/write access refcount.

Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
include/sound/pcm_oss.h
sound/core/oss/pcm_oss.c

index 760c969..12bbf8c 100644 (file)
@@ -57,6 +57,7 @@ struct snd_pcm_oss_runtime {
        char *buffer;                           /* vmallocated period */
        size_t buffer_used;                     /* used length from period buffer */
        struct mutex params_lock;
+       atomic_t rw_ref;                /* concurrent read/write accesses */
 #ifdef CONFIG_SND_PCM_OSS_PLUGINS
        struct snd_pcm_plugin *plugin_first;
        struct snd_pcm_plugin *plugin_last;
index bf5f3f1..702bfcc 100644 (file)
@@ -1405,6 +1405,7 @@ static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const cha
        if (atomic_read(&substream->mmap_count))
                return -ENXIO;
 
+       atomic_inc(&runtime->oss.rw_ref);
        while (bytes > 0) {
                if (mutex_lock_interruptible(&runtime->oss.params_lock)) {
                        tmp = -ERESTARTSYS;
@@ -1468,6 +1469,7 @@ static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const cha
                }
                tmp = 0;
        }
+       atomic_dec(&runtime->oss.rw_ref);
        return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
 }
 
@@ -1513,6 +1515,7 @@ static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __use
        if (atomic_read(&substream->mmap_count))
                return -ENXIO;
 
+       atomic_inc(&runtime->oss.rw_ref);
        while (bytes > 0) {
                if (mutex_lock_interruptible(&runtime->oss.params_lock)) {
                        tmp = -ERESTARTSYS;
@@ -1561,6 +1564,7 @@ static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __use
                }
                tmp = 0;
        }
+       atomic_dec(&runtime->oss.rw_ref);
        return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
 }
 
@@ -1667,8 +1671,11 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                        goto __direct;
                if ((err = snd_pcm_oss_make_ready(substream)) < 0)
                        return err;
-               if (mutex_lock_interruptible(&runtime->oss.params_lock))
+               atomic_inc(&runtime->oss.rw_ref);
+               if (mutex_lock_interruptible(&runtime->oss.params_lock)) {
+                       atomic_dec(&runtime->oss.rw_ref);
                        return -ERESTARTSYS;
+               }
                format = snd_pcm_oss_format_from(runtime->oss.format);
                width = snd_pcm_format_physical_width(format);
                if (runtime->oss.buffer_used > 0) {
@@ -1680,10 +1687,8 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                                                   runtime->oss.buffer + runtime->oss.buffer_used,
                                                   size);
                        err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes);
-                       if (err < 0) {
-                               mutex_unlock(&runtime->oss.params_lock);
-                               return err;
-                       }
+                       if (err < 0)
+                               goto unlock;
                } else if (runtime->oss.period_ptr > 0) {
 #ifdef OSS_DEBUG
                        pcm_dbg(substream->pcm, "sync: period_ptr\n");
@@ -1693,10 +1698,8 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                                                   runtime->oss.buffer,
                                                   size * 8 / width);
                        err = snd_pcm_oss_sync1(substream, size);
-                       if (err < 0) {
-                               mutex_unlock(&runtime->oss.params_lock);
-                               return err;
-                       }
+                       if (err < 0)
+                               goto unlock;
                }
                /*
                 * The ALSA's period might be a bit large than OSS one.
@@ -1727,7 +1730,11 @@ static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file)
                                snd_pcm_lib_writev(substream, buffers, size);
                        }
                }
+unlock:
                mutex_unlock(&runtime->oss.params_lock);
+               atomic_dec(&runtime->oss.rw_ref);
+               if (err < 0)
+                       return err;
                /*
                 * finish sync: drain the buffer
                 */
@@ -1775,6 +1782,8 @@ static int snd_pcm_oss_set_rate(struct snd_pcm_oss_file *pcm_oss_file, int rate)
                        rate = 192000;
                if (mutex_lock_interruptible(&runtime->oss.params_lock))
                        return -ERESTARTSYS;
+               if (atomic_read(&runtime->oss.rw_ref))
+                       return -EBUSY;
                if (runtime->oss.rate != rate) {
                        runtime->oss.params = 1;
                        runtime->oss.rate = rate;
@@ -1809,6 +1818,8 @@ static int snd_pcm_oss_set_channels(struct snd_pcm_oss_file *pcm_oss_file, unsig
                runtime = substream->runtime;
                if (mutex_lock_interruptible(&runtime->oss.params_lock))
                        return -ERESTARTSYS;
+               if (atomic_read(&runtime->oss.rw_ref))
+                       return -EBUSY;
                if (runtime->oss.channels != channels) {
                        runtime->oss.params = 1;
                        runtime->oss.channels = channels;
@@ -1899,6 +1910,8 @@ static int snd_pcm_oss_set_format(struct snd_pcm_oss_file *pcm_oss_file, int for
                        if (substream == NULL)
                                continue;
                        runtime = substream->runtime;
+                       if (atomic_read(&runtime->oss.rw_ref))
+                               return -EBUSY;
                        if (mutex_lock_interruptible(&runtime->oss.params_lock))
                                return -ERESTARTSYS;
                        if (runtime->oss.format != format) {
@@ -1953,6 +1966,8 @@ static int snd_pcm_oss_set_subdivide(struct snd_pcm_oss_file *pcm_oss_file, int
                if (substream == NULL)
                        continue;
                runtime = substream->runtime;
+               if (atomic_read(&runtime->oss.rw_ref))
+                       return -EBUSY;
                if (mutex_lock_interruptible(&runtime->oss.params_lock))
                        return -ERESTARTSYS;
                err = snd_pcm_oss_set_subdivide1(substream, subdivide);
@@ -1991,6 +2006,8 @@ static int snd_pcm_oss_set_fragment(struct snd_pcm_oss_file *pcm_oss_file, unsig
                if (substream == NULL)
                        continue;
                runtime = substream->runtime;
+               if (atomic_read(&runtime->oss.rw_ref))
+                       return -EBUSY;
                if (mutex_lock_interruptible(&runtime->oss.params_lock))
                        return -ERESTARTSYS;
                err = snd_pcm_oss_set_fragment1(substream, val);
@@ -2385,6 +2402,7 @@ static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream,
        runtime->oss.maxfrags = 0;
        runtime->oss.subdivision = 0;
        substream->pcm_release = snd_pcm_oss_release_substream;
+       atomic_set(&runtime->oss.rw_ref, 0);
 }
 
 static int snd_pcm_oss_release_file(struct snd_pcm_oss_file *pcm_oss_file)