diff options
Diffstat (limited to 'sound/core/control.c')
-rw-r--r-- | sound/core/control.c | 304 |
1 files changed, 241 insertions, 63 deletions
diff --git a/sound/core/control.c b/sound/core/control.c index 7a4d8690ce41..aa0c0cf182af 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -11,6 +11,7 @@ #include <linux/vmalloc.h> #include <linux/time.h> #include <linux/mm.h> +#include <linux/math64.h> #include <linux/sched/signal.h> #include <sound/core.h> #include <sound/minors.h> @@ -248,7 +249,8 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, SNDRV_CTL_ELEM_ACCESS_INACTIVE | SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND | - SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK | + SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK); err = snd_ctl_new(&kctl, count, access, NULL); if (err < 0) @@ -758,51 +760,199 @@ static int snd_ctl_elem_list(struct snd_card *card, return err; } -static bool validate_element_member_dimension(struct snd_ctl_elem_info *info) +/* Check whether the given kctl info is valid */ +static int snd_ctl_check_elem_info(struct snd_card *card, + const struct snd_ctl_elem_info *info) { - unsigned int members; - unsigned int i; + static const unsigned int max_value_counts[] = { + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, + [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, + [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, + [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, + }; + + if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || + info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) { + if (card) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: invalid type %d\n", + info->id.iface, info->id.device, + info->id.subdevice, info->id.name, + info->id.index, info->type); + return -EINVAL; + } + if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && + info->value.enumerated.items == 0) { + if (card) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: zero enum items\n", + info->id.iface, info->id.device, + info->id.subdevice, info->id.name, + info->id.index); + return -EINVAL; + } + if (info->count > max_value_counts[info->type]) { + if (card) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: invalid count %d\n", + info->id.iface, info->id.device, + info->id.subdevice, info->id.name, + info->id.index, info->count); + return -EINVAL; + } + + return 0; +} - if (info->dimen.d[0] == 0) - return true; +/* The capacity of struct snd_ctl_elem_value.value.*/ +static const unsigned int value_sizes[] = { + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), + [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), + [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), + [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), +}; - members = 1; - for (i = 0; i < ARRAY_SIZE(info->dimen.d); ++i) { - if (info->dimen.d[i] == 0) - break; - members *= info->dimen.d[i]; - - /* - * info->count should be validated in advance, to guarantee - * calculation soundness. - */ - if (members > info->count) - return false; +#ifdef CONFIG_SND_CTL_VALIDATION +/* fill the remaining snd_ctl_elem_value data with the given pattern */ +static void fill_remaining_elem_value(struct snd_ctl_elem_value *control, + struct snd_ctl_elem_info *info, + u32 pattern) +{ + size_t offset = value_sizes[info->type] * info->count; + + offset = (offset + sizeof(u32) - 1) / sizeof(u32); + memset32((u32 *)control->value.bytes.data + offset, pattern, + sizeof(control->value) / sizeof(u32) - offset); +} + +/* check whether the given integer ctl value is valid */ +static int sanity_check_int_value(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + int i) +{ + long long lval, lmin, lmax, lstep; + u64 rem; + + switch (info->type) { + default: + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + lval = control->value.integer.value[i]; + lmin = 0; + lmax = 1; + lstep = 0; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + lval = control->value.integer.value[i]; + lmin = info->value.integer.min; + lmax = info->value.integer.max; + lstep = info->value.integer.step; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + lval = control->value.integer64.value[i]; + lmin = info->value.integer64.min; + lmax = info->value.integer64.max; + lstep = info->value.integer64.step; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + lval = control->value.enumerated.item[i]; + lmin = 0; + lmax = info->value.enumerated.items - 1; + lstep = 0; + break; } - for (++i; i < ARRAY_SIZE(info->dimen.d); ++i) { - if (info->dimen.d[i] > 0) - return false; + if (lval < lmin || lval > lmax) { + dev_err(card->dev, + "control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index, lval, lmin, lmax, i); + return -EINVAL; + } + if (lstep) { + div64_u64_rem(lval, lstep, &rem); + if (rem) { + dev_err(card->dev, + "control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index, lval, lstep, i); + return -EINVAL; + } } - return members == info->count; + return 0; } -static int snd_ctl_elem_info(struct snd_ctl_file *ctl, - struct snd_ctl_elem_info *info) +/* perform sanity checks to the given snd_ctl_elem_value object */ +static int sanity_check_elem_value(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + u32 pattern) +{ + size_t offset; + int i, ret = 0; + u32 *p; + + switch (info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < info->count; i++) { + ret = sanity_check_int_value(card, control, info, i); + if (ret < 0) + return ret; + } + break; + default: + break; + } + + /* check whether the remaining area kept untouched */ + offset = value_sizes[info->type] * info->count; + offset = (offset + sizeof(u32) - 1) / sizeof(u32); + p = (u32 *)control->value.bytes.data + offset; + for (; offset < sizeof(control->value) / sizeof(u32); offset++, p++) { + if (*p != pattern) { + ret = -EINVAL; + break; + } + *p = 0; /* clear the checked area */ + } + + return ret; +} +#else +static inline void fill_remaining_elem_value(struct snd_ctl_elem_value *control, + struct snd_ctl_elem_info *info, + u32 pattern) +{ +} + +static inline int sanity_check_elem_value(struct snd_card *card, + struct snd_ctl_elem_value *control, + struct snd_ctl_elem_info *info, + u32 pattern) +{ + return 0; +} +#endif + +static int __snd_ctl_elem_info(struct snd_card *card, + struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *info, + struct snd_ctl_file *ctl) { - struct snd_card *card = ctl->card; - struct snd_kcontrol *kctl; struct snd_kcontrol_volatile *vd; unsigned int index_offset; int result; - down_read(&card->controls_rwsem); - kctl = snd_ctl_find_id(card, &info->id); - if (kctl == NULL) { - up_read(&card->controls_rwsem); - return -ENOENT; - } #ifdef CONFIG_SND_DEBUG info->access = 0; #endif @@ -821,7 +971,26 @@ static int snd_ctl_elem_info(struct snd_ctl_file *ctl, } else { info->owner = -1; } + if (!snd_ctl_skip_validation(info) && + snd_ctl_check_elem_info(card, info) < 0) + result = -EINVAL; } + return result; +} + +static int snd_ctl_elem_info(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info *info) +{ + struct snd_card *card = ctl->card; + struct snd_kcontrol *kctl; + int result; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id(card, &info->id); + if (kctl == NULL) + result = -ENOENT; + else + result = __snd_ctl_elem_info(card, kctl, info, ctl); up_read(&card->controls_rwsem); return result; } @@ -840,6 +1009,8 @@ static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, result = snd_ctl_elem_info(ctl, &info); if (result < 0) return result; + /* drop internal access flags */ + info.access &= ~SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK; if (copy_to_user(_info, &info, sizeof(info))) return -EFAULT; return result; @@ -851,6 +1022,9 @@ static int snd_ctl_elem_read(struct snd_card *card, struct snd_kcontrol *kctl; struct snd_kcontrol_volatile *vd; unsigned int index_offset; + struct snd_ctl_elem_info info; + const u32 pattern = 0xdeadbeef; + int ret; kctl = snd_ctl_find_id(card, &control->id); if (kctl == NULL) @@ -862,7 +1036,31 @@ static int snd_ctl_elem_read(struct snd_card *card, return -EPERM; snd_ctl_build_ioff(&control->id, kctl, index_offset); - return kctl->get(kctl, control); + +#ifdef CONFIG_SND_CTL_VALIDATION + /* info is needed only for validation */ + memset(&info, 0, sizeof(info)); + info.id = control->id; + ret = __snd_ctl_elem_info(card, kctl, &info, NULL); + if (ret < 0) + return ret; +#endif + + if (!snd_ctl_skip_validation(&info)) + fill_remaining_elem_value(control, &info, pattern); + ret = kctl->get(kctl, control); + if (ret < 0) + return ret; + if (!snd_ctl_skip_validation(&info) && + sanity_check_elem_value(card, control, &info, pattern) < 0) { + dev_err(card->dev, + "control %i:%i:%i:%s:%i: access overflow\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index); + return -EINVAL; + } + return ret; } static int snd_ctl_elem_read_user(struct snd_card *card, @@ -1203,23 +1401,6 @@ static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) static int snd_ctl_elem_add(struct snd_ctl_file *file, struct snd_ctl_elem_info *info, int replace) { - /* The capacity of struct snd_ctl_elem_value.value.*/ - static const unsigned int value_sizes[] = { - [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), - [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), - [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), - [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), - [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), - [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), - }; - static const unsigned int max_value_counts[] = { - [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, - [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, - [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, - [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, - [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, - [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, - }; struct snd_card *card = file->card; struct snd_kcontrol *kctl; unsigned int count; @@ -1271,16 +1452,12 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, * Check information and calculate the size of data specific to * this userspace control. */ - if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || - info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) - return -EINVAL; - if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && - info->value.enumerated.items == 0) - return -EINVAL; - if (info->count < 1 || - info->count > max_value_counts[info->type]) - return -EINVAL; - if (!validate_element_member_dimension(info)) + /* pass NULL to card for suppressing error messages */ + err = snd_ctl_check_elem_info(NULL, info); + if (err < 0) + return err; + /* user-space control doesn't allow zero-size data */ + if (info->count < 1) return -EINVAL; private_size = value_sizes[info->type] * info->count; @@ -1430,8 +1607,9 @@ static int call_tlv_handler(struct snd_ctl_file *file, int op_flag, if (kctl->tlv.c == NULL) return -ENXIO; - /* When locked, this is unavailable. */ - if (vd->owner != NULL && vd->owner != file) + /* Write and command operations are not allowed for locked element. */ + if (op_flag != SNDRV_CTL_TLV_OP_READ && + vd->owner != NULL && vd->owner != file) return -EPERM; return kctl->tlv.c(kctl, op_flag, size, buf); @@ -1854,7 +2032,7 @@ static int snd_ctl_dev_free(struct snd_device *device) */ int snd_ctl_create(struct snd_card *card) { - static struct snd_device_ops ops = { + static const struct snd_device_ops ops = { .dev_free = snd_ctl_dev_free, .dev_register = snd_ctl_dev_register, .dev_disconnect = snd_ctl_dev_disconnect, |