]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: multitouch: fix out-of-bounds bit access on mt_io_flags
authorTrung Nguyen <trungnh@cystack.net>
Wed, 1 Jul 2026 17:13:19 +0000 (00:13 +0700)
committerBenjamin Tissoires <bentiss@kernel.org>
Wed, 1 Jul 2026 18:36:12 +0000 (20:36 +0200)
mt_io_flags is a single unsigned long, but mt_process_slot(),
mt_release_pending_palms() and mt_release_contacts() use it as a
per-slot bitmap indexed by the slot number. That slot number is only
bounded by td->maxcontacts, which is taken from the device's
ContactCountMaximum feature report and can be up to 255, not by
BITS_PER_LONG.

As a result, a multitouch device that advertises a large contact count
makes set_bit()/clear_bit() operate past the mt_io_flags word and
corrupt the adjacent members of struct mt_device. The sticky-fingers
release timer is the easiest way to reach this. mt_release_contacts()
runs

for (i = 0; i < mt->num_slots; i++)
clear_bit(i, &td->mt_io_flags);

with num_slots == maxcontacts. For maxcontacts around 250 the loop
clears the bits that overlap td->applications.next, zeroing that list
head, and the list_for_each_entry() that immediately follows then
dereferences NULL. The kernel panics from timer (softirq) context. On a
KASAN build this shows up as a general protection fault in
mt_release_contacts() with a null-ptr-deref at offset 0x58, which is
offsetof(struct mt_application, num_received).

The state is reachable from an untrusted USB or Bluetooth HID
multitouch device; no local privileges are required.

Store the per-slot active state in a separately allocated bitmap sized
for maxcontacts, the same pattern already used for pending_palm_slots,
and keep only MT_IO_FLAGS_RUNNING in mt_io_flags. The two
"mt_io_flags & MT_IO_SLOTS_MASK" arming checks become
bitmap_empty(td->active_slots, td->maxcontacts).

Move MT_IO_FLAGS_RUNNING back to bit 0. It was bumped to bit 32 by the
same commit to leave the low byte for the slot bits; with the slot bits
gone it fits in bit 0 again, which also keeps it within the unsigned
long on 32-bit.

Fixes: 46f781e0d151 ("HID: multitouch: fix sticky fingers")
Cc: stable@vger.kernel.org
Signed-off-by: Trung Nguyen <trungnh@cystack.net>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
drivers/hid/hid-multitouch.c

index 0495152091e3b537e2bf3ffa15573fc72f67a8d9..edb37b4c867e4bd5f04a92e49241faa82e6d7413 100644 (file)
@@ -31,6 +31,7 @@
  * [1] https://gitlab.freedesktop.org/libevdev/hid-tools
  */
 
+#include <linux/bitmap.h>
 #include <linux/bits.h>
 #include <linux/device.h>
 #include <linux/hid.h>
@@ -97,8 +98,7 @@ enum report_mode {
        TOUCHPAD_REPORT_ALL = TOUCHPAD_REPORT_BUTTONS | TOUCHPAD_REPORT_CONTACTS,
 };
 
-#define MT_IO_SLOTS_MASK               GENMASK(7, 0) /* reserve first 8 bits for slot tracking */
-#define MT_IO_FLAGS_RUNNING            32
+#define MT_IO_FLAGS_RUNNING            0
 
 static const bool mtrue = true;                /* default for true */
 static const bool mfalse;              /* default for false */
@@ -174,10 +174,9 @@ struct mt_device {
        struct timer_list release_timer;        /* to release sticky fingers */
        struct hid_haptic_device *haptic;       /* haptic related configuration */
        struct hid_device *hdev;        /* hid_device we're attached to */
-       unsigned long mt_io_flags;      /* mt flags (MT_IO_FLAGS_RUNNING)
-                                        * first 8 bits are reserved for keeping the slot
-                                        * states, this is fine because we only support up
-                                        * to 250 slots (MT_MAX_MAXCONTACT)
+       unsigned long mt_io_flags;      /* mt flags (MT_IO_FLAGS_RUNNING) */
+       unsigned long *active_slots;    /* bitmap of slots with an active
+                                        * contact, sized for maxcontacts
                                         */
        __u8 inputmode_value;   /* InputMode HID feature value */
        __u8 maxcontacts;
@@ -1036,7 +1035,7 @@ static void mt_release_pending_palms(struct mt_device *td,
 
        for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) {
                clear_bit(slotnum, app->pending_palm_slots);
-               clear_bit(slotnum, &td->mt_io_flags);
+               clear_bit(slotnum, td->active_slots);
 
                input_mt_slot(input, slotnum);
                input_mt_report_slot_inactive(input);
@@ -1247,9 +1246,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
                input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
                input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
 
-               set_bit(slotnum, &td->mt_io_flags);
+               set_bit(slotnum, td->active_slots);
        } else {
-               clear_bit(slotnum, &td->mt_io_flags);
+               clear_bit(slotnum, td->active_slots);
        }
 
        return 0;
@@ -1384,7 +1383,7 @@ static void mt_touch_report(struct hid_device *hid,
         * defect.
         */
        if (app->quirks & MT_QUIRK_STICKY_FINGERS) {
-               if (td->mt_io_flags & MT_IO_SLOTS_MASK)
+               if (!bitmap_empty(td->active_slots, td->maxcontacts))
                        mod_timer(&td->release_timer,
                                  jiffies + msecs_to_jiffies(100));
                else
@@ -1443,6 +1442,15 @@ static int mt_touch_input_configured(struct hid_device *hdev,
        if (td->is_pressurepad)
                __set_bit(INPUT_PROP_PRESSUREPAD, input->propbit);
 
+       if (!td->active_slots) {
+               td->active_slots = devm_kcalloc(&td->hdev->dev,
+                                               BITS_TO_LONGS(td->maxcontacts),
+                                               sizeof(long),
+                                               GFP_KERNEL);
+               if (!td->active_slots)
+                       return -ENOMEM;
+       }
+
        app->pending_palm_slots = devm_kcalloc(&hi->input->dev,
                                               BITS_TO_LONGS(td->maxcontacts),
                                               sizeof(long),
@@ -2062,7 +2070,7 @@ static void mt_release_contacts(struct hid_device *hid)
                        for (i = 0; i < mt->num_slots; i++) {
                                input_mt_slot(input_dev, i);
                                input_mt_report_slot_inactive(input_dev);
-                               clear_bit(i, &td->mt_io_flags);
+                               clear_bit(i, td->active_slots);
                        }
                        input_mt_sync_frame(input_dev);
                        input_sync(input_dev);
@@ -2085,7 +2093,7 @@ static void mt_expired_timeout(struct timer_list *t)
         */
        if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
                return;
-       if (td->mt_io_flags & MT_IO_SLOTS_MASK)
+       if (!bitmap_empty(td->active_slots, td->maxcontacts))
                mt_release_contacts(hdev);
        clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
 }