| From 6236d8bb2afcfe71b88ecea554e0dc638090a45f Mon Sep 17 00:00:00 2001 |
| From: Takashi Iwai <tiwai@suse.de> |
| Date: Sat, 27 Feb 2016 17:52:42 +0100 |
| Subject: ALSA: ctl: Fix ioctls for X32 ABI |
| |
| From: Takashi Iwai <tiwai@suse.de> |
| |
| commit 6236d8bb2afcfe71b88ecea554e0dc638090a45f upstream. |
| |
| The X32 ABI takes the same alignment like x86-64, and this may result |
| in the incompatible struct size from ia32. Unfortunately, we hit this |
| in some control ABI: struct snd_ctl_elem_value differs between them |
| due to the position of 64bit variable array. This ends up with the |
| unknown ioctl (ENOTTY) error. |
| |
| The fix is to add the compat entries for the new aligned struct. |
| |
| Reported-and-tested-by: Steven Newbury <steve@snewbury.org.uk> |
| Signed-off-by: Takashi Iwai <tiwai@suse.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| sound/core/control_compat.c | 90 ++++++++++++++++++++++++++++++++++++-------- |
| 1 file changed, 74 insertions(+), 16 deletions(-) |
| |
| --- a/sound/core/control_compat.c |
| +++ b/sound/core/control_compat.c |
| @@ -170,6 +170,19 @@ struct snd_ctl_elem_value32 { |
| unsigned char reserved[128]; |
| }; |
| |
| +#ifdef CONFIG_X86_X32 |
| +/* x32 has a different alignment for 64bit values from ia32 */ |
| +struct snd_ctl_elem_value_x32 { |
| + struct snd_ctl_elem_id id; |
| + unsigned int indirect; /* bit-field causes misalignment */ |
| + union { |
| + s32 integer[128]; |
| + unsigned char data[512]; |
| + s64 integer64[64]; |
| + } value; |
| + unsigned char reserved[128]; |
| +}; |
| +#endif /* CONFIG_X86_X32 */ |
| |
| /* get the value type and count of the control */ |
| static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, |
| @@ -219,9 +232,11 @@ static int get_elem_size(int type, int c |
| |
| static int copy_ctl_value_from_user(struct snd_card *card, |
| struct snd_ctl_elem_value *data, |
| - struct snd_ctl_elem_value32 __user *data32, |
| + void __user *userdata, |
| + void __user *valuep, |
| int *typep, int *countp) |
| { |
| + struct snd_ctl_elem_value32 __user *data32 = userdata; |
| int i, type, size; |
| int uninitialized_var(count); |
| unsigned int indirect; |
| @@ -239,8 +254,9 @@ static int copy_ctl_value_from_user(stru |
| if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
| type == SNDRV_CTL_ELEM_TYPE_INTEGER) { |
| for (i = 0; i < count; i++) { |
| + s32 __user *intp = valuep; |
| int val; |
| - if (get_user(val, &data32->value.integer[i])) |
| + if (get_user(val, &intp[i])) |
| return -EFAULT; |
| data->value.integer.value[i] = val; |
| } |
| @@ -250,8 +266,7 @@ static int copy_ctl_value_from_user(stru |
| printk(KERN_ERR "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); |
| return -EINVAL; |
| } |
| - if (copy_from_user(data->value.bytes.data, |
| - data32->value.data, size)) |
| + if (copy_from_user(data->value.bytes.data, valuep, size)) |
| return -EFAULT; |
| } |
| |
| @@ -261,7 +276,8 @@ static int copy_ctl_value_from_user(stru |
| } |
| |
| /* restore the value to 32bit */ |
| -static int copy_ctl_value_to_user(struct snd_ctl_elem_value32 __user *data32, |
| +static int copy_ctl_value_to_user(void __user *userdata, |
| + void __user *valuep, |
| struct snd_ctl_elem_value *data, |
| int type, int count) |
| { |
| @@ -270,22 +286,22 @@ static int copy_ctl_value_to_user(struct |
| if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
| type == SNDRV_CTL_ELEM_TYPE_INTEGER) { |
| for (i = 0; i < count; i++) { |
| + s32 __user *intp = valuep; |
| int val; |
| val = data->value.integer.value[i]; |
| - if (put_user(val, &data32->value.integer[i])) |
| + if (put_user(val, &intp[i])) |
| return -EFAULT; |
| } |
| } else { |
| size = get_elem_size(type, count); |
| - if (copy_to_user(data32->value.data, |
| - data->value.bytes.data, size)) |
| + if (copy_to_user(valuep, data->value.bytes.data, size)) |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| -static int snd_ctl_elem_read_user_compat(struct snd_card *card, |
| - struct snd_ctl_elem_value32 __user *data32) |
| +static int ctl_elem_read_user(struct snd_card *card, |
| + void __user *userdata, void __user *valuep) |
| { |
| struct snd_ctl_elem_value *data; |
| int err, type, count; |
| @@ -294,7 +310,9 @@ static int snd_ctl_elem_read_user_compat |
| if (data == NULL) |
| return -ENOMEM; |
| |
| - if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0) |
| + err = copy_ctl_value_from_user(card, data, userdata, valuep, |
| + &type, &count); |
| + if (err < 0) |
| goto error; |
| |
| snd_power_lock(card); |
| @@ -303,14 +321,15 @@ static int snd_ctl_elem_read_user_compat |
| err = snd_ctl_elem_read(card, data); |
| snd_power_unlock(card); |
| if (err >= 0) |
| - err = copy_ctl_value_to_user(data32, data, type, count); |
| + err = copy_ctl_value_to_user(userdata, valuep, data, |
| + type, count); |
| error: |
| kfree(data); |
| return err; |
| } |
| |
| -static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, |
| - struct snd_ctl_elem_value32 __user *data32) |
| +static int ctl_elem_write_user(struct snd_ctl_file *file, |
| + void __user *userdata, void __user *valuep) |
| { |
| struct snd_ctl_elem_value *data; |
| struct snd_card *card = file->card; |
| @@ -320,7 +339,9 @@ static int snd_ctl_elem_write_user_compa |
| if (data == NULL) |
| return -ENOMEM; |
| |
| - if ((err = copy_ctl_value_from_user(card, data, data32, &type, &count)) < 0) |
| + err = copy_ctl_value_from_user(card, data, userdata, valuep, |
| + &type, &count); |
| + if (err < 0) |
| goto error; |
| |
| snd_power_lock(card); |
| @@ -329,12 +350,39 @@ static int snd_ctl_elem_write_user_compa |
| err = snd_ctl_elem_write(card, file, data); |
| snd_power_unlock(card); |
| if (err >= 0) |
| - err = copy_ctl_value_to_user(data32, data, type, count); |
| + err = copy_ctl_value_to_user(userdata, valuep, data, |
| + type, count); |
| error: |
| kfree(data); |
| return err; |
| } |
| |
| +static int snd_ctl_elem_read_user_compat(struct snd_card *card, |
| + struct snd_ctl_elem_value32 __user *data32) |
| +{ |
| + return ctl_elem_read_user(card, data32, &data32->value); |
| +} |
| + |
| +static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, |
| + struct snd_ctl_elem_value32 __user *data32) |
| +{ |
| + return ctl_elem_write_user(file, data32, &data32->value); |
| +} |
| + |
| +#ifdef CONFIG_X86_X32 |
| +static int snd_ctl_elem_read_user_x32(struct snd_card *card, |
| + struct snd_ctl_elem_value_x32 __user *data32) |
| +{ |
| + return ctl_elem_read_user(card, data32, &data32->value); |
| +} |
| + |
| +static int snd_ctl_elem_write_user_x32(struct snd_ctl_file *file, |
| + struct snd_ctl_elem_value_x32 __user *data32) |
| +{ |
| + return ctl_elem_write_user(file, data32, &data32->value); |
| +} |
| +#endif /* CONFIG_X86_X32 */ |
| + |
| /* add or replace a user control */ |
| static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, |
| struct snd_ctl_elem_info32 __user *data32, |
| @@ -393,6 +441,10 @@ enum { |
| SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32), |
| SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32), |
| SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32), |
| +#ifdef CONFIG_X86_X32 |
| + SNDRV_CTL_IOCTL_ELEM_READ_X32 = _IOWR('U', 0x12, struct snd_ctl_elem_value_x32), |
| + SNDRV_CTL_IOCTL_ELEM_WRITE_X32 = _IOWR('U', 0x13, struct snd_ctl_elem_value_x32), |
| +#endif /* CONFIG_X86_X32 */ |
| }; |
| |
| static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) |
| @@ -431,6 +483,12 @@ static inline long snd_ctl_ioctl_compat( |
| return snd_ctl_elem_add_compat(ctl, argp, 0); |
| case SNDRV_CTL_IOCTL_ELEM_REPLACE32: |
| return snd_ctl_elem_add_compat(ctl, argp, 1); |
| +#ifdef CONFIG_X86_X32 |
| + case SNDRV_CTL_IOCTL_ELEM_READ_X32: |
| + return snd_ctl_elem_read_user_x32(ctl->card, argp); |
| + case SNDRV_CTL_IOCTL_ELEM_WRITE_X32: |
| + return snd_ctl_elem_write_user_x32(ctl, argp); |
| +#endif /* CONFIG_X86_X32 */ |
| } |
| |
| down_read(&snd_ioctl_rwsem); |