ALSA: pcm: Fix potential data race at PCM memory allocation helpers
authorTakashi Iwai <tiwai@suse.de>
Mon, 3 Jul 2023 11:24:30 +0000 (13:24 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 19 Jul 2023 14:21:47 +0000 (16:21 +0200)
commit bd55842ed998a622ba6611fe59b3358c9f76773d upstream.

The PCM memory allocation helpers have a sanity check against too many
buffer allocations.  However, the check is performed without a proper
lock and the allocation isn't serialized; this allows user to allocate
more memories than predefined max size.

Practically seen, this isn't really a big problem, as it's more or
less some "soft limit" as a sanity check, and it's not possible to
allocate unlimitedly.  But it's still better to address this for more
consistent behavior.

The patch covers the size check in do_alloc_pages() with the
card->memory_mutex, and increases the allocated size there for
preventing the further overflow.  When the actual allocation fails,
the size is decreased accordingly.

Reported-by: BassCheck <bass@buaa.edu.cn>
Reported-by: Tuo Li <islituo@gmail.com>
Link: https://lore.kernel.org/r/CADm8Tek6t0WedK+3Y6rbE5YEt19tML8BUL45N2ji4ZAz1KcN_A@mail.gmail.com
Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/20230703112430.30634-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
sound/core/pcm_memory.c

index 7bde7fb..a0b9514 100644 (file)
@@ -31,15 +31,41 @@ static unsigned long max_alloc_per_card = 32UL * 1024UL * 1024UL;
 module_param(max_alloc_per_card, ulong, 0644);
 MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card.");
 
+static void __update_allocated_size(struct snd_card *card, ssize_t bytes)
+{
+       card->total_pcm_alloc_bytes += bytes;
+}
+
+static void update_allocated_size(struct snd_card *card, ssize_t bytes)
+{
+       mutex_lock(&card->memory_mutex);
+       __update_allocated_size(card, bytes);
+       mutex_unlock(&card->memory_mutex);
+}
+
+static void decrease_allocated_size(struct snd_card *card, size_t bytes)
+{
+       mutex_lock(&card->memory_mutex);
+       WARN_ON(card->total_pcm_alloc_bytes < bytes);
+       __update_allocated_size(card, -(ssize_t)bytes);
+       mutex_unlock(&card->memory_mutex);
+}
+
 static int do_alloc_pages(struct snd_card *card, int type, struct device *dev,
                          int str, size_t size, struct snd_dma_buffer *dmab)
 {
        enum dma_data_direction dir;
        int err;
 
+       /* check and reserve the requested size */
+       mutex_lock(&card->memory_mutex);
        if (max_alloc_per_card &&
-           card->total_pcm_alloc_bytes + size > max_alloc_per_card)
+           card->total_pcm_alloc_bytes + size > max_alloc_per_card) {
+               mutex_unlock(&card->memory_mutex);
                return -ENOMEM;
+       }
+       __update_allocated_size(card, size);
+       mutex_unlock(&card->memory_mutex);
 
        if (str == SNDRV_PCM_STREAM_PLAYBACK)
                dir = DMA_TO_DEVICE;
@@ -47,9 +73,14 @@ static int do_alloc_pages(struct snd_card *card, int type, struct device *dev,
                dir = DMA_FROM_DEVICE;
        err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab);
        if (!err) {
-               mutex_lock(&card->memory_mutex);
-               card->total_pcm_alloc_bytes += dmab->bytes;
-               mutex_unlock(&card->memory_mutex);
+               /* the actual allocation size might be bigger than requested,
+                * and we need to correct the account
+                */
+               if (dmab->bytes != size)
+                       update_allocated_size(card, dmab->bytes - size);
+       } else {
+               /* take back on allocation failure */
+               decrease_allocated_size(card, size);
        }
        return err;
 }
@@ -58,10 +89,7 @@ static void do_free_pages(struct snd_card *card, struct snd_dma_buffer *dmab)
 {
        if (!dmab->area)
                return;
-       mutex_lock(&card->memory_mutex);
-       WARN_ON(card->total_pcm_alloc_bytes < dmab->bytes);
-       card->total_pcm_alloc_bytes -= dmab->bytes;
-       mutex_unlock(&card->memory_mutex);
+       decrease_allocated_size(card, dmab->bytes);
        snd_dma_free_pages(dmab);
        dmab->area = NULL;
 }