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 @@  #include <linux/export.h>  #include "wusbhc.h" -static void wusbhc_set_gtk_callback(struct urb *urb); -static void wusbhc_gtk_rekey_done_work(struct work_struct *work); +static void wusbhc_gtk_rekey_work(struct work_struct *work);  int wusbhc_sec_create(struct wusbhc *wusbhc)  {  	wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data);  	wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY;  	wusbhc->gtk.descr.bReserved = 0; +	wusbhc->gtk_index = 0; -	wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, -					   WUSB_KEY_INDEX_ORIGINATOR_HOST); - -	INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); +	INIT_WORK(&wusbhc->gtk_rekey_work, wusbhc_gtk_rekey_work);  	return 0;  } @@ -113,7 +110,7 @@ int wusbhc_sec_start(struct wusbhc *wusbhc)  	wusbhc_generate_gtk(wusbhc);  	result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, -				 &wusbhc->gtk.descr.bKeyData, key_size); +				&wusbhc->gtk.descr.bKeyData, key_size);  	if (result < 0)  		dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n",  			result); @@ -129,7 +126,7 @@ int wusbhc_sec_start(struct wusbhc *wusbhc)   */  void wusbhc_sec_stop(struct wusbhc *wusbhc)  { -	cancel_work_sync(&wusbhc->gtk_rekey_done_work); +	cancel_work_sync(&wusbhc->gtk_rekey_work);  } @@ -185,12 +182,14 @@ static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value)  static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)  {  	struct usb_device *usb_dev = wusb_dev->usb_dev; +	u8 key_index = wusb_key_index(wusbhc->gtk_index, +		WUSB_KEY_INDEX_TYPE_GTK, WUSB_KEY_INDEX_ORIGINATOR_HOST);  	return usb_control_msg(  		usb_dev, usb_sndctrlpipe(usb_dev, 0),  		USB_REQ_SET_DESCRIPTOR,  		USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, -		USB_DT_KEY << 8 | wusbhc->gtk_index, 0, +		USB_DT_KEY << 8 | key_index, 0,  		&wusbhc->gtk.descr, wusbhc->gtk.descr.bLength,  		1000);  } @@ -520,24 +519,55 @@ error_kzalloc:   * Once all connected and authenticated devices have received the new   * GTK, switch the host to using it.   */ -static void wusbhc_gtk_rekey_done_work(struct work_struct *work) +static void wusbhc_gtk_rekey_work(struct work_struct *work)  { -	struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); +	struct wusbhc *wusbhc = container_of(work, +					struct wusbhc, gtk_rekey_work);  	size_t key_size = sizeof(wusbhc->gtk.data); +	int port_idx; +	struct wusb_dev *wusb_dev, *wusb_dev_next; +	LIST_HEAD(rekey_list);  	mutex_lock(&wusbhc->mutex); +	/* generate the new key */ +	wusbhc_generate_gtk(wusbhc); +	/* roll the gtk index. */ +	wusbhc->gtk_index = (wusbhc->gtk_index + 1) % (WUSB_KEY_INDEX_MAX + 1); +	/* +	 * Save all connected devices on a list while holding wusbhc->mutex and +	 * take a reference to each one.  Then submit the set key request to +	 * them after releasing the lock in order to avoid a deadlock. +	 */ +	for (port_idx = 0; port_idx < wusbhc->ports_max; port_idx++) { +		wusb_dev = wusbhc->port[port_idx].wusb_dev; +		if (!wusb_dev || !wusb_dev->usb_dev +			|| !wusb_dev->usb_dev->authenticated) +			continue; -	if (--wusbhc->pending_set_gtks == 0) -		wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); - +		wusb_dev_get(wusb_dev); +		list_add_tail(&wusb_dev->rekey_node, &rekey_list); +	}  	mutex_unlock(&wusbhc->mutex); -} -static void wusbhc_set_gtk_callback(struct urb *urb) -{ -	struct wusbhc *wusbhc = urb->context; +	/* Submit the rekey requests without holding wusbhc->mutex. */ +	list_for_each_entry_safe(wusb_dev, wusb_dev_next, &rekey_list, +		rekey_node) { +		list_del_init(&wusb_dev->rekey_node); +		dev_dbg(&wusb_dev->usb_dev->dev, "%s: rekey device at port %d\n", +			__func__, wusb_dev->port_idx); + +		if (wusb_dev_set_gtk(wusbhc, wusb_dev) < 0) { +			dev_err(&wusb_dev->usb_dev->dev, "%s: rekey device at port %d failed\n", +				__func__, wusb_dev->port_idx); +		} +		wusb_dev_put(wusb_dev); +	} -	queue_work(wusbd, &wusbhc->gtk_rekey_done_work); +	/* Switch the host controller to use the new GTK. */ +	mutex_lock(&wusbhc->mutex); +	wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, +		&wusbhc->gtk.descr.bKeyData, key_size); +	mutex_unlock(&wusbhc->mutex);  }  /** @@ -553,26 +583,12 @@ static void wusbhc_set_gtk_callback(struct urb *urb)   */  void wusbhc_gtk_rekey(struct wusbhc *wusbhc)  { -	static const size_t key_size = sizeof(wusbhc->gtk.data); -	int p; - -	wusbhc_generate_gtk(wusbhc); - -	for (p = 0; p < wusbhc->ports_max; p++) { -		struct wusb_dev *wusb_dev; - -		wusb_dev = wusbhc->port[p].wusb_dev; -		if (!wusb_dev || !wusb_dev->usb_dev || !wusb_dev->usb_dev->authenticated) -			continue; - -		usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, -				     usb_sndctrlpipe(wusb_dev->usb_dev, 0), -				     (void *)wusb_dev->set_gtk_req, -				     &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, -				     wusbhc_set_gtk_callback, wusbhc); -		if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) -			wusbhc->pending_set_gtks++; -	} -	if (wusbhc->pending_set_gtks == 0) -		wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); +	/* +	 * We need to submit a URB to the downstream WUSB devices in order to +	 * change the group key.  This can't be done while holding the +	 * wusbhc->mutex since that is also taken in the urb_enqueue routine +	 * and will cause a deadlock.  Instead, queue a work item to do +	 * it when the lock is not held +	 */ +	queue_work(wusbd, &wusbhc->gtk_rekey_work);  } |