1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * compat ioctls for control API |
4 | * |
5 | * Copyright (c) by Takashi Iwai <tiwai@suse.de> |
6 | */ |
7 | |
8 | /* this file included from control.c */ |
9 | |
10 | #include <linux/compat.h> |
11 | #include <linux/slab.h> |
12 | |
13 | struct snd_ctl_elem_list32 { |
14 | u32 offset; |
15 | u32 space; |
16 | u32 used; |
17 | u32 count; |
18 | u32 pids; |
19 | unsigned char reserved[50]; |
20 | } /* don't set packed attribute here */; |
21 | |
22 | static int snd_ctl_elem_list_compat(struct snd_card *card, |
23 | struct snd_ctl_elem_list32 __user *data32) |
24 | { |
25 | struct snd_ctl_elem_list data = {}; |
26 | compat_caddr_t ptr; |
27 | int err; |
28 | |
29 | /* offset, space, used, count */ |
30 | if (copy_from_user(to: &data, from: data32, n: 4 * sizeof(u32))) |
31 | return -EFAULT; |
32 | /* pids */ |
33 | if (get_user(ptr, &data32->pids)) |
34 | return -EFAULT; |
35 | data.pids = compat_ptr(uptr: ptr); |
36 | err = snd_ctl_elem_list(card, list: &data); |
37 | if (err < 0) |
38 | return err; |
39 | /* copy the result */ |
40 | if (copy_to_user(to: data32, from: &data, n: 4 * sizeof(u32))) |
41 | return -EFAULT; |
42 | return 0; |
43 | } |
44 | |
45 | /* |
46 | * control element info |
47 | * it uses union, so the things are not easy.. |
48 | */ |
49 | |
50 | struct snd_ctl_elem_info32 { |
51 | struct snd_ctl_elem_id id; // the size of struct is same |
52 | s32 type; |
53 | u32 access; |
54 | u32 count; |
55 | s32 owner; |
56 | union { |
57 | struct { |
58 | s32 min; |
59 | s32 max; |
60 | s32 step; |
61 | } integer; |
62 | struct { |
63 | u64 min; |
64 | u64 max; |
65 | u64 step; |
66 | } integer64; |
67 | struct { |
68 | u32 items; |
69 | u32 item; |
70 | char name[64]; |
71 | u64 names_ptr; |
72 | u32 names_length; |
73 | } enumerated; |
74 | unsigned char reserved[128]; |
75 | } value; |
76 | unsigned char reserved[64]; |
77 | } __packed; |
78 | |
79 | static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl, |
80 | struct snd_ctl_elem_info32 __user *data32) |
81 | { |
82 | struct snd_ctl_elem_info *data __free(kfree) = NULL; |
83 | int err; |
84 | |
85 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
86 | if (! data) |
87 | return -ENOMEM; |
88 | |
89 | /* copy id */ |
90 | if (copy_from_user(to: &data->id, from: &data32->id, n: sizeof(data->id))) |
91 | return -EFAULT; |
92 | /* we need to copy the item index. |
93 | * hope this doesn't break anything.. |
94 | */ |
95 | if (get_user(data->value.enumerated.item, &data32->value.enumerated.item)) |
96 | return -EFAULT; |
97 | |
98 | err = snd_ctl_elem_info(ctl, info: data); |
99 | if (err < 0) |
100 | return err; |
101 | /* restore info to 32bit */ |
102 | /* id, type, access, count */ |
103 | if (copy_to_user(to: &data32->id, from: &data->id, n: sizeof(data->id)) || |
104 | copy_to_user(to: &data32->type, from: &data->type, n: 3 * sizeof(u32))) |
105 | return -EFAULT; |
106 | if (put_user(data->owner, &data32->owner)) |
107 | return -EFAULT; |
108 | switch (data->type) { |
109 | case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
110 | case SNDRV_CTL_ELEM_TYPE_INTEGER: |
111 | if (put_user(data->value.integer.min, &data32->value.integer.min) || |
112 | put_user(data->value.integer.max, &data32->value.integer.max) || |
113 | put_user(data->value.integer.step, &data32->value.integer.step)) |
114 | return -EFAULT; |
115 | break; |
116 | case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
117 | if (copy_to_user(to: &data32->value.integer64, |
118 | from: &data->value.integer64, |
119 | n: sizeof(data->value.integer64))) |
120 | return -EFAULT; |
121 | break; |
122 | case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
123 | if (copy_to_user(to: &data32->value.enumerated, |
124 | from: &data->value.enumerated, |
125 | n: sizeof(data->value.enumerated))) |
126 | return -EFAULT; |
127 | break; |
128 | default: |
129 | break; |
130 | } |
131 | return 0; |
132 | } |
133 | |
134 | /* read / write */ |
135 | struct snd_ctl_elem_value32 { |
136 | struct snd_ctl_elem_id id; |
137 | unsigned int indirect; /* bit-field causes misalignment */ |
138 | union { |
139 | s32 integer[128]; |
140 | unsigned char data[512]; |
141 | #ifndef CONFIG_X86_64 |
142 | s64 integer64[64]; |
143 | #endif |
144 | } value; |
145 | unsigned char reserved[128]; |
146 | }; |
147 | |
148 | #ifdef CONFIG_X86_X32_ABI |
149 | /* x32 has a different alignment for 64bit values from ia32 */ |
150 | struct snd_ctl_elem_value_x32 { |
151 | struct snd_ctl_elem_id id; |
152 | unsigned int indirect; /* bit-field causes misalignment */ |
153 | union { |
154 | s32 integer[128]; |
155 | unsigned char data[512]; |
156 | s64 integer64[64]; |
157 | } value; |
158 | unsigned char reserved[128]; |
159 | }; |
160 | #endif /* CONFIG_X86_X32_ABI */ |
161 | |
162 | /* get the value type and count of the control */ |
163 | static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, |
164 | int *countp) |
165 | { |
166 | struct snd_kcontrol *kctl; |
167 | struct snd_ctl_elem_info *info __free(kfree) = NULL; |
168 | int err; |
169 | |
170 | guard(rwsem_read)(T: &card->controls_rwsem); |
171 | kctl = snd_ctl_find_id_locked(card, id); |
172 | if (!kctl) |
173 | return -ENOENT; |
174 | info = kzalloc(size: sizeof(*info), GFP_KERNEL); |
175 | if (info == NULL) |
176 | return -ENOMEM; |
177 | info->id = *id; |
178 | err = snd_power_ref_and_wait(card); |
179 | if (!err) |
180 | err = kctl->info(kctl, info); |
181 | snd_power_unref(card); |
182 | if (err >= 0) { |
183 | err = info->type; |
184 | *countp = info->count; |
185 | } |
186 | return err; |
187 | } |
188 | |
189 | static int get_elem_size(snd_ctl_elem_type_t type, int count) |
190 | { |
191 | switch (type) { |
192 | case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
193 | return sizeof(s64) * count; |
194 | case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
195 | return sizeof(int) * count; |
196 | case SNDRV_CTL_ELEM_TYPE_BYTES: |
197 | return 512; |
198 | case SNDRV_CTL_ELEM_TYPE_IEC958: |
199 | return sizeof(struct snd_aes_iec958); |
200 | default: |
201 | return -1; |
202 | } |
203 | } |
204 | |
205 | static int copy_ctl_value_from_user(struct snd_card *card, |
206 | struct snd_ctl_elem_value *data, |
207 | void __user *userdata, |
208 | void __user *valuep, |
209 | int *typep, int *countp) |
210 | { |
211 | struct snd_ctl_elem_value32 __user *data32 = userdata; |
212 | int i, type, size; |
213 | int count; |
214 | unsigned int indirect; |
215 | |
216 | if (copy_from_user(to: &data->id, from: &data32->id, n: sizeof(data->id))) |
217 | return -EFAULT; |
218 | if (get_user(indirect, &data32->indirect)) |
219 | return -EFAULT; |
220 | if (indirect) |
221 | return -EINVAL; |
222 | type = get_ctl_type(card, id: &data->id, countp: &count); |
223 | if (type < 0) |
224 | return type; |
225 | |
226 | if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
227 | type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { |
228 | for (i = 0; i < count; i++) { |
229 | s32 __user *intp = valuep; |
230 | int val; |
231 | if (get_user(val, &intp[i])) |
232 | return -EFAULT; |
233 | data->value.integer.value[i] = val; |
234 | } |
235 | } else { |
236 | size = get_elem_size(type: (__force snd_ctl_elem_type_t)type, count); |
237 | if (size < 0) { |
238 | dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n" , type); |
239 | return -EINVAL; |
240 | } |
241 | if (copy_from_user(to: data->value.bytes.data, from: valuep, n: size)) |
242 | return -EFAULT; |
243 | } |
244 | |
245 | *typep = type; |
246 | *countp = count; |
247 | return 0; |
248 | } |
249 | |
250 | /* restore the value to 32bit */ |
251 | static int copy_ctl_value_to_user(void __user *userdata, |
252 | void __user *valuep, |
253 | struct snd_ctl_elem_value *data, |
254 | int type, int count) |
255 | { |
256 | struct snd_ctl_elem_value32 __user *data32 = userdata; |
257 | int i, size; |
258 | |
259 | if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
260 | type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { |
261 | for (i = 0; i < count; i++) { |
262 | s32 __user *intp = valuep; |
263 | int val; |
264 | val = data->value.integer.value[i]; |
265 | if (put_user(val, &intp[i])) |
266 | return -EFAULT; |
267 | } |
268 | } else { |
269 | size = get_elem_size(type: (__force snd_ctl_elem_type_t)type, count); |
270 | if (copy_to_user(to: valuep, from: data->value.bytes.data, n: size)) |
271 | return -EFAULT; |
272 | } |
273 | if (copy_to_user(to: &data32->id, from: &data->id, n: sizeof(data32->id))) |
274 | return -EFAULT; |
275 | return 0; |
276 | } |
277 | |
278 | static int ctl_elem_read_user(struct snd_card *card, |
279 | void __user *userdata, void __user *valuep) |
280 | { |
281 | struct snd_ctl_elem_value *data __free(kfree) = NULL; |
282 | int err, type, count; |
283 | |
284 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
285 | if (data == NULL) |
286 | return -ENOMEM; |
287 | |
288 | err = copy_ctl_value_from_user(card, data, userdata, valuep, |
289 | typep: &type, countp: &count); |
290 | if (err < 0) |
291 | return err; |
292 | |
293 | err = snd_ctl_elem_read(card, control: data); |
294 | if (err < 0) |
295 | return err; |
296 | return copy_ctl_value_to_user(userdata, valuep, data, type, count); |
297 | } |
298 | |
299 | static int ctl_elem_write_user(struct snd_ctl_file *file, |
300 | void __user *userdata, void __user *valuep) |
301 | { |
302 | struct snd_ctl_elem_value *data __free(kfree) = NULL; |
303 | struct snd_card *card = file->card; |
304 | int err, type, count; |
305 | |
306 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
307 | if (data == NULL) |
308 | return -ENOMEM; |
309 | |
310 | err = copy_ctl_value_from_user(card, data, userdata, valuep, |
311 | typep: &type, countp: &count); |
312 | if (err < 0) |
313 | return err; |
314 | |
315 | err = snd_ctl_elem_write(card, file, control: data); |
316 | if (err < 0) |
317 | return err; |
318 | return copy_ctl_value_to_user(userdata, valuep, data, type, count); |
319 | } |
320 | |
321 | static int snd_ctl_elem_read_user_compat(struct snd_card *card, |
322 | struct snd_ctl_elem_value32 __user *data32) |
323 | { |
324 | return ctl_elem_read_user(card, userdata: data32, valuep: &data32->value); |
325 | } |
326 | |
327 | static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, |
328 | struct snd_ctl_elem_value32 __user *data32) |
329 | { |
330 | return ctl_elem_write_user(file, userdata: data32, valuep: &data32->value); |
331 | } |
332 | |
333 | #ifdef CONFIG_X86_X32_ABI |
334 | static int snd_ctl_elem_read_user_x32(struct snd_card *card, |
335 | struct snd_ctl_elem_value_x32 __user *data32) |
336 | { |
337 | return ctl_elem_read_user(card, userdata: data32, valuep: &data32->value); |
338 | } |
339 | |
340 | static int snd_ctl_elem_write_user_x32(struct snd_ctl_file *file, |
341 | struct snd_ctl_elem_value_x32 __user *data32) |
342 | { |
343 | return ctl_elem_write_user(file, userdata: data32, valuep: &data32->value); |
344 | } |
345 | #endif /* CONFIG_X86_X32_ABI */ |
346 | |
347 | /* add or replace a user control */ |
348 | static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, |
349 | struct snd_ctl_elem_info32 __user *data32, |
350 | int replace) |
351 | { |
352 | struct snd_ctl_elem_info *data __free(kfree) = NULL; |
353 | |
354 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
355 | if (! data) |
356 | return -ENOMEM; |
357 | |
358 | /* id, type, access, count */ \ |
359 | if (copy_from_user(to: &data->id, from: &data32->id, n: sizeof(data->id)) || |
360 | copy_from_user(to: &data->type, from: &data32->type, n: 3 * sizeof(u32))) |
361 | return -EFAULT; |
362 | if (get_user(data->owner, &data32->owner)) |
363 | return -EFAULT; |
364 | switch (data->type) { |
365 | case SNDRV_CTL_ELEM_TYPE_BOOLEAN: |
366 | case SNDRV_CTL_ELEM_TYPE_INTEGER: |
367 | if (get_user(data->value.integer.min, &data32->value.integer.min) || |
368 | get_user(data->value.integer.max, &data32->value.integer.max) || |
369 | get_user(data->value.integer.step, &data32->value.integer.step)) |
370 | return -EFAULT; |
371 | break; |
372 | case SNDRV_CTL_ELEM_TYPE_INTEGER64: |
373 | if (copy_from_user(to: &data->value.integer64, |
374 | from: &data32->value.integer64, |
375 | n: sizeof(data->value.integer64))) |
376 | return -EFAULT; |
377 | break; |
378 | case SNDRV_CTL_ELEM_TYPE_ENUMERATED: |
379 | if (copy_from_user(to: &data->value.enumerated, |
380 | from: &data32->value.enumerated, |
381 | n: sizeof(data->value.enumerated))) |
382 | return -EFAULT; |
383 | data->value.enumerated.names_ptr = |
384 | (uintptr_t)compat_ptr(uptr: data->value.enumerated.names_ptr); |
385 | break; |
386 | default: |
387 | break; |
388 | } |
389 | return snd_ctl_elem_add(file, info: data, replace); |
390 | } |
391 | |
392 | enum { |
393 | SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), |
394 | SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32), |
395 | SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32), |
396 | SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32), |
397 | SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32), |
398 | SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32), |
399 | #ifdef CONFIG_X86_X32_ABI |
400 | SNDRV_CTL_IOCTL_ELEM_READ_X32 = _IOWR('U', 0x12, struct snd_ctl_elem_value_x32), |
401 | SNDRV_CTL_IOCTL_ELEM_WRITE_X32 = _IOWR('U', 0x13, struct snd_ctl_elem_value_x32), |
402 | #endif /* CONFIG_X86_X32_ABI */ |
403 | }; |
404 | |
405 | static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) |
406 | { |
407 | struct snd_ctl_file *ctl; |
408 | struct snd_kctl_ioctl *p; |
409 | void __user *argp = compat_ptr(uptr: arg); |
410 | int err; |
411 | |
412 | ctl = file->private_data; |
413 | if (snd_BUG_ON(!ctl || !ctl->card)) |
414 | return -ENXIO; |
415 | |
416 | switch (cmd) { |
417 | case SNDRV_CTL_IOCTL_PVERSION: |
418 | case SNDRV_CTL_IOCTL_CARD_INFO: |
419 | case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: |
420 | case SNDRV_CTL_IOCTL_POWER: |
421 | case SNDRV_CTL_IOCTL_POWER_STATE: |
422 | case SNDRV_CTL_IOCTL_ELEM_LOCK: |
423 | case SNDRV_CTL_IOCTL_ELEM_UNLOCK: |
424 | case SNDRV_CTL_IOCTL_ELEM_REMOVE: |
425 | case SNDRV_CTL_IOCTL_TLV_READ: |
426 | case SNDRV_CTL_IOCTL_TLV_WRITE: |
427 | case SNDRV_CTL_IOCTL_TLV_COMMAND: |
428 | return snd_ctl_ioctl(file, cmd, arg: (unsigned long)argp); |
429 | case SNDRV_CTL_IOCTL_ELEM_LIST32: |
430 | return snd_ctl_elem_list_compat(card: ctl->card, data32: argp); |
431 | case SNDRV_CTL_IOCTL_ELEM_INFO32: |
432 | return snd_ctl_elem_info_compat(ctl, data32: argp); |
433 | case SNDRV_CTL_IOCTL_ELEM_READ32: |
434 | return snd_ctl_elem_read_user_compat(card: ctl->card, data32: argp); |
435 | case SNDRV_CTL_IOCTL_ELEM_WRITE32: |
436 | return snd_ctl_elem_write_user_compat(file: ctl, data32: argp); |
437 | case SNDRV_CTL_IOCTL_ELEM_ADD32: |
438 | return snd_ctl_elem_add_compat(file: ctl, data32: argp, replace: 0); |
439 | case SNDRV_CTL_IOCTL_ELEM_REPLACE32: |
440 | return snd_ctl_elem_add_compat(file: ctl, data32: argp, replace: 1); |
441 | #ifdef CONFIG_X86_X32_ABI |
442 | case SNDRV_CTL_IOCTL_ELEM_READ_X32: |
443 | return snd_ctl_elem_read_user_x32(card: ctl->card, data32: argp); |
444 | case SNDRV_CTL_IOCTL_ELEM_WRITE_X32: |
445 | return snd_ctl_elem_write_user_x32(file: ctl, data32: argp); |
446 | #endif /* CONFIG_X86_X32_ABI */ |
447 | } |
448 | |
449 | guard(rwsem_read)(T: &snd_ioctl_rwsem); |
450 | list_for_each_entry(p, &snd_control_compat_ioctls, list) { |
451 | if (p->fioctl) { |
452 | err = p->fioctl(ctl->card, ctl, cmd, arg); |
453 | if (err != -ENOIOCTLCMD) |
454 | return err; |
455 | } |
456 | } |
457 | return -ENOIOCTLCMD; |
458 | } |
459 | |