CVE-2017-8071: Improper Resource Shutdown or Release (CWE-404)
value: drivers/hid/hid-cp2112.c in the Linux kernel 4.9.x before 4.9.9 uses a spinlock without considering that sleeping is possible in a USB HID request callback, which allows local users to cause a denial of service (deadlock) via unspecified vectors.
CWE description: The product does not release or incorrectly releases a resource before it is made available for re-use.
CVSS v3 base score: 5.5
Published: 2017-04-23T05:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: drivers/hid/hid-cp2112.c
Commit date: 2017-01-31 12:59:32 +0100
Commit message: HID: cp2112: fix sleep-while-atomic

A recent commit fixing DMA-buffers on stack added a shared transfer
buffer protected by a spinlock. This is broken as the USB HID request
callbacks can sleep. Fix this up by replacing the spinlock with a mutex.

Fixes: 1ffb3c40ffb5 ("HID: cp2112: make transfer buffers DMA capable")
Cc: stable <stable@vger.kernel.org>	# 4.9
Signed-off-by: Johan Hovold <johan@kernel.org>
Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>

Diff: diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c
index f31a778b0851..3e0b6bad29f2 100644
--- a/drivers/hid/hid-cp2112.c
+++ b/drivers/hid/hid-cp2112.c
@@ -166,11 +166,11 @@ struct cp2112_device {
 	int xfer_status;
 	atomic_t read_avail;
 	atomic_t xfer_avail;
 	struct gpio_chip gc;
 	u8 *in_out_buffer;
-	spinlock_t lock;
+	struct mutex lock;
 
 	struct gpio_desc *desc[8];
 	bool gpio_poll;
 	struct delayed_work gpio_poll_worker;
 	unsigned long irq_mask;
@@ -184,14 +184,13 @@ MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask");
 static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
 {
 	struct cp2112_device *dev = gpiochip_get_data(chip);
 	struct hid_device *hdev = dev->hdev;
 	u8 *buf = dev->in_out_buffer;
-	unsigned long flags;
 	int ret;
 
-	spin_lock_irqsave(&dev->lock, flags);
+	mutex_lock(&dev->lock);
 
 	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
 				 CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
 				 HID_REQ_GET_REPORT);
 	if (ret != CP2112_GPIO_CONFIG_LENGTH) {
@@ -211,23 +210,22 @@ static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
 	}
 
 	ret = 0;
 
 exit:
-	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock);
 	return ret <= 0 ? ret : -EIO;
 }
 
 static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
 {
 	struct cp2112_device *dev = gpiochip_get_data(chip);
 	struct hid_device *hdev = dev->hdev;
 	u8 *buf = dev->in_out_buffer;
-	unsigned long flags;
 	int ret;
 
-	spin_lock_irqsave(&dev->lock, flags);
+	mutex_lock(&dev->lock);
 
 	buf[0] = CP2112_GPIO_SET;
 	buf[1] = value ? 0xff : 0;
 	buf[2] = 1 << offset;
 
@@ -235,22 +233,21 @@ static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
 				 CP2112_GPIO_SET_LENGTH, HID_FEATURE_REPORT,
 				 HID_REQ_SET_REPORT);
 	if (ret < 0)
 		hid_err(hdev, "error setting GPIO values: %d\n", ret);
 
-	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock);
 }
 
 static int cp2112_gpio_get_all(struct gpio_chip *chip)
 {
 	struct cp2112_device *dev = gpiochip_get_data(chip);
 	struct hid_device *hdev = dev->hdev;
 	u8 *buf = dev->in_out_buffer;
-	unsigned long flags;
 	int ret;
 
-	spin_lock_irqsave(&dev->lock, flags);
+	mutex_lock(&dev->lock);
 
 	ret = hid_hw_raw_request(hdev, CP2112_GPIO_GET, buf,
 				 CP2112_GPIO_GET_LENGTH, HID_FEATURE_REPORT,
 				 HID_REQ_GET_REPORT);
 	if (ret != CP2112_GPIO_GET_LENGTH) {
@@ -260,11 +257,11 @@ static int cp2112_gpio_get_all(struct gpio_chip *chip)
 	}
 
 	ret = buf[1];
 
 exit:
-	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock);
 
 	return ret;
 }
 
 static int cp2112_gpio_get(struct gpio_chip *chip, unsigned int offset)
@@ -282,14 +279,13 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
 					unsigned offset, int value)
 {
 	struct cp2112_device *dev = gpiochip_get_data(chip);
 	struct hid_device *hdev = dev->hdev;
 	u8 *buf = dev->in_out_buffer;
-	unsigned long flags;
 	int ret;
 
-	spin_lock_irqsave(&dev->lock, flags);
+	mutex_lock(&dev->lock);
 
 	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
 				 CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
 				 HID_REQ_GET_REPORT);
 	if (ret != CP2112_GPIO_CONFIG_LENGTH) {
@@ -306,22 +302,22 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
 	if (ret < 0) {
 		hid_err(hdev, "error setting GPIO config: %d\n", ret);
 		goto fail;
 	}
 
-	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock);
 
 	/*
 	 * Set gpio value when output direction is already set,
 	 * as specified in AN495, Rev. 0.2, cpt. 4.4
 	 */
 	cp2112_gpio_set(chip, offset, value);
 
 	return 0;
 
 fail:
-	spin_unlock_irqrestore(&dev->lock, flags);
+	mutex_unlock(&dev->lock);
 	return ret < 0 ? ret : -EIO;
 }
 
 static int cp2112_hid_get(struct hid_device *hdev, unsigned char report_number,
 			  u8 *data, size_t count, unsigned char report_type)
@@ -1233,11 +1229,11 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	dev->in_out_buffer = devm_kzalloc(&hdev->dev, CP2112_REPORT_MAX_LENGTH,
 					  GFP_KERNEL);
 	if (!dev->in_out_buffer)
 		return -ENOMEM;
 
-	spin_lock_init(&dev->lock);
+	mutex_init(&dev->lock);
 
 	ret = hid_parse(hdev);
 	if (ret) {
 		hid_err(hdev, "parse failed\n");
 		return ret;

Vulnerable code: 	spinlock_t lock;
	unsigned long flags;
	spin_lock_irqsave(&dev->lock, flags);
	spin_unlock_irqrestore(&dev->lock, flags);
	unsigned long flags;
	spin_lock_irqsave(&dev->lock, flags);
	spin_unlock_irqrestore(&dev->lock, flags);
	unsigned long flags;
	spin_lock_irqsave(&dev->lock, flags);
	spin_unlock_irqrestore(&dev->lock, flags);
	unsigned long flags;
	spin_lock_irqsave(&dev->lock, flags);
	spin_unlock_irqrestore(&dev->lock, flags);
	spin_unlock_irqrestore(&dev->lock, flags);
	spin_lock_init(&dev->lock);
Fixed code: 	struct mutex lock;
	mutex_lock(&dev->lock);
	mutex_unlock(&dev->lock);
	mutex_lock(&dev->lock);
	mutex_unlock(&dev->lock);
	mutex_lock(&dev->lock);
	mutex_unlock(&dev->lock);
	mutex_lock(&dev->lock);
	mutex_unlock(&dev->lock);
	mutex_unlock(&dev->lock);
	mutex_init(&dev->lock);