Re: [RFC] [PATCH] HID: Fix race condition between driver core and ll-driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Jul 14, 2011 at 10:48 AM, David Herrmann
<dh.herrmann@xxxxxxxxxxxxxx> wrote:
> On Wed, Jul 13, 2011 at 4:24 PM, David Herrmann
> <dh.herrmann@xxxxxxxxxxxxxx> wrote:
>> 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: David Herrmann <dh.herrmann@xxxxxxxxxxxxxx>
>> ---
>>  drivers/hid/hid-core.c |   41 ++++++++++++++++++++++++++++++++++-------
>>  include/linux/hid.h    |    2 ++
>>  2 files changed, 36 insertions(+), 7 deletions(-)
>>
>> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
>> index 2a268fc..59b1a5b 100644
>> --- a/drivers/hid/hid-core.c
>> +++ b/drivers/hid/hid-core.c
>> @@ -29,6 +29,7 @@
>>  #include <linux/wait.h>
>>  #include <linux/vmalloc.h>
>>  #include <linux/sched.h>
>> +#include <linux/semaphore.h>
>>
>>  #include <linux/hid.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
>>        unsigned int i;
>>        int ret;
>
> This must be "int ret = 0;".
>
>>
>> -       if (!hid || !hid->driver)
>> +       if (!hid)
>>                return -ENODEV;
>> +
>> +       if (down_trylock(&hid->driver_lock))
>> +               return -EBUSY;
>> +
>> +       if (!hid->driver) {
>> +               ret = -ENODEV;
>> +               goto unlock;
>> +       }
>>        report_enum = hid->report_enum + type;
>>        hdrv = hid->driver;
>>
>>        if (!size) {
>>                dbg_hid("empty report\n");
>> -               return -1;
>> +               ret = -1;
>> +               goto unlock;
>>        }
>>
>>        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
>>  nomem:
>>        report = hid_get_report(report_enum, data);
>>
>> -       if (!report)
>> -               return -1;
>> +       if (!report) {
>> +               ret = -1;
>> +               goto unlock;
>> +       }
>>
>>        if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) {
>>                ret = hdrv->raw_event(hid, report, data, size);
>> -               if (ret != 0)
>> -                       return ret < 0 ? ret : 0;
>> +               if (ret != 0) {
>> +                       ret = ret < 0 ? ret : 0;
>> +                       goto unlock;
>> +               }
>>        }
>>
>>        hid_report_raw_event(hid, type, data, size, interrupt);
>>
>> +unlock:
>> +       up(&hid->driver_lock);
>>        return 0;
>
> And this must be "return ret;" of course.
>
>>
>> Regards
>> David
>>
>

Although this race condition seems to be quite obvious, I have lots of
trouble to trigger it. So I don't know whether you actually care to
fix it, but just to keep it up: *ping*

Regards
David
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux