diff options
| author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2013-02-18 05:26:11 -0500 |
|---|---|---|
| committer | Jiri Kosina <jkosina@suse.cz> | 2013-02-18 05:28:16 -0500 |
| commit | befde0226a595d1a7854c0cbf32904b8279c4fd0 (patch) | |
| tree | 4a32e96b1216a309e405cd0546bad541baf540fe | |
| parent | c284979affcc6870a9a6545fc4b1adb3816dfcbf (diff) | |
HID: uhid: make creating devices work on 64/32 systems
Unfortunately UHID interface, as it was introduced, is broken with 32 bit
userspace running on 64 bit kernels as it uses a pointer in its userspace
facing API.
Fix it by checking if we are executing compat task and munge the request
appropriately.
Reported-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
| -rw-r--r-- | drivers/hid/uhid.c | 95 |
1 files changed, 92 insertions, 3 deletions
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index 714cd8cc9579..fc307e0422af 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c | |||
| @@ -11,6 +11,7 @@ | |||
| 11 | */ | 11 | */ |
| 12 | 12 | ||
| 13 | #include <linux/atomic.h> | 13 | #include <linux/atomic.h> |
| 14 | #include <linux/compat.h> | ||
| 14 | #include <linux/device.h> | 15 | #include <linux/device.h> |
| 15 | #include <linux/fs.h> | 16 | #include <linux/fs.h> |
| 16 | #include <linux/hid.h> | 17 | #include <linux/hid.h> |
| @@ -276,6 +277,94 @@ static struct hid_ll_driver uhid_hid_driver = { | |||
| 276 | .parse = uhid_hid_parse, | 277 | .parse = uhid_hid_parse, |
| 277 | }; | 278 | }; |
| 278 | 279 | ||
| 280 | #ifdef CONFIG_COMPAT | ||
| 281 | |||
| 282 | /* Apparently we haven't stepped on these rakes enough times yet. */ | ||
| 283 | struct uhid_create_req_compat { | ||
| 284 | __u8 name[128]; | ||
| 285 | __u8 phys[64]; | ||
| 286 | __u8 uniq[64]; | ||
| 287 | |||
| 288 | compat_uptr_t rd_data; | ||
| 289 | __u16 rd_size; | ||
| 290 | |||
| 291 | __u16 bus; | ||
| 292 | __u32 vendor; | ||
| 293 | __u32 product; | ||
| 294 | __u32 version; | ||
| 295 | __u32 country; | ||
| 296 | } __attribute__((__packed__)); | ||
| 297 | |||
| 298 | static int uhid_event_from_user(const char __user *buffer, size_t len, | ||
| 299 | struct uhid_event *event) | ||
| 300 | { | ||
| 301 | if (is_compat_task()) { | ||
| 302 | u32 type; | ||
| 303 | |||
| 304 | if (get_user(type, buffer)) | ||
| 305 | return -EFAULT; | ||
| 306 | |||
| 307 | if (type == UHID_CREATE) { | ||
| 308 | /* | ||
| 309 | * This is our messed up request with compat pointer. | ||
| 310 | * It is largish (more than 256 bytes) so we better | ||
| 311 | * allocate it from the heap. | ||
| 312 | */ | ||
| 313 | struct uhid_create_req_compat *compat; | ||
| 314 | |||
| 315 | compat = kmalloc(sizeof(*compat), GFP_KERNEL); | ||
| 316 | if (!compat) | ||
| 317 | return -ENOMEM; | ||
| 318 | |||
| 319 | buffer += sizeof(type); | ||
| 320 | len -= sizeof(type); | ||
| 321 | if (copy_from_user(compat, buffer, | ||
| 322 | min(len, sizeof(*compat)))) { | ||
| 323 | kfree(compat); | ||
| 324 | return -EFAULT; | ||
| 325 | } | ||
| 326 | |||
| 327 | /* Shuffle the data over to proper structure */ | ||
| 328 | event->type = type; | ||
| 329 | |||
| 330 | memcpy(event->u.create.name, compat->name, | ||
| 331 | sizeof(compat->name)); | ||
| 332 | memcpy(event->u.create.phys, compat->phys, | ||
| 333 | sizeof(compat->phys)); | ||
| 334 | memcpy(event->u.create.uniq, compat->uniq, | ||
| 335 | sizeof(compat->uniq)); | ||
| 336 | |||
| 337 | event->u.create.rd_data = compat_ptr(compat->rd_data); | ||
| 338 | event->u.create.rd_size = compat->rd_size; | ||
| 339 | |||
| 340 | event->u.create.bus = compat->bus; | ||
| 341 | event->u.create.vendor = compat->vendor; | ||
| 342 | event->u.create.product = compat->product; | ||
| 343 | event->u.create.version = compat->version; | ||
| 344 | event->u.create.country = compat->country; | ||
| 345 | |||
| 346 | kfree(compat); | ||
| 347 | return 0; | ||
| 348 | } | ||
| 349 | /* All others can be copied directly */ | ||
| 350 | } | ||
| 351 | |||
| 352 | if (copy_from_user(event, buffer, min(len, sizeof(*event)))) | ||
| 353 | return -EFAULT; | ||
| 354 | |||
| 355 | return 0; | ||
| 356 | } | ||
| 357 | #else | ||
| 358 | static int uhid_event_from_user(const char __user *buffer, size_t len, | ||
| 359 | struct uhid_event *event) | ||
| 360 | { | ||
| 361 | if (copy_from_user(event, buffer, min(len, sizeof(*event)))) | ||
| 362 | return -EFAULT; | ||
| 363 | |||
| 364 | return 0; | ||
| 365 | } | ||
| 366 | #endif | ||
| 367 | |||
| 279 | static int uhid_dev_create(struct uhid_device *uhid, | 368 | static int uhid_dev_create(struct uhid_device *uhid, |
| 280 | const struct uhid_event *ev) | 369 | const struct uhid_event *ev) |
| 281 | { | 370 | { |
| @@ -498,10 +587,10 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer, | |||
| 498 | 587 | ||
| 499 | memset(&uhid->input_buf, 0, sizeof(uhid->input_buf)); | 588 | memset(&uhid->input_buf, 0, sizeof(uhid->input_buf)); |
| 500 | len = min(count, sizeof(uhid->input_buf)); | 589 | len = min(count, sizeof(uhid->input_buf)); |
| 501 | if (copy_from_user(&uhid->input_buf, buffer, len)) { | 590 | |
| 502 | ret = -EFAULT; | 591 | ret = uhid_event_from_user(buffer, len, &uhid->input_buf); |
| 592 | if (ret) | ||
| 503 | goto unlock; | 593 | goto unlock; |
| 504 | } | ||
| 505 | 594 | ||
| 506 | switch (uhid->input_buf.type) { | 595 | switch (uhid->input_buf.type) { |
| 507 | case UHID_CREATE: | 596 | case UHID_CREATE: |
