diff options
Diffstat (limited to 'drivers/usb/wusbcore/security.c')
| -rw-r--r-- | drivers/usb/wusbcore/security.c | 98 |
1 files changed, 57 insertions, 41 deletions
diff --git a/drivers/usb/wusbcore/security.c b/drivers/usb/wusbcore/security.c index dd88441c8f78..4c40d0dbf53d 100644 --- a/drivers/usb/wusbcore/security.c +++ b/drivers/usb/wusbcore/security.c | |||
| @@ -29,19 +29,16 @@ | |||
| 29 | #include <linux/export.h> | 29 | #include <linux/export.h> |
| 30 | #include "wusbhc.h" | 30 | #include "wusbhc.h" |
| 31 | 31 | ||
| 32 | static void wusbhc_set_gtk_callback(struct urb *urb); | 32 | static void wusbhc_gtk_rekey_work(struct work_struct *work); |
| 33 | static void wusbhc_gtk_rekey_done_work(struct work_struct *work); | ||
| 34 | 33 | ||
| 35 | int wusbhc_sec_create(struct wusbhc *wusbhc) | 34 | int wusbhc_sec_create(struct wusbhc *wusbhc) |
| 36 | { | 35 | { |
| 37 | wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); | 36 | wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); |
| 38 | wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; | 37 | wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; |
| 39 | wusbhc->gtk.descr.bReserved = 0; | 38 | wusbhc->gtk.descr.bReserved = 0; |
| 39 | wusbhc->gtk_index = 0; | ||
| 40 | 40 | ||
| 41 | wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, | 41 | INIT_WORK(&wusbhc->gtk_rekey_work, wusbhc_gtk_rekey_work); |
| 42 | WUSB_KEY_INDEX_ORIGINATOR_HOST); | ||
| 43 | |||
| 44 | INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); | ||
| 45 | 42 | ||
| 46 | return 0; | 43 | return 0; |
| 47 | } | 44 | } |
| @@ -113,7 +110,7 @@ int wusbhc_sec_start(struct wusbhc *wusbhc) | |||
| 113 | wusbhc_generate_gtk(wusbhc); | 110 | wusbhc_generate_gtk(wusbhc); |
| 114 | 111 | ||
| 115 | result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, | 112 | result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, |
| 116 | &wusbhc->gtk.descr.bKeyData, key_size); | 113 | &wusbhc->gtk.descr.bKeyData, key_size); |
| 117 | if (result < 0) | 114 | if (result < 0) |
| 118 | dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", | 115 | dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", |
| 119 | result); | 116 | result); |
| @@ -129,7 +126,7 @@ int wusbhc_sec_start(struct wusbhc *wusbhc) | |||
| 129 | */ | 126 | */ |
| 130 | void wusbhc_sec_stop(struct wusbhc *wusbhc) | 127 | void wusbhc_sec_stop(struct wusbhc *wusbhc) |
| 131 | { | 128 | { |
| 132 | cancel_work_sync(&wusbhc->gtk_rekey_done_work); | 129 | cancel_work_sync(&wusbhc->gtk_rekey_work); |
| 133 | } | 130 | } |
| 134 | 131 | ||
| 135 | 132 | ||
| @@ -185,12 +182,14 @@ static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value) | |||
| 185 | static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) | 182 | static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) |
| 186 | { | 183 | { |
| 187 | struct usb_device *usb_dev = wusb_dev->usb_dev; | 184 | struct usb_device *usb_dev = wusb_dev->usb_dev; |
| 185 | u8 key_index = wusb_key_index(wusbhc->gtk_index, | ||
| 186 | WUSB_KEY_INDEX_TYPE_GTK, WUSB_KEY_INDEX_ORIGINATOR_HOST); | ||
| 188 | 187 | ||
| 189 | return usb_control_msg( | 188 | return usb_control_msg( |
| 190 | usb_dev, usb_sndctrlpipe(usb_dev, 0), | 189 | usb_dev, usb_sndctrlpipe(usb_dev, 0), |
| 191 | USB_REQ_SET_DESCRIPTOR, | 190 | USB_REQ_SET_DESCRIPTOR, |
| 192 | USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, | 191 | USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, |
| 193 | USB_DT_KEY << 8 | wusbhc->gtk_index, 0, | 192 | USB_DT_KEY << 8 | key_index, 0, |
| 194 | &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, | 193 | &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, |
| 195 | 1000); | 194 | 1000); |
| 196 | } | 195 | } |
| @@ -520,24 +519,55 @@ error_kzalloc: | |||
| 520 | * Once all connected and authenticated devices have received the new | 519 | * Once all connected and authenticated devices have received the new |
| 521 | * GTK, switch the host to using it. | 520 | * GTK, switch the host to using it. |
| 522 | */ | 521 | */ |
| 523 | static void wusbhc_gtk_rekey_done_work(struct work_struct *work) | 522 | static void wusbhc_gtk_rekey_work(struct work_struct *work) |
| 524 | { | 523 | { |
| 525 | struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); | 524 | struct wusbhc *wusbhc = container_of(work, |
| 525 | struct wusbhc, gtk_rekey_work); | ||
| 526 | size_t key_size = sizeof(wusbhc->gtk.data); | 526 | size_t key_size = sizeof(wusbhc->gtk.data); |
| 527 | int port_idx; | ||
| 528 | struct wusb_dev *wusb_dev, *wusb_dev_next; | ||
| 529 | LIST_HEAD(rekey_list); | ||
| 527 | 530 | ||
| 528 | mutex_lock(&wusbhc->mutex); | 531 | mutex_lock(&wusbhc->mutex); |
| 532 | /* generate the new key */ | ||
| 533 | wusbhc_generate_gtk(wusbhc); | ||
| 534 | /* roll the gtk index. */ | ||
| 535 | wusbhc->gtk_index = (wusbhc->gtk_index + 1) % (WUSB_KEY_INDEX_MAX + 1); | ||
| 536 | /* | ||
| 537 | * Save all connected devices on a list while holding wusbhc->mutex and | ||
| 538 | * take a reference to each one. Then submit the set key request to | ||
| 539 | * them after releasing the lock in order to avoid a deadlock. | ||
| 540 | */ | ||
| 541 | for (port_idx = 0; port_idx < wusbhc->ports_max; port_idx++) { | ||
| 542 | wusb_dev = wusbhc->port[port_idx].wusb_dev; | ||
| 543 | if (!wusb_dev || !wusb_dev->usb_dev | ||
| 544 | || !wusb_dev->usb_dev->authenticated) | ||
| 545 | continue; | ||
| 529 | 546 | ||
| 530 | if (--wusbhc->pending_set_gtks == 0) | 547 | wusb_dev_get(wusb_dev); |
| 531 | wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); | 548 | list_add_tail(&wusb_dev->rekey_node, &rekey_list); |
| 532 | 549 | } | |
| 533 | mutex_unlock(&wusbhc->mutex); | 550 | mutex_unlock(&wusbhc->mutex); |
| 534 | } | ||
| 535 | 551 | ||
| 536 | static void wusbhc_set_gtk_callback(struct urb *urb) | 552 | /* Submit the rekey requests without holding wusbhc->mutex. */ |
| 537 | { | 553 | list_for_each_entry_safe(wusb_dev, wusb_dev_next, &rekey_list, |
| 538 | struct wusbhc *wusbhc = urb->context; | 554 | rekey_node) { |
| 555 | list_del_init(&wusb_dev->rekey_node); | ||
| 556 | dev_dbg(&wusb_dev->usb_dev->dev, "%s: rekey device at port %d\n", | ||
| 557 | __func__, wusb_dev->port_idx); | ||
| 558 | |||
| 559 | if (wusb_dev_set_gtk(wusbhc, wusb_dev) < 0) { | ||
| 560 | dev_err(&wusb_dev->usb_dev->dev, "%s: rekey device at port %d failed\n", | ||
| 561 | __func__, wusb_dev->port_idx); | ||
| 562 | } | ||
| 563 | wusb_dev_put(wusb_dev); | ||
| 564 | } | ||
| 539 | 565 | ||
| 540 | queue_work(wusbd, &wusbhc->gtk_rekey_done_work); | 566 | /* Switch the host controller to use the new GTK. */ |
| 567 | mutex_lock(&wusbhc->mutex); | ||
| 568 | wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, | ||
| 569 | &wusbhc->gtk.descr.bKeyData, key_size); | ||
| 570 | mutex_unlock(&wusbhc->mutex); | ||
| 541 | } | 571 | } |
| 542 | 572 | ||
| 543 | /** | 573 | /** |
| @@ -553,26 +583,12 @@ static void wusbhc_set_gtk_callback(struct urb *urb) | |||
| 553 | */ | 583 | */ |
| 554 | void wusbhc_gtk_rekey(struct wusbhc *wusbhc) | 584 | void wusbhc_gtk_rekey(struct wusbhc *wusbhc) |
| 555 | { | 585 | { |
| 556 | static const size_t key_size = sizeof(wusbhc->gtk.data); | 586 | /* |
| 557 | int p; | 587 | * We need to submit a URB to the downstream WUSB devices in order to |
| 558 | 588 | * change the group key. This can't be done while holding the | |
| 559 | wusbhc_generate_gtk(wusbhc); | 589 | * wusbhc->mutex since that is also taken in the urb_enqueue routine |
| 560 | 590 | * and will cause a deadlock. Instead, queue a work item to do | |
| 561 | for (p = 0; p < wusbhc->ports_max; p++) { | 591 | * it when the lock is not held |
| 562 | struct wusb_dev *wusb_dev; | 592 | */ |
| 563 | 593 | queue_work(wusbd, &wusbhc->gtk_rekey_work); | |
| 564 | wusb_dev = wusbhc->port[p].wusb_dev; | ||
| 565 | if (!wusb_dev || !wusb_dev->usb_dev || !wusb_dev->usb_dev->authenticated) | ||
| 566 | continue; | ||
| 567 | |||
| 568 | usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, | ||
| 569 | usb_sndctrlpipe(wusb_dev->usb_dev, 0), | ||
| 570 | (void *)wusb_dev->set_gtk_req, | ||
| 571 | &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, | ||
| 572 | wusbhc_set_gtk_callback, wusbhc); | ||
| 573 | if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) | ||
| 574 | wusbhc->pending_set_gtks++; | ||
| 575 | } | ||
| 576 | if (wusbhc->pending_set_gtks == 0) | ||
| 577 | wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); | ||
| 578 | } | 594 | } |
