Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ALSA: control: Add verification for kctl accesses

The current implementation of ALSA control API fully relies on the
callbacks of each driver, and there is no verification of the values
passed via API. This patch is an attempt to improve the situation
slightly by adding the validation code for the values stored via info
and get callbacks.

The patch adds a new kconfig, CONFIG_SND_CTL_VALIDATION. It depends
on CONFIG_SND_DEBUG and off as default since the validation would
require a slight overhead including the additional call of info
callback at each get callback invocation.

When this config is enabled, the values stored by each info callback
invocation are verified, namely:
- Whether the info type is valid
- Whether the number of enum items is non-zero
- Whether the given info count is within the allowed boundary

Similarly, the values stored at each get callback are verified as
well:
- Whether the values are within the given range
- Whether the values are aligned with the given step
- Whether any further changes are seen in the data array over the
given info count

The last point helps identifying a possibly invalid data type access,
typically a case where the info callback declares the type being
SNDRV_CTL_ELEM_TYPE_ENUMERATED while the get/put callbacks store
the values in value.integer.value[] array.

When a validation fails, the ALSA core logs an error message including
the device and the control ID, and the API call also returns an
error. So, with the new validation turned on, the driver behavior
difference may be visible on user-space, too -- it's intentional,
though, so that we can catch an error more clearly.

The patch also introduces a new ctl access type,
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK. A driver may pass this flag with
other access bits to indicate that the ctl element won't be verified.
It's useful when a driver code is specially written to access the data
greater than info->count size by some reason. For example, this flag
is actually set now in HD-audio HDMI codec driver which needs to clear
the data array in the case of the disconnected monitor.

Also, the PCM channel-map helper code is slightly modified to avoid
the false-positive hit by this validation code, too.

Link: https://lore.kernel.org/r/20200104083556.27789-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>

+268 -39
+10
include/sound/control.h
··· 22 22 unsigned int size, 23 23 unsigned int __user *tlv); 24 24 25 + /* internal flag for skipping validations */ 26 + #ifdef CONFIG_SND_CTL_VALIDATION 27 + #define SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK (1 << 27) 28 + #define snd_ctl_skip_validation(info) \ 29 + ((info)->access & SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK) 30 + #else 31 + #define SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK 0 32 + #define snd_ctl_skip_validation(info) true 33 + #endif 34 + 25 35 enum { 26 36 SNDRV_CTL_TLV_OP_READ = 0, 27 37 SNDRV_CTL_TLV_OP_WRITE = 1,
+9
sound/core/Kconfig
··· 178 178 sound clicking when system is loaded, it may help to determine 179 179 the process or driver which causes the scheduling gaps. 180 180 181 + config SND_CTL_VALIDATION 182 + bool "Perform sanity-checks for each control element access" 183 + depends on SND_DEBUG 184 + help 185 + Say Y to enable the additional validation of each control element 186 + access, including sanity-checks like whether the values returned 187 + from the driver are in the proper ranges or the check of the invalid 188 + access at out-of-array areas. 189 + 181 190 config SND_VMASTER 182 191 bool 183 192
+246 -37
sound/core/control.c
··· 11 11 #include <linux/vmalloc.h> 12 12 #include <linux/time.h> 13 13 #include <linux/mm.h> 14 + #include <linux/math64.h> 14 15 #include <linux/sched/signal.h> 15 16 #include <sound/core.h> 16 17 #include <sound/minors.h> ··· 249 248 SNDRV_CTL_ELEM_ACCESS_INACTIVE | 250 249 SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | 251 250 SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND | 252 - SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); 251 + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK | 252 + SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK); 253 253 254 254 err = snd_ctl_new(&kctl, count, access, NULL); 255 255 if (err < 0) ··· 760 758 return err; 761 759 } 762 760 763 - static int snd_ctl_elem_info(struct snd_ctl_file *ctl, 764 - struct snd_ctl_elem_info *info) 761 + /* Check whether the given kctl info is valid */ 762 + static int snd_ctl_check_elem_info(struct snd_card *card, 763 + const struct snd_ctl_elem_info *info) 765 764 { 766 - struct snd_card *card = ctl->card; 767 - struct snd_kcontrol *kctl; 765 + static const unsigned int max_value_counts[] = { 766 + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, 767 + [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, 768 + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, 769 + [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, 770 + [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, 771 + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, 772 + }; 773 + 774 + if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || 775 + info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) { 776 + if (card) 777 + dev_err(card->dev, 778 + "control %i:%i:%i:%s:%i: invalid type %d\n", 779 + info->id.iface, info->id.device, 780 + info->id.subdevice, info->id.name, 781 + info->id.index, info->type); 782 + return -EINVAL; 783 + } 784 + if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && 785 + info->value.enumerated.items == 0) { 786 + if (card) 787 + dev_err(card->dev, 788 + "control %i:%i:%i:%s:%i: zero enum items\n", 789 + info->id.iface, info->id.device, 790 + info->id.subdevice, info->id.name, 791 + info->id.index); 792 + return -EINVAL; 793 + } 794 + if (info->count > max_value_counts[info->type]) { 795 + if (card) 796 + dev_err(card->dev, 797 + "control %i:%i:%i:%s:%i: invalid count %d\n", 798 + info->id.iface, info->id.device, 799 + info->id.subdevice, info->id.name, 800 + info->id.index, info->count); 801 + return -EINVAL; 802 + } 803 + 804 + return 0; 805 + } 806 + 807 + /* The capacity of struct snd_ctl_elem_value.value.*/ 808 + static const unsigned int value_sizes[] = { 809 + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), 810 + [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), 811 + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), 812 + [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), 813 + [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), 814 + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), 815 + }; 816 + 817 + #ifdef CONFIG_SND_CTL_VALIDATION 818 + /* fill the remaining snd_ctl_elem_value data with the given pattern */ 819 + static void fill_remaining_elem_value(struct snd_ctl_elem_value *control, 820 + struct snd_ctl_elem_info *info, 821 + u32 pattern) 822 + { 823 + size_t offset = value_sizes[info->type] * info->count; 824 + 825 + offset = (offset + sizeof(u32) - 1) / sizeof(u32); 826 + memset32((u32 *)control->value.bytes.data + offset, pattern, 827 + sizeof(control->value) / sizeof(u32) - offset); 828 + } 829 + 830 + /* check whether the given integer ctl value is valid */ 831 + static int sanity_check_int_value(struct snd_card *card, 832 + const struct snd_ctl_elem_value *control, 833 + const struct snd_ctl_elem_info *info, 834 + int i) 835 + { 836 + long long lval, lmin, lmax, lstep; 837 + u64 rem; 838 + 839 + switch (info->type) { 840 + default: 841 + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: 842 + lval = control->value.integer.value[i]; 843 + lmin = 0; 844 + lmax = 1; 845 + lstep = 0; 846 + break; 847 + case SNDRV_CTL_ELEM_TYPE_INTEGER: 848 + lval = control->value.integer.value[i]; 849 + lmin = info->value.integer.min; 850 + lmax = info->value.integer.max; 851 + lstep = info->value.integer.step; 852 + break; 853 + case SNDRV_CTL_ELEM_TYPE_INTEGER64: 854 + lval = control->value.integer64.value[i]; 855 + lmin = info->value.integer64.min; 856 + lmax = info->value.integer64.max; 857 + lstep = info->value.integer64.step; 858 + break; 859 + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: 860 + lval = control->value.enumerated.item[i]; 861 + lmin = 0; 862 + lmax = info->value.enumerated.items - 1; 863 + lstep = 0; 864 + break; 865 + } 866 + 867 + if (lval < lmin || lval > lmax) { 868 + dev_err(card->dev, 869 + "control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n", 870 + control->id.iface, control->id.device, 871 + control->id.subdevice, control->id.name, 872 + control->id.index, lval, lmin, lmax, i); 873 + return -EINVAL; 874 + } 875 + if (lstep) { 876 + div64_u64_rem(lval, lstep, &rem); 877 + if (rem) { 878 + dev_err(card->dev, 879 + "control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n", 880 + control->id.iface, control->id.device, 881 + control->id.subdevice, control->id.name, 882 + control->id.index, lval, lstep, i); 883 + return -EINVAL; 884 + } 885 + } 886 + 887 + return 0; 888 + } 889 + 890 + /* perform sanity checks to the given snd_ctl_elem_value object */ 891 + static int sanity_check_elem_value(struct snd_card *card, 892 + const struct snd_ctl_elem_value *control, 893 + const struct snd_ctl_elem_info *info, 894 + u32 pattern) 895 + { 896 + size_t offset; 897 + int i, ret; 898 + u32 *p; 899 + 900 + switch (info->type) { 901 + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: 902 + case SNDRV_CTL_ELEM_TYPE_INTEGER: 903 + case SNDRV_CTL_ELEM_TYPE_INTEGER64: 904 + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: 905 + for (i = 0; i < info->count; i++) { 906 + ret = sanity_check_int_value(card, control, info, i); 907 + if (ret < 0) 908 + return ret; 909 + } 910 + break; 911 + default: 912 + break; 913 + } 914 + 915 + /* check whether the remaining area kept untouched */ 916 + offset = value_sizes[info->type] * info->count; 917 + offset = (offset + sizeof(u32) - 1) / sizeof(u32); 918 + p = (u32 *)control->value.bytes.data + offset; 919 + for (; offset < sizeof(control->value) / sizeof(u32); offset++, p++) { 920 + if (*p != pattern) { 921 + ret = -EINVAL; 922 + break; 923 + } 924 + *p = 0; /* clear the checked area */ 925 + } 926 + 927 + return ret; 928 + } 929 + #else 930 + static inline void fill_remaining_elem_value(struct snd_ctl_elem_value *control, 931 + struct snd_ctl_elem_info *info, 932 + u32 pattern) 933 + { 934 + } 935 + 936 + static inline int sanity_check_elem_value(struct snd_card *card, 937 + struct snd_ctl_elem_value *control, 938 + struct snd_ctl_elem_info *info, 939 + u32 pattern) 940 + { 941 + return 0; 942 + } 943 + #endif 944 + 945 + static int __snd_ctl_elem_info(struct snd_card *card, 946 + struct snd_kcontrol *kctl, 947 + struct snd_ctl_elem_info *info, 948 + struct snd_ctl_file *ctl) 949 + { 768 950 struct snd_kcontrol_volatile *vd; 769 951 unsigned int index_offset; 770 952 int result; 771 953 772 - down_read(&card->controls_rwsem); 773 - kctl = snd_ctl_find_id(card, &info->id); 774 - if (kctl == NULL) { 775 - up_read(&card->controls_rwsem); 776 - return -ENOENT; 777 - } 778 954 #ifdef CONFIG_SND_DEBUG 779 955 info->access = 0; 780 956 #endif ··· 971 791 } else { 972 792 info->owner = -1; 973 793 } 794 + if (!snd_ctl_skip_validation(info) && 795 + snd_ctl_check_elem_info(card, info) < 0) 796 + result = -EINVAL; 974 797 } 798 + return result; 799 + } 800 + 801 + static int snd_ctl_elem_info(struct snd_ctl_file *ctl, 802 + struct snd_ctl_elem_info *info) 803 + { 804 + struct snd_card *card = ctl->card; 805 + struct snd_kcontrol *kctl; 806 + int result; 807 + 808 + down_read(&card->controls_rwsem); 809 + kctl = snd_ctl_find_id(card, &info->id); 810 + if (kctl == NULL) 811 + result = -ENOENT; 812 + else 813 + result = __snd_ctl_elem_info(card, kctl, info, ctl); 975 814 up_read(&card->controls_rwsem); 976 815 return result; 977 816 } ··· 1009 810 result = snd_ctl_elem_info(ctl, &info); 1010 811 if (result < 0) 1011 812 return result; 813 + /* drop internal access flags */ 814 + info.access &= ~SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK; 1012 815 if (copy_to_user(_info, &info, sizeof(info))) 1013 816 return -EFAULT; 1014 817 return result; ··· 1022 821 struct snd_kcontrol *kctl; 1023 822 struct snd_kcontrol_volatile *vd; 1024 823 unsigned int index_offset; 824 + struct snd_ctl_elem_info info; 825 + const u32 pattern = 0xdeadbeef; 826 + int ret; 1025 827 1026 828 kctl = snd_ctl_find_id(card, &control->id); 1027 829 if (kctl == NULL) ··· 1036 832 return -EPERM; 1037 833 1038 834 snd_ctl_build_ioff(&control->id, kctl, index_offset); 1039 - return kctl->get(kctl, control); 835 + 836 + #ifdef CONFIG_SND_CTL_VALIDATION 837 + /* info is needed only for validation */ 838 + memset(&info, 0, sizeof(info)); 839 + info.id = control->id; 840 + ret = __snd_ctl_elem_info(card, kctl, &info, NULL); 841 + if (ret < 0) 842 + return ret; 843 + #endif 844 + 845 + if (!snd_ctl_skip_validation(&info)) 846 + fill_remaining_elem_value(control, &info, pattern); 847 + ret = kctl->get(kctl, control); 848 + if (ret < 0) 849 + return ret; 850 + if (!snd_ctl_skip_validation(&info) && 851 + sanity_check_elem_value(card, control, &info, pattern) < 0) { 852 + dev_err(card->dev, 853 + "control %i:%i:%i:%s:%i: access overflow\n", 854 + control->id.iface, control->id.device, 855 + control->id.subdevice, control->id.name, 856 + control->id.index); 857 + return -EINVAL; 858 + } 859 + return ret; 1040 860 } 1041 861 1042 862 static int snd_ctl_elem_read_user(struct snd_card *card, ··· 1401 1173 static int snd_ctl_elem_add(struct snd_ctl_file *file, 1402 1174 struct snd_ctl_elem_info *info, int replace) 1403 1175 { 1404 - /* The capacity of struct snd_ctl_elem_value.value.*/ 1405 - static const unsigned int value_sizes[] = { 1406 - [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), 1407 - [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), 1408 - [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), 1409 - [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), 1410 - [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), 1411 - [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), 1412 - }; 1413 - static const unsigned int max_value_counts[] = { 1414 - [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, 1415 - [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, 1416 - [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, 1417 - [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, 1418 - [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, 1419 - [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, 1420 - }; 1421 1176 struct snd_card *card = file->card; 1422 1177 struct snd_kcontrol *kctl; 1423 1178 unsigned int count; ··· 1452 1241 * Check information and calculate the size of data specific to 1453 1242 * this userspace control. 1454 1243 */ 1455 - if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || 1456 - info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) 1457 - return -EINVAL; 1458 - if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && 1459 - info->value.enumerated.items == 0) 1460 - return -EINVAL; 1461 - if (info->count < 1 || 1462 - info->count > max_value_counts[info->type]) 1244 + /* pass NULL to card for suppressing error messages */ 1245 + err = snd_ctl_check_elem_info(NULL, info); 1246 + if (err < 0) 1247 + return err; 1248 + /* user-space control doesn't allow zero-size data */ 1249 + if (info->count < 1) 1463 1250 return -EINVAL; 1464 1251 private_size = value_sizes[info->type] * info->count; 1465 1252
+1 -1
sound/core/pcm_lib.c
··· 2341 2341 if (!substream) 2342 2342 return -ENODEV; 2343 2343 memset(ucontrol->value.integer.value, 0, 2344 - sizeof(ucontrol->value.integer.value)); 2344 + sizeof(long) * info->max_channels); 2345 2345 if (!substream->runtime) 2346 2346 return 0; /* no channels set */ 2347 2347 for (map = info->chmap; map->channels; map++) {
+2 -1
sound/pci/hda/patch_hdmi.c
··· 372 372 } 373 373 374 374 static const struct snd_kcontrol_new eld_bytes_ctl = { 375 - .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, 375 + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE | 376 + SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK, 376 377 .iface = SNDRV_CTL_ELEM_IFACE_PCM, 377 378 .name = "ELD", 378 379 .info = hdmi_eld_ctl_info,