Commit 4ea54542 authored by David Herrmann's avatar David Herrmann Committed by Jiri Kosina

HID: Fix race condition between driver core and ll-driver

HID low level drivers register new devices with the HID core which then
adds the devices to the HID bus. The HID bus normally immediately probes
an appropriate driver which then handles HID input for this device.
The ll driver now uses the hid_input_report() function to report input
events for a specific device. However, if the HID bus unloads the driver
at the same time (for instance via a call to
 /sys/bus/hid/devices/<dev>/unbind) then the hdev->driver pointer may be
used by hid_input_report() and hid_device_remove() at the same time
which may cause hdev->driver to point to invalid memory.

This fix adds a semaphore to every hid device which protects
hdev->driver from asynchronous access. This semaphore is locked during
driver *_probe and *_remove and also inside hid_input_report(). The
*_probe and *_remove functions may sleep so the semaphore is good here,
however, hid_input_report() is in atomic context and hence only uses
down_trylock(). If it cannot acquire the lock it simply drops the input
package.

The low-level drivers report input events synchronously so
hid_input_report() should never be entered twice at the same time on the
same device. Hence, the lock should always be available. But if the
driver is currently probed/removed then the lock is not available and
dropping the package should be safe because this is what would have
happened if the package arrived some milliseconds earlier/later.

This also fixes another race condition while probing drivers:
First the *_probe function of the driver is called and only if that
succeeds, the related input device of hidinput is registered. If the low
level driver reports input events after the *_probe function returned
but before the input device is registered, then a NULL pointer
dereference will occur. (Equivalently on driver remove function).
This is not possible anymore, since the semaphore lock drops all
incoming packages until the driver/device is fully initialized.
Signed-off-by: default avatarDavid Herrmann <dh.herrmann@googlemail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 00b15628
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <linux/wait.h> #include <linux/wait.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/semaphore.h>
#include <linux/hid.h> #include <linux/hid.h>
#include <linux/hiddev.h> #include <linux/hiddev.h>
...@@ -1087,14 +1088,23 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i ...@@ -1087,14 +1088,23 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
unsigned int i; unsigned int i;
int ret; int ret;
if (!hid || !hid->driver) if (!hid)
return -ENODEV; return -ENODEV;
if (down_trylock(&hid->driver_lock))
return -EBUSY;
if (!hid->driver) {
ret = -ENODEV;
goto unlock;
}
report_enum = hid->report_enum + type; report_enum = hid->report_enum + type;
hdrv = hid->driver; hdrv = hid->driver;
if (!size) { if (!size) {
dbg_hid("empty report\n"); dbg_hid("empty report\n");
return -1; ret = -1;
goto unlock;
} }
buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC);
...@@ -1118,17 +1128,23 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i ...@@ -1118,17 +1128,23 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
nomem: nomem:
report = hid_get_report(report_enum, data); report = hid_get_report(report_enum, data);
if (!report) if (!report) {
return -1; ret = -1;
goto unlock;
}
if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) { if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) {
ret = hdrv->raw_event(hid, report, data, size); ret = hdrv->raw_event(hid, report, data, size);
if (ret != 0) if (ret != 0) {
return ret < 0 ? ret : 0; ret = ret < 0 ? ret : 0;
goto unlock;
}
} }
hid_report_raw_event(hid, type, data, size, interrupt); hid_report_raw_event(hid, type, data, size, interrupt);
unlock:
up(&hid->driver_lock);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(hid_input_report); EXPORT_SYMBOL_GPL(hid_input_report);
...@@ -1617,6 +1633,9 @@ static int hid_device_probe(struct device *dev) ...@@ -1617,6 +1633,9 @@ static int hid_device_probe(struct device *dev)
const struct hid_device_id *id; const struct hid_device_id *id;
int ret = 0; int ret = 0;
if (down_interruptible(&hdev->driver_lock))
return -EINTR;
if (!hdev->driver) { if (!hdev->driver) {
id = hid_match_device(hdev, hdrv); id = hid_match_device(hdev, hdrv);
if (id == NULL) if (id == NULL)
...@@ -1633,14 +1652,20 @@ static int hid_device_probe(struct device *dev) ...@@ -1633,14 +1652,20 @@ static int hid_device_probe(struct device *dev)
if (ret) if (ret)
hdev->driver = NULL; hdev->driver = NULL;
} }
up(&hdev->driver_lock);
return ret; return ret;
} }
static int hid_device_remove(struct device *dev) static int hid_device_remove(struct device *dev)
{ {
struct hid_device *hdev = container_of(dev, struct hid_device, dev); struct hid_device *hdev = container_of(dev, struct hid_device, dev);
struct hid_driver *hdrv = hdev->driver; struct hid_driver *hdrv;
if (down_interruptible(&hdev->driver_lock))
return -EINTR;
hdrv = hdev->driver;
if (hdrv) { if (hdrv) {
if (hdrv->remove) if (hdrv->remove)
hdrv->remove(hdev); hdrv->remove(hdev);
...@@ -1649,6 +1674,7 @@ static int hid_device_remove(struct device *dev) ...@@ -1649,6 +1674,7 @@ static int hid_device_remove(struct device *dev)
hdev->driver = NULL; hdev->driver = NULL;
} }
up(&hdev->driver_lock);
return 0; return 0;
} }
...@@ -1996,6 +2022,7 @@ struct hid_device *hid_allocate_device(void) ...@@ -1996,6 +2022,7 @@ struct hid_device *hid_allocate_device(void)
init_waitqueue_head(&hdev->debug_wait); init_waitqueue_head(&hdev->debug_wait);
INIT_LIST_HEAD(&hdev->debug_list); INIT_LIST_HEAD(&hdev->debug_list);
sema_init(&hdev->driver_lock, 1);
return hdev; return hdev;
err: err:
......
...@@ -71,6 +71,7 @@ ...@@ -71,6 +71,7 @@
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <linux/input.h> #include <linux/input.h>
#include <linux/semaphore.h>
/* /*
* We parse each description item into this structure. Short items data * We parse each description item into this structure. Short items data
...@@ -475,6 +476,7 @@ struct hid_device { /* device report descriptor */ ...@@ -475,6 +476,7 @@ struct hid_device { /* device report descriptor */
unsigned country; /* HID country */ unsigned country; /* HID country */
struct hid_report_enum report_enum[HID_REPORT_TYPES]; struct hid_report_enum report_enum[HID_REPORT_TYPES];
struct semaphore driver_lock; /* protects the current driver */
struct device dev; /* device */ struct device dev; /* device */
struct hid_driver *driver; struct hid_driver *driver;
struct hid_ll_driver *ll_driver; struct hid_ll_driver *ll_driver;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment