diff options
author | Thomas Pugliese <thomas.pugliese@gmail.com> | 2013-12-02 16:39:45 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-12-02 18:21:04 -0500 |
commit | 471e42ad148c05d091219096726d751684ebf918 (patch) | |
tree | 407fba0caeb3d553f86c8de029a9f34da39e03d1 | |
parent | 6161ae5f1f371e8ff52306d9a1893f5dec6f60a4 (diff) |
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>
-rw-r--r-- | drivers/usb/wusbcore/devconnect.c | 24 | ||||
-rw-r--r-- | drivers/usb/wusbcore/security.c | 98 | ||||
-rw-r--r-- | drivers/usb/wusbcore/wusbhc.h | 6 | ||||
-rw-r--r-- | include/linux/usb/wusb.h | 2 |
4 files changed, 62 insertions, 68 deletions
diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c index 5107ca999d4c..f14e7929ba22 100644 --- a/drivers/usb/wusbcore/devconnect.c +++ b/drivers/usb/wusbcore/devconnect.c | |||
@@ -97,18 +97,12 @@ static void wusbhc_devconnect_acked_work(struct work_struct *work); | |||
97 | 97 | ||
98 | static void wusb_dev_free(struct wusb_dev *wusb_dev) | 98 | static void wusb_dev_free(struct wusb_dev *wusb_dev) |
99 | { | 99 | { |
100 | if (wusb_dev) { | 100 | kfree(wusb_dev); |
101 | kfree(wusb_dev->set_gtk_req); | ||
102 | usb_free_urb(wusb_dev->set_gtk_urb); | ||
103 | kfree(wusb_dev); | ||
104 | } | ||
105 | } | 101 | } |
106 | 102 | ||
107 | static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) | 103 | static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) |
108 | { | 104 | { |
109 | struct wusb_dev *wusb_dev; | 105 | struct wusb_dev *wusb_dev; |
110 | struct urb *urb; | ||
111 | struct usb_ctrlrequest *req; | ||
112 | 106 | ||
113 | wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); | 107 | wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); |
114 | if (wusb_dev == NULL) | 108 | if (wusb_dev == NULL) |
@@ -118,22 +112,6 @@ static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) | |||
118 | 112 | ||
119 | INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); | 113 | INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); |
120 | 114 | ||
121 | urb = usb_alloc_urb(0, GFP_KERNEL); | ||
122 | if (urb == NULL) | ||
123 | goto err; | ||
124 | wusb_dev->set_gtk_urb = urb; | ||
125 | |||
126 | req = kmalloc(sizeof(*req), GFP_KERNEL); | ||
127 | if (req == NULL) | ||
128 | goto err; | ||
129 | wusb_dev->set_gtk_req = req; | ||
130 | |||
131 | req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; | ||
132 | req->bRequest = USB_REQ_SET_DESCRIPTOR; | ||
133 | req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index); | ||
134 | req->wIndex = 0; | ||
135 | req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength); | ||
136 | |||
137 | return wusb_dev; | 115 | return wusb_dev; |
138 | err: | 116 | err: |
139 | wusb_dev_free(wusb_dev); | 117 | wusb_dev_free(wusb_dev); |
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 | } |
diff --git a/drivers/usb/wusbcore/wusbhc.h b/drivers/usb/wusbcore/wusbhc.h index 711b1952b114..6bd3b819a6b5 100644 --- a/drivers/usb/wusbcore/wusbhc.h +++ b/drivers/usb/wusbcore/wusbhc.h | |||
@@ -97,6 +97,7 @@ struct wusb_dev { | |||
97 | struct kref refcnt; | 97 | struct kref refcnt; |
98 | struct wusbhc *wusbhc; | 98 | struct wusbhc *wusbhc; |
99 | struct list_head cack_node; /* Connect-Ack list */ | 99 | struct list_head cack_node; /* Connect-Ack list */ |
100 | struct list_head rekey_node; /* GTK rekey list */ | ||
100 | u8 port_idx; | 101 | u8 port_idx; |
101 | u8 addr; | 102 | u8 addr; |
102 | u8 beacon_type:4; | 103 | u8 beacon_type:4; |
@@ -107,8 +108,6 @@ struct wusb_dev { | |||
107 | struct usb_wireless_cap_descriptor *wusb_cap_descr; | 108 | struct usb_wireless_cap_descriptor *wusb_cap_descr; |
108 | struct uwb_mas_bm availability; | 109 | struct uwb_mas_bm availability; |
109 | struct work_struct devconnect_acked_work; | 110 | struct work_struct devconnect_acked_work; |
110 | struct urb *set_gtk_urb; | ||
111 | struct usb_ctrlrequest *set_gtk_req; | ||
112 | struct usb_device *usb_dev; | 111 | struct usb_device *usb_dev; |
113 | }; | 112 | }; |
114 | 113 | ||
@@ -296,8 +295,7 @@ struct wusbhc { | |||
296 | } __attribute__((packed)) gtk; | 295 | } __attribute__((packed)) gtk; |
297 | u8 gtk_index; | 296 | u8 gtk_index; |
298 | u32 gtk_tkid; | 297 | u32 gtk_tkid; |
299 | struct work_struct gtk_rekey_done_work; | 298 | struct work_struct gtk_rekey_work; |
300 | int pending_set_gtks; | ||
301 | 299 | ||
302 | struct usb_encryption_descriptor *ccm1_etd; | 300 | struct usb_encryption_descriptor *ccm1_etd; |
303 | }; | 301 | }; |
diff --git a/include/linux/usb/wusb.h b/include/linux/usb/wusb.h index 0c4d4ca370ec..eeb28329fa3c 100644 --- a/include/linux/usb/wusb.h +++ b/include/linux/usb/wusb.h | |||
@@ -271,6 +271,8 @@ static inline u8 wusb_key_index(int index, int type, int originator) | |||
271 | #define WUSB_KEY_INDEX_TYPE_GTK 2 | 271 | #define WUSB_KEY_INDEX_TYPE_GTK 2 |
272 | #define WUSB_KEY_INDEX_ORIGINATOR_HOST 0 | 272 | #define WUSB_KEY_INDEX_ORIGINATOR_HOST 0 |
273 | #define WUSB_KEY_INDEX_ORIGINATOR_DEVICE 1 | 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 | /* A CCM Nonce, defined in WUSB1.0[6.4.1] */ | 277 | /* A CCM Nonce, defined in WUSB1.0[6.4.1] */ |
276 | struct aes_ccm_nonce { | 278 | struct aes_ccm_nonce { |