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

usb: wusbcore: fix deadlock in wusbhc_gtk_rekey

When multiple wireless USB devices are connected and one of the devices
disconnects, the host will distribute a new group key to the remaining
devicese using wusbhc_gtk_rekey. wusbhc_gtk_rekey takes the
wusbhc->mutex and holds it while it submits a URB to set the new key.
This causes a deadlock in wa_urb_enqueue when it calls a device lookup
helper function that takes the same lock.

This patch changes wusbhc_gtk_rekey to submit a work item to set the GTK
so that the URB is submitted without holding wusbhc->mutex.

Signed-off-by: Thomas Pugliese <thomas.pugliese@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Thomas Pugliese and committed by
Greg Kroah-Hartman
471e42ad 6161ae5f

+62 -68
+1 -23
drivers/usb/wusbcore/devconnect.c
··· 97 97 98 98 static void wusb_dev_free(struct wusb_dev *wusb_dev) 99 99 { 100 - if (wusb_dev) { 101 - kfree(wusb_dev->set_gtk_req); 102 - usb_free_urb(wusb_dev->set_gtk_urb); 103 - kfree(wusb_dev); 104 - } 100 + kfree(wusb_dev); 105 101 } 106 102 107 103 static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) 108 104 { 109 105 struct wusb_dev *wusb_dev; 110 - struct urb *urb; 111 - struct usb_ctrlrequest *req; 112 106 113 107 wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); 114 108 if (wusb_dev == NULL) ··· 111 117 wusb_dev->wusbhc = wusbhc; 112 118 113 119 INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); 114 - 115 - urb = usb_alloc_urb(0, GFP_KERNEL); 116 - if (urb == NULL) 117 - goto err; 118 - wusb_dev->set_gtk_urb = urb; 119 - 120 - req = kmalloc(sizeof(*req), GFP_KERNEL); 121 - if (req == NULL) 122 - goto err; 123 - wusb_dev->set_gtk_req = req; 124 - 125 - req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; 126 - req->bRequest = USB_REQ_SET_DESCRIPTOR; 127 - req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index); 128 - req->wIndex = 0; 129 - req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength); 130 120 131 121 return wusb_dev; 132 122 err:
+57 -41
drivers/usb/wusbcore/security.c
··· 29 29 #include <linux/export.h> 30 30 #include "wusbhc.h" 31 31 32 - static void wusbhc_set_gtk_callback(struct urb *urb); 33 - static void wusbhc_gtk_rekey_done_work(struct work_struct *work); 32 + static void wusbhc_gtk_rekey_work(struct work_struct *work); 34 33 35 34 int wusbhc_sec_create(struct wusbhc *wusbhc) 36 35 { 37 36 wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); 38 37 wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; 39 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, 42 - WUSB_KEY_INDEX_ORIGINATOR_HOST); 43 - 44 - INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); 41 + INIT_WORK(&wusbhc->gtk_rekey_work, wusbhc_gtk_rekey_work); 45 42 46 43 return 0; 47 44 } ··· 110 113 wusbhc_generate_gtk(wusbhc); 111 114 112 115 result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, 113 - &wusbhc->gtk.descr.bKeyData, key_size); 116 + &wusbhc->gtk.descr.bKeyData, key_size); 114 117 if (result < 0) 115 118 dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", 116 119 result); ··· 126 129 */ 127 130 void wusbhc_sec_stop(struct wusbhc *wusbhc) 128 131 { 129 - cancel_work_sync(&wusbhc->gtk_rekey_done_work); 132 + cancel_work_sync(&wusbhc->gtk_rekey_work); 130 133 } 131 134 132 135 ··· 182 185 static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) 183 186 { 184 187 struct usb_device *usb_dev = wusb_dev->usb_dev; 188 + u8 key_index = wusb_key_index(wusbhc->gtk_index, 189 + WUSB_KEY_INDEX_TYPE_GTK, WUSB_KEY_INDEX_ORIGINATOR_HOST); 185 190 186 191 return usb_control_msg( 187 192 usb_dev, usb_sndctrlpipe(usb_dev, 0), 188 193 USB_REQ_SET_DESCRIPTOR, 189 194 USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, 190 - USB_DT_KEY << 8 | wusbhc->gtk_index, 0, 195 + USB_DT_KEY << 8 | key_index, 0, 191 196 &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, 192 197 1000); 193 198 } ··· 519 520 * Once all connected and authenticated devices have received the new 520 521 * GTK, switch the host to using it. 521 522 */ 522 - static void wusbhc_gtk_rekey_done_work(struct work_struct *work) 523 + static void wusbhc_gtk_rekey_work(struct work_struct *work) 523 524 { 524 - struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); 525 + struct wusbhc *wusbhc = container_of(work, 526 + struct wusbhc, gtk_rekey_work); 525 527 size_t key_size = sizeof(wusbhc->gtk.data); 528 + int port_idx; 529 + struct wusb_dev *wusb_dev, *wusb_dev_next; 530 + LIST_HEAD(rekey_list); 526 531 527 532 mutex_lock(&wusbhc->mutex); 533 + /* generate the new key */ 534 + wusbhc_generate_gtk(wusbhc); 535 + /* roll the gtk index. */ 536 + wusbhc->gtk_index = (wusbhc->gtk_index + 1) % (WUSB_KEY_INDEX_MAX + 1); 537 + /* 538 + * Save all connected devices on a list while holding wusbhc->mutex and 539 + * take a reference to each one. Then submit the set key request to 540 + * them after releasing the lock in order to avoid a deadlock. 541 + */ 542 + for (port_idx = 0; port_idx < wusbhc->ports_max; port_idx++) { 543 + wusb_dev = wusbhc->port[port_idx].wusb_dev; 544 + if (!wusb_dev || !wusb_dev->usb_dev 545 + || !wusb_dev->usb_dev->authenticated) 546 + continue; 528 547 529 - if (--wusbhc->pending_set_gtks == 0) 530 - wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); 531 - 548 + wusb_dev_get(wusb_dev); 549 + list_add_tail(&wusb_dev->rekey_node, &rekey_list); 550 + } 532 551 mutex_unlock(&wusbhc->mutex); 533 - } 534 552 535 - static void wusbhc_set_gtk_callback(struct urb *urb) 536 - { 537 - struct wusbhc *wusbhc = urb->context; 553 + /* Submit the rekey requests without holding wusbhc->mutex. */ 554 + list_for_each_entry_safe(wusb_dev, wusb_dev_next, &rekey_list, 555 + rekey_node) { 556 + list_del_init(&wusb_dev->rekey_node); 557 + dev_dbg(&wusb_dev->usb_dev->dev, "%s: rekey device at port %d\n", 558 + __func__, wusb_dev->port_idx); 538 559 539 - queue_work(wusbd, &wusbhc->gtk_rekey_done_work); 560 + if (wusb_dev_set_gtk(wusbhc, wusb_dev) < 0) { 561 + dev_err(&wusb_dev->usb_dev->dev, "%s: rekey device at port %d failed\n", 562 + __func__, wusb_dev->port_idx); 563 + } 564 + wusb_dev_put(wusb_dev); 565 + } 566 + 567 + /* Switch the host controller to use the new GTK. */ 568 + mutex_lock(&wusbhc->mutex); 569 + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, 570 + &wusbhc->gtk.descr.bKeyData, key_size); 571 + mutex_unlock(&wusbhc->mutex); 540 572 } 541 573 542 574 /** ··· 583 553 */ 584 554 void wusbhc_gtk_rekey(struct wusbhc *wusbhc) 585 555 { 586 - static const size_t key_size = sizeof(wusbhc->gtk.data); 587 - int p; 588 - 589 - wusbhc_generate_gtk(wusbhc); 590 - 591 - for (p = 0; p < wusbhc->ports_max; p++) { 592 - struct wusb_dev *wusb_dev; 593 - 594 - wusb_dev = wusbhc->port[p].wusb_dev; 595 - if (!wusb_dev || !wusb_dev->usb_dev || !wusb_dev->usb_dev->authenticated) 596 - continue; 597 - 598 - usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, 599 - usb_sndctrlpipe(wusb_dev->usb_dev, 0), 600 - (void *)wusb_dev->set_gtk_req, 601 - &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, 602 - wusbhc_set_gtk_callback, wusbhc); 603 - if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) 604 - wusbhc->pending_set_gtks++; 605 - } 606 - if (wusbhc->pending_set_gtks == 0) 607 - wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); 556 + /* 557 + * We need to submit a URB to the downstream WUSB devices in order to 558 + * change the group key. This can't be done while holding the 559 + * wusbhc->mutex since that is also taken in the urb_enqueue routine 560 + * and will cause a deadlock. Instead, queue a work item to do 561 + * it when the lock is not held 562 + */ 563 + queue_work(wusbd, &wusbhc->gtk_rekey_work); 608 564 }
+2 -4
drivers/usb/wusbcore/wusbhc.h
··· 97 97 struct kref refcnt; 98 98 struct wusbhc *wusbhc; 99 99 struct list_head cack_node; /* Connect-Ack list */ 100 + struct list_head rekey_node; /* GTK rekey list */ 100 101 u8 port_idx; 101 102 u8 addr; 102 103 u8 beacon_type:4; ··· 108 107 struct usb_wireless_cap_descriptor *wusb_cap_descr; 109 108 struct uwb_mas_bm availability; 110 109 struct work_struct devconnect_acked_work; 111 - struct urb *set_gtk_urb; 112 - struct usb_ctrlrequest *set_gtk_req; 113 110 struct usb_device *usb_dev; 114 111 }; 115 112 ··· 295 296 } __attribute__((packed)) gtk; 296 297 u8 gtk_index; 297 298 u32 gtk_tkid; 298 - struct work_struct gtk_rekey_done_work; 299 - int pending_set_gtks; 299 + struct work_struct gtk_rekey_work; 300 300 301 301 struct usb_encryption_descriptor *ccm1_etd; 302 302 };
+2
include/linux/usb/wusb.h
··· 271 271 #define WUSB_KEY_INDEX_TYPE_GTK 2 272 272 #define WUSB_KEY_INDEX_ORIGINATOR_HOST 0 273 273 #define WUSB_KEY_INDEX_ORIGINATOR_DEVICE 1 274 + /* bits 0-3 used for the key index. */ 275 + #define WUSB_KEY_INDEX_MAX 15 274 276 275 277 /* A CCM Nonce, defined in WUSB1.0[6.4.1] */ 276 278 struct aes_ccm_nonce {