HID: wacom: Redesign shared sibling data lifecycle

The Wacom driver coordinates state between sibling interfaces of
the same physical device using a shared structure 'wacom_shared'
inside 'wacom_hdev_data'.  The driver kept a volatile representative
pointer 'data->dev' pointing to a sibling 'hid_device' for physical
path comparisons during sibling matching.

This pointer management is fragile.  When the representative device
is disconnected, wacom_remove_shared_data() failed to clear/update
'data->dev', leading to a Use-After-Free vulnerability when
subsequent sibling probes dereference the dangling 'data->dev'
pointer.

Resolve this issue by redesigning the sibling data lifecycle:

 - Eliminate the volatile 'data->dev' representative pointer
   completely
 - Redesign 'wacom_hdev_data' to store stable static copies of the
   required attributes upon first allocation: 'phys' path string,
   'vendor', 'product' IDs and the sibling's 'device_type'
 - Use these static attributes for stable sibling matching in
   wacom_are_sibling() and wacom_get_hdev_data()

This ensures sibling matching remains safe and stable even if
individual siblings are dynamically added or removed.

To secure the lifecycle against concurrent probe/disconnect races:

 - Switch kref_put() to kref_put_mutex() in
   wacom_remove_shared_data() to serialize refcount drops with list
   traversal and lookup
 - Modify wacom_release_shared_data() to assume the list lock is
   already held

Also, do not accumulate the 'device_type' capability flag during
subsequent sibling probes.  Keeping only the first probed sibling's
device_type exactly preserves the original sibling matching behavior
without introducing side effects.

Cc: stable@vger.kernel.org
Fixes: 4492efffff ("Input: wacom - share pen info with touch of the same ID")
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:27 +00:00 committed by Jiri Kosina
parent 3a523ecf9a
commit 480c9cb14a

View File

@ -757,27 +757,47 @@ static void wacom_retrieve_hid_descriptor(struct hid_device *hdev,
struct wacom_hdev_data {
struct list_head list;
struct kref kref;
struct hid_device *dev;
char phys[64];
__u32 vendor;
__u32 product;
__u32 device_type;
struct wacom_shared shared;
};
static bool wacom_compare_device_paths(struct hid_device *hdev_a,
const char *phys_b, char separator)
{
const char *p1 = strrchr(hdev_a->phys, separator);
const char *p2 = strrchr(phys_b, separator);
int n1, n2;
if (!p1 || !p2)
return false;
n1 = p1 - hdev_a->phys;
n2 = p2 - phys_b;
if (n1 != n2 || n1 <= 0 || n2 <= 0)
return false;
return !strncmp(hdev_a->phys, phys_b, n1);
}
static LIST_HEAD(wacom_udev_list);
static DEFINE_MUTEX(wacom_udev_list_lock);
static bool wacom_are_sibling(struct hid_device *hdev,
struct hid_device *sibling)
struct wacom_hdev_data *data)
{
struct wacom *wacom = hid_get_drvdata(hdev);
struct wacom_features *features = &wacom->wacom_wac.features;
struct wacom *sibling_wacom = hid_get_drvdata(sibling);
struct wacom_features *sibling_features = &sibling_wacom->wacom_wac.features;
__u32 oVid = features->oVid ? features->oVid : hdev->vendor;
__u32 oPid = features->oPid ? features->oPid : hdev->product;
/* The defined oVid/oPid must match that of the sibling */
if (features->oVid != HID_ANY_ID && sibling->vendor != oVid)
if (features->oVid != HID_ANY_ID && data->vendor != oVid)
return false;
if (features->oPid != HID_ANY_ID && sibling->product != oPid)
if (features->oPid != HID_ANY_ID && data->product != oPid)
return false;
/*
@ -785,11 +805,11 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* device path, while those with different VID/PID must share
* the same physical parent device path.
*/
if (hdev->vendor == sibling->vendor && hdev->product == sibling->product) {
if (!hid_compare_device_paths(hdev, sibling, '/'))
if (hdev->vendor == data->vendor && hdev->product == data->product) {
if (!wacom_compare_device_paths(hdev, data->phys, '/'))
return false;
} else {
if (!hid_compare_device_paths(hdev, sibling, '.'))
if (!wacom_compare_device_paths(hdev, data->phys, '.'))
return false;
}
@ -802,7 +822,7 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* devices.
*/
if ((features->device_type & WACOM_DEVICETYPE_DIRECT) &&
!(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
!(data->device_type & WACOM_DEVICETYPE_DIRECT))
return false;
/*
@ -810,17 +830,17 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* devices.
*/
if (!(features->device_type & WACOM_DEVICETYPE_DIRECT) &&
(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
(data->device_type & WACOM_DEVICETYPE_DIRECT))
return false;
/* Pen devices may only be siblings of touch devices */
if ((features->device_type & WACOM_DEVICETYPE_PEN) &&
!(sibling_features->device_type & WACOM_DEVICETYPE_TOUCH))
!(data->device_type & WACOM_DEVICETYPE_TOUCH))
return false;
/* Touch devices may only be siblings of pen devices */
if ((features->device_type & WACOM_DEVICETYPE_TOUCH) &&
!(sibling_features->device_type & WACOM_DEVICETYPE_PEN))
!(data->device_type & WACOM_DEVICETYPE_PEN))
return false;
/*
@ -836,7 +856,7 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
/* Try to find an already-probed interface from the same device */
list_for_each_entry(data, &wacom_udev_list, list) {
if (hid_compare_device_paths(hdev, data->dev, '/')) {
if (wacom_compare_device_paths(hdev, data->phys, '/')) {
kref_get(&data->kref);
return data;
}
@ -844,7 +864,7 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
/* Fallback to finding devices that appear to be "siblings" */
list_for_each_entry(data, &wacom_udev_list, list) {
if (wacom_are_sibling(hdev, data->dev)) {
if (wacom_are_sibling(hdev, data)) {
kref_get(&data->kref);
return data;
}
@ -858,18 +878,15 @@ static void wacom_release_shared_data(struct kref *kref)
struct wacom_hdev_data *data =
container_of(kref, struct wacom_hdev_data, kref);
mutex_lock(&wacom_udev_list_lock);
list_del(&data->list);
mutex_unlock(&wacom_udev_list_lock);
kfree(data);
}
static void wacom_remove_shared_data(void *res)
{
struct wacom *wacom = res;
struct wacom *res_wacom = res;
struct wacom_hdev_data *data;
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
struct wacom_wac *wacom_wac = &res_wacom->wacom_wac;
if (wacom_wac->shared) {
data = container_of(wacom_wac->shared, struct wacom_hdev_data,
@ -883,17 +900,19 @@ static void wacom_remove_shared_data(void *res)
rcu_dereference_protected(wacom_wac->shared->pen,
lockdep_is_held(&wacom_udev_list_lock));
if (touch == wacom->hdev) {
if (touch == res_wacom->hdev) {
rcu_assign_pointer(wacom_wac->shared->touch, NULL);
rcu_assign_pointer(wacom_wac->shared->touch_input, NULL);
} else if (pen == wacom->hdev) {
} else if (pen == res_wacom->hdev) {
rcu_assign_pointer(wacom_wac->shared->pen, NULL);
}
}
synchronize_rcu();
kref_put(&data->kref, wacom_release_shared_data);
if (kref_put_mutex(&data->kref, wacom_release_shared_data, &wacom_udev_list_lock))
mutex_unlock(&wacom_udev_list_lock);
wacom_wac->shared = NULL;
}
}
@ -916,7 +935,10 @@ static int wacom_add_shared_data(struct hid_device *hdev)
}
kref_init(&data->kref);
data->dev = hdev;
strscpy(data->phys, hdev->phys, sizeof(data->phys));
data->vendor = hdev->vendor;
data->product = hdev->product;
data->device_type = wacom_wac->features.device_type;
list_add_tail(&data->list, &wacom_udev_list);
}