ALSA: control: Add memory consumption limit to user controls

ALSA control interface allows users to add arbitrary control elements
(called "user controls" or "user elements"), and its resource usage is
limited just by the max number of control sets (currently 32).  This
limit, however, is quite loose: each allocation of control set may
have 1028 elements, and each element may have up to 512 bytes (ILP32) or
1024 bytes (LP64) of value data. Moreover, each control set may contain
the enum strings and TLV data, which can be up to 64kB and 128kB,
respectively.  Totally, the whole memory consumption may go over 38MB --
it's quite large, and we'd rather like to reduce the size.

OTOH, there have been other requests even to increase the max number
of user elements; e.g. ALSA firewire stack require the more user
controls, hence we want to raise the bar, too.

For satisfying both requirements, this patch changes the management of
user controls: instead of setting the upper limit of the number of
user controls, we check the actual memory allocation size and set the
upper limit of the total allocation in bytes.  As long as the memory
consumption stays below the limit, more user controls are allowed than
the current limit 32. At the same time, we set the lower limit (8MB)
as default than the current theoretical limit, in order to lower the
risk of DoS.

As a compromise for lowering the default limit, now the actual memory
limit is defined as a module option, 'max_user_ctl_alloc_size', so that
user can increase/decrease the limit if really needed, too.

Link: https://lore.kernel.org/r/s5htur3zl5e.wl-tiwai@suse.de
Co-developed-by: Takashi Iwai <tiwai@suse.de>
Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Tested-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Link: https://lore.kernel.org/r/20210408103149.40357-1-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Sakamoto 2021-04-08 19:31:49 +09:00 committed by Takashi Iwai
parent 884c7094a2
commit 66c6d1ef86
2 changed files with 52 additions and 25 deletions

View File

@ -100,7 +100,7 @@ struct snd_card {
struct rw_semaphore controls_rwsem; /* controls list lock */
rwlock_t ctl_files_rwlock; /* ctl_files list lock */
int controls_count; /* count of all controls */
int user_ctl_count; /* count of all user controls */
size_t user_ctl_alloc_size; // current memory allocation by user controls.
struct list_head controls; /* all controls for this card */
struct list_head ctl_files; /* active control files */

View File

@ -7,6 +7,7 @@
#include <linux/threads.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/time.h>
@ -18,8 +19,11 @@
#include <sound/info.h>
#include <sound/control.h>
/* max number of user-defined controls */
#define MAX_USER_CONTROLS 32
// Max allocation size for user controls.
static int max_user_ctl_alloc_size = 8 * 1024 * 1024;
module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444);
MODULE_PARM_DESC(max_user_ctl_alloc_size, "Max allocation size for user controls");
#define MAX_CONTROL_COUNT 1028
struct snd_kctl_ioctl {
@ -561,9 +565,6 @@ static int snd_ctl_remove_user_ctl(struct snd_ctl_file * file,
goto error;
}
ret = snd_ctl_remove(card, kctl);
if (ret < 0)
goto error;
card->user_ctl_count--;
error:
up_write(&card->controls_rwsem);
return ret;
@ -1265,6 +1266,12 @@ struct user_element {
void *priv_data; /* private data (like strings for enumerated type) */
};
// check whether the addition (in bytes) of user ctl element may overflow the limit.
static bool check_user_elem_overflow(struct snd_card *card, ssize_t add)
{
return (ssize_t)card->user_ctl_alloc_size + add > max_user_ctl_alloc_size;
}
static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
@ -1342,6 +1349,10 @@ static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
if (size > 1024 * 128) /* sane value */
return -EINVAL;
// does the TLV size change cause overflow?
if (check_user_elem_overflow(ue->card, (ssize_t)(size - ue->tlv_data_size)))
return -ENOMEM;
container = vmemdup_user(buf, size);
if (IS_ERR(container))
return PTR_ERR(container);
@ -1359,11 +1370,16 @@ static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf,
for (i = 0; i < kctl->count; ++i)
kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
mask = SNDRV_CTL_EVENT_MASK_INFO;
} else {
ue->card->user_ctl_alloc_size -= ue->tlv_data_size;
ue->tlv_data_size = 0;
kvfree(ue->tlv_data);
}
kvfree(ue->tlv_data);
ue->tlv_data = container;
ue->tlv_data_size = size;
// decremented at private_free.
ue->card->user_ctl_alloc_size += size;
mask |= SNDRV_CTL_EVENT_MASK_TLV;
for (i = 0; i < kctl->count; ++i)
@ -1405,16 +1421,17 @@ static int snd_ctl_elem_init_enum_names(struct user_element *ue)
unsigned int i;
const uintptr_t user_ptrval = ue->info.value.enumerated.names_ptr;
if (ue->info.value.enumerated.names_length > 64 * 1024)
buf_len = ue->info.value.enumerated.names_length;
if (buf_len > 64 * 1024)
return -EINVAL;
names = vmemdup_user((const void __user *)user_ptrval,
ue->info.value.enumerated.names_length);
if (check_user_elem_overflow(ue->card, buf_len))
return -ENOMEM;
names = vmemdup_user((const void __user *)user_ptrval, buf_len);
if (IS_ERR(names))
return PTR_ERR(names);
/* check that there are enough valid names */
buf_len = ue->info.value.enumerated.names_length;
p = names;
for (i = 0; i < ue->info.value.enumerated.items; ++i) {
name_len = strnlen(p, buf_len);
@ -1428,14 +1445,27 @@ static int snd_ctl_elem_init_enum_names(struct user_element *ue)
ue->priv_data = names;
ue->info.value.enumerated.names_ptr = 0;
// increment the allocation size; decremented again at private_free.
ue->card->user_ctl_alloc_size += ue->info.value.enumerated.names_length;
return 0;
}
static size_t compute_user_elem_size(size_t size, unsigned int count)
{
return sizeof(struct user_element) + size * count;
}
static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
{
struct user_element *ue = kcontrol->private_data;
// decrement the allocation size.
ue->card->user_ctl_alloc_size -= compute_user_elem_size(ue->elem_data_size, kcontrol->count);
ue->card->user_ctl_alloc_size -= ue->tlv_data_size;
if (ue->priv_data)
ue->card->user_ctl_alloc_size -= ue->info.value.enumerated.names_length;
kvfree(ue->tlv_data);
kvfree(ue->priv_data);
kfree(ue);
@ -1449,6 +1479,7 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
unsigned int count;
unsigned int access;
long private_size;
size_t alloc_size;
struct user_element *ue;
unsigned int offset;
int err;
@ -1466,13 +1497,6 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
return err;
}
/*
* The number of userspace controls are counted control by control,
* not element by element.
*/
if (card->user_ctl_count + 1 > MAX_USER_CONTROLS)
return -ENOMEM;
/* Check the number of elements for this userspace control. */
count = info->owner;
if (count == 0)
@ -1503,6 +1527,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
if (info->count < 1)
return -EINVAL;
private_size = value_sizes[info->type] * info->count;
alloc_size = compute_user_elem_size(private_size, count);
if (check_user_elem_overflow(card, alloc_size))
return -ENOMEM;
/*
* Keep memory object for this userspace control. After passing this
@ -1514,16 +1542,18 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
if (err < 0)
return err;
memcpy(&kctl->id, &info->id, sizeof(kctl->id));
kctl->private_data = kzalloc(sizeof(struct user_element) + private_size * count,
GFP_KERNEL);
if (kctl->private_data == NULL) {
ue = kzalloc(alloc_size, GFP_KERNEL);
if (!ue) {
kfree(kctl);
return -ENOMEM;
}
kctl->private_data = ue;
kctl->private_free = snd_ctl_elem_user_free;
// increment the allocated size; decremented again at private_free.
card->user_ctl_alloc_size += alloc_size;
/* Set private data for this userspace control. */
ue = (struct user_element *)kctl->private_data;
ue->card = card;
ue->info = *info;
ue->info.access = 0;
@ -1565,9 +1595,6 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
* applications because the field originally means PID of a process
* which locks the element.
*/
card->user_ctl_count++;
unlock:
up_write(&card->controls_rwsem);
return err;