HID: wacom: Fix Use-After-Free in wacom_intuos_pad

wacom_intuos_pad() accesses wacom->shared->touch_input locklessly
inside the interrupt handler context.  If the Touch sibling device
is disconnected, wacom_remove_shared_data() clears 'touch_input'
outside any lock, creating a Time-of-Check to Time-of-Use (TOCTOU)
race condition where a preempted reader in interrupt context
dereferences the freed pointer, leading to a Use-After-Free.

Resolve this by introducing RCU protection for the touch_input
pointer:

 - Annotate 'touch_input' in wacom_shared struct with __rcu
 - Wrap all lockless readers in wacom_wac.c with guard(rcu)() and
   rcu_dereference() using a unified wacom_report_touch_mute()
   helper
 - Update writers in wacom_sys.c using rcu_assign_pointer()
 - Call synchronize_rcu() in wacom_remove_shared_data() to ensure
   all active RCU readers have finished before the input device is
   freed

Also wrap wacom_set_shared_values() and touch/pen assignments in
wacom_add_shared_data() inside the wacom_udev_list_lock to serialize
concurrent probe assignments, and verify that 'shared->touch == hdev'
before setting touch_input to prevent concurrent sibling probe state
desynchronization.

Cc: stable@vger.kernel.org
Fixes: 961794a00e ("Input: wacom - add reporting of SW_MUTE_DEVICE events")
Signed-off-by: Lee Jones <lee@kernel.org>
Acked-by: Ping Cheng <ping.cheng@wacom.com>
Tested-by: Ping Cheng <ping.cheng@wacom.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
This commit is contained in:
Lee Jones 2026-09-09 11:12:25 +00:00 committed by Jiri Kosina
parent ba16e5ffba
commit ef32877368
3 changed files with 35 additions and 26 deletions

View File

@ -875,10 +875,16 @@ static void wacom_remove_shared_data(void *res)
data = container_of(wacom_wac->shared, struct wacom_hdev_data,
shared);
if (wacom_wac->shared->touch == wacom->hdev)
wacom_wac->shared->touch = NULL;
else if (wacom_wac->shared->pen == wacom->hdev)
wacom_wac->shared->pen = NULL;
scoped_guard(mutex, &wacom_udev_list_lock) {
if (wacom_wac->shared->touch == wacom->hdev) {
wacom_wac->shared->touch = NULL;
rcu_assign_pointer(wacom_wac->shared->touch_input, NULL);
} else if (wacom_wac->shared->pen == wacom->hdev) {
wacom_wac->shared->pen = NULL;
}
}
synchronize_rcu();
kref_put(&data->kref, wacom_release_shared_data);
wacom_wac->shared = NULL;
@ -915,6 +921,7 @@ static int wacom_add_shared_data(struct hid_device *hdev)
mutex_unlock(&wacom_udev_list_lock);
retval = devm_add_action_or_reset(&hdev->dev, wacom_remove_shared_data, wacom);
return retval;
}
@ -2354,9 +2361,11 @@ static void wacom_set_shared_values(struct wacom_wac *wacom_wac)
}
if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH) {
wacom_wac->shared->type = wacom_wac->features.type;
wacom_wac->shared->touch_input = wacom_wac->touch_input;
wacom_wac->shared->touch = wacom->hdev;
if (wacom_wac->shared->touch == wacom->hdev || !wacom_wac->shared->touch) {
wacom_wac->shared->type = wacom_wac->features.type;
wacom_wac->shared->touch = wacom->hdev;
rcu_assign_pointer(wacom_wac->shared->touch_input, wacom_wac->touch_input);
}
} else if (wacom_wac->features.device_type & WACOM_DEVICETYPE_PEN) {
/* Pairs with smp_load_acquire() in wacom_sibling_pending() */
smp_store_release(&wacom_wac->shared->pen, wacom->hdev);

View File

@ -510,6 +510,18 @@ static void wacom_intuos_schedule_prox_event(struct wacom_wac *wacom_wac)
}
}
static void wacom_report_touch_mute(struct wacom_wac *wacom_wac, bool mute)
{
struct input_dev *touch_input;
guard(rcu)();
touch_input = rcu_dereference(wacom_wac->shared->touch_input);
if (touch_input) {
input_report_switch(touch_input, SW_MUTE_DEVICE, mute);
input_sync(touch_input);
}
}
static int wacom_intuos_pad(struct wacom_wac *wacom)
{
struct wacom_features *features = &wacom->features;
@ -650,12 +662,8 @@ static int wacom_intuos_pad(struct wacom_wac *wacom)
input_report_key(input, KEY_CONTROLPANEL, menu);
input_report_key(input, KEY_INFO, info);
if (wacom->shared && wacom->shared->touch_input) {
input_report_switch(wacom->shared->touch_input,
SW_MUTE_DEVICE,
!wacom->shared->is_touch_on);
input_sync(wacom->shared->touch_input);
}
if (wacom->shared)
wacom_report_touch_mute(wacom, !wacom->shared->is_touch_on);
input_report_abs(input, ABS_RX, strip1);
input_report_abs(input, ABS_RY, strip2);
@ -2168,7 +2176,7 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
*/
if ((equivalent_usage == WACOM_HID_WD_MUTE_DEVICE) ||
(equivalent_usage == WACOM_HID_WD_TOUCHONOFF)) {
if (wacom_wac->shared->touch_input) {
if (wacom_wac->shared) {
bool *is_touch_on = &wacom_wac->shared->is_touch_on;
if (equivalent_usage == WACOM_HID_WD_MUTE_DEVICE && value)
@ -2176,9 +2184,7 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
else if (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)
*is_touch_on = value;
input_report_switch(wacom_wac->shared->touch_input,
SW_MUTE_DEVICE, !(*is_touch_on));
input_sync(wacom_wac->shared->touch_input);
wacom_report_touch_mute(wacom_wac, !(*is_touch_on));
}
return;
}
@ -3396,11 +3402,8 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
if ((wacom->shared->type == INTUOSHT ||
wacom->shared->type == INTUOSHT2) &&
wacom->shared->touch_input &&
wacom->shared->touch_max) {
input_report_switch(wacom->shared->touch_input,
SW_MUTE_DEVICE, data[5] & 0x40);
input_sync(wacom->shared->touch_input);
wacom_report_touch_mute(wacom, data[5] & 0x40);
}
pid = get_unaligned_be16(&data[6]);
@ -3435,11 +3438,8 @@ static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len)
if ((features->type == INTUOSHT ||
features->type == INTUOSHT2) &&
wacom_wac->shared->touch_input &&
features->touch_max) {
input_report_switch(wacom_wac->shared->touch_input,
SW_MUTE_DEVICE, data[8] & 0x40);
input_sync(wacom_wac->shared->touch_input);
wacom_report_touch_mute(wacom_wac, data[8] & 0x40);
}
if (data[9] & 0x02) { /* wireless module is attached */

View File

@ -285,7 +285,7 @@ struct wacom_shared {
/* for wireless device to access USB interfaces */
unsigned touch_max;
int type;
struct input_dev *touch_input;
struct input_dev __rcu *touch_input;
struct hid_device *pen;
struct hid_device *touch;
bool has_mute_touch_switch;