Merge tag 'hid-for-linus-2026070801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid

Pull HID fixes from Jiri Kosina:

 - OOB, UAF, NULL-deref fixes in core and picolcd, logitech, letsketch,
   appleir and multitouch drivers (Georgiy Osokin, HyeongJun An, Lee
   Jones, Manish Khadka, Maoyi Xie and Trung Nguyen)

 - fix for integer wraparound (and corresponding regression selftest) in
   hid-bpf (Yiyang Chen)

* tag 'hid-for-linus-2026070801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid:
  selftests/hid: multitouch: test a large ContactCountMaximum
  HID: multitouch: fix out-of-bounds bit access on mt_io_flags
  selftests/hid: Cover hid_bpf_get_data() size overflow
  selftests/hid: Load only requested struct_ops maps
  HID: bpf: Fix hid_bpf_get_data() range check
  HID: lg-g15: cancel pending work on remove to fix a use-after-free
  HID: logitech-dj: Fix maxfield check in DJ short report validation
  HID: core: Fix OOB read in hid_get_report for numbered reports
  HID: picolcd: prevent NULL pointer dereference in picolcd_send_and_wait()
  HID: appleir: fix UAF on pending key_up_timer in remove()
  HID: letsketch: fix UAF on inrange_timer at driver unbind
This commit is contained in:
Linus Torvalds
2026-07-08 08:43:44 -07:00
12 changed files with 283 additions and 37 deletions

View File

@@ -17,6 +17,7 @@
#include <linux/kfifo.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include "hid_bpf_dispatch.h"
const struct hid_ops *hid_ops;
@@ -296,10 +297,12 @@ __bpf_kfunc __u8 *
hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
{
struct hid_bpf_ctx_kern *ctx_kern;
size_t end;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (rdwr_buf_size + offset > ctx->allocated_size)
if (check_add_overflow(rdwr_buf_size, offset, &end) ||
end > ctx->allocated_size)
return NULL;
return ctx_kern->data + offset;

View File

@@ -109,9 +109,10 @@ struct appleir {
struct hid_device *hid;
unsigned short keymap[ARRAY_SIZE(appleir_key_table)];
struct timer_list key_up_timer; /* timer for key up */
spinlock_t lock; /* protects .current_key */
spinlock_t lock; /* protects .current_key, .removing */
int current_key; /* the currently pressed key */
int prev_key_idx; /* key index in a 2 packets message */
bool removing; /* set during teardown; gates input_dev access */
};
static int get_key(int data)
@@ -172,7 +173,7 @@ static void key_up_tick(struct timer_list *t)
unsigned long flags;
spin_lock_irqsave(&appleir->lock, flags);
if (appleir->current_key) {
if (!appleir->removing && appleir->current_key) {
key_up(hid, appleir, appleir->current_key);
appleir->current_key = 0;
}
@@ -195,6 +196,10 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
int index;
spin_lock_irqsave(&appleir->lock, flags);
if (appleir->removing) {
spin_unlock_irqrestore(&appleir->lock, flags);
goto out;
}
/*
* If we already have a key down, take it up before marking
* this one down
@@ -229,17 +234,25 @@ static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
appleir->prev_key_idx = 0;
if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) {
key_down(hid, appleir, appleir->current_key);
/*
* Remote doesn't do key up, either pull them up, in the test
* above, or here set a timer which pulls them up after 1/8 s
*/
mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
spin_lock_irqsave(&appleir->lock, flags);
if (!appleir->removing) {
key_down(hid, appleir, appleir->current_key);
/*
* Remote doesn't do key up, either pull them up, in
* the test above, or here set a timer which pulls them
* up after 1/8 s
*/
mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
}
spin_unlock_irqrestore(&appleir->lock, flags);
goto out;
}
if (!memcmp(data, flatbattery, sizeof(flatbattery))) {
battery_flat(appleir);
spin_lock_irqsave(&appleir->lock, flags);
if (!appleir->removing)
battery_flat(appleir);
spin_unlock_irqrestore(&appleir->lock, flags);
/* Fall through */
}
@@ -318,8 +331,20 @@ static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id)
static void appleir_remove(struct hid_device *hid)
{
struct appleir *appleir = hid_get_drvdata(hid);
unsigned long flags;
/*
* Mark the driver as tearing down so that any concurrent raw_event
* (e.g. from a USB URB completion that hid_hw_stop() has not yet
* killed) and the key_up_timer softirq stop touching input_dev
* before hid_hw_stop() frees it via hidinput_disconnect().
*/
spin_lock_irqsave(&appleir->lock, flags);
appleir->removing = true;
spin_unlock_irqrestore(&appleir->lock, flags);
timer_shutdown_sync(&appleir->key_up_timer);
hid_hw_stop(hid);
timer_delete_sync(&appleir->key_up_timer);
}
static const struct hid_device_id appleir_devices[] = {

View File

@@ -2045,6 +2045,13 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
u8 *cdata = data;
int ret = 0;
if (report_enum->numbered && (size < 1 || bufsize < 1)) {
hid_warn_ratelimited(hid,
"Event data for numbered report is too short (%d vs %zu)\n",
size, bufsize);
return -EINVAL;
}
report = hid_get_report(report_enum, data);
if (!report)
return 0;

View File

@@ -296,13 +296,42 @@ static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id *
ret = letsketch_setup_input_tablet(data);
if (ret)
return ret;
goto err_shutdown_timer;
ret = letsketch_setup_input_tablet_pad(data);
if (ret)
return ret;
goto err_shutdown_timer;
return hid_hw_start(hdev, HID_CONNECT_HIDRAW);
ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
if (ret)
goto err_shutdown_timer;
return 0;
err_shutdown_timer:
/*
* Drain any pending callback and permanently disable the timer
* before devm releases data: if hid_hw_start() enabled I/O on an
* always-poll-quirk device and then failed, raw_event may have
* armed the timer already.
*/
timer_shutdown_sync(&data->inrange_timer);
return ret;
}
static void letsketch_remove(struct hid_device *hdev)
{
struct letsketch_data *data = hid_get_drvdata(hdev);
/*
* hid_hw_stop() synchronously kills the URBs that deliver
* raw_event(), so once it returns no path can re-arm
* inrange_timer. timer_shutdown_sync() then drains any
* in-flight callback and permanently disables further
* mod_timer() calls before devm releases data.
*/
hid_hw_stop(hdev);
timer_shutdown_sync(&data->inrange_timer);
}
static const struct hid_device_id letsketch_devices[] = {
@@ -315,6 +344,7 @@ static struct hid_driver letsketch_driver = {
.name = "letsketch",
.id_table = letsketch_devices,
.probe = letsketch_probe,
.remove = letsketch_remove,
.raw_event = letsketch_raw_event,
};
module_hid_driver(letsketch_driver);

View File

@@ -1374,11 +1374,27 @@ static const struct hid_device_id lg_g15_devices[] = {
};
MODULE_DEVICE_TABLE(hid, lg_g15_devices);
static void lg_g15_remove(struct hid_device *hdev)
{
struct lg_g15_data *g15 = hid_get_drvdata(hdev);
/*
* g15->work is only initialized for the models that schedule it
* (G15, G15 v2, G510). The G13 and Z-10 leave it zeroed, so only
* cancel it when it was set up.
*/
if (g15 && g15->work.func)
cancel_work_sync(&g15->work);
hid_hw_stop(hdev);
}
static struct hid_driver lg_g15_driver = {
.name = "lg-g15",
.id_table = lg_g15_devices,
.raw_event = lg_g15_raw_event,
.probe = lg_g15_probe,
.remove = lg_g15_remove,
};
module_hid_driver(lg_g15_driver);

View File

@@ -1907,8 +1907,13 @@ static int logi_dj_probe(struct hid_device *hdev,
output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT];
rep = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT];
if (rep && (rep->maxfield < 1 ||
rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1)) {
if (rep && rep->maxfield < 1) {
hid_err(hdev, "Expected size of DJ short report is %d, but got 0",
DJREPORT_SHORT_LENGTH - 1);
return -EINVAL;
}
if (rep && rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1) {
hid_err(hdev, "Expected size of DJ short report is %d, but got %d",
DJREPORT_SHORT_LENGTH - 1, rep->field[0]->report_count);
return -EINVAL;

View 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);
}

View File

@@ -72,7 +72,8 @@ struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
struct picolcd_pending *work;
struct hid_report *report = picolcd_out_report(report_id, hdev);
unsigned long flags;
int i, j, k;
int i, j;
unsigned int k;
if (!report || !data)
return NULL;

View File

@@ -24,7 +24,7 @@ CXX ?= $(CROSS_COMPILE)g++
HOSTPKG_CONFIG := pkg-config
CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT)
CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT) $(KHDR_INCLUDES)
CFLAGS += -I$(OUTPUT)/tools/include
LDLIBS += -lelf -lz -lrt -lpthread

View File

@@ -86,6 +86,20 @@ static void load_programs(const struct test_program programs[],
self->skel = hid__open();
ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open");
/*
* Disable all struct_ops maps by default so libbpf does not autoload
* programs referenced by maps that are unrelated to the current test.
*/
bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj) {
if (bpf_map__type(iter_map) == BPF_MAP_TYPE_STRUCT_OPS) {
err = bpf_map__set_autocreate(iter_map, false);
ASSERT_OK(err) TH_LOG("can not disable struct_ops map '%s'",
bpf_map__name(iter_map));
}
bpf_map__set_autoattach(iter_map, false);
}
for (int i = 0; i < progs_count; i++) {
struct bpf_program *prog;
struct bpf_map *map;
@@ -102,6 +116,10 @@ static void load_programs(const struct test_program programs[],
ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'",
programs[i].name + 4);
err = bpf_map__set_autocreate(map, true);
ASSERT_OK(err) TH_LOG("can not enable struct_ops map '%s'",
programs[i].name + 4);
/* hid_id is the first field of struct hid_bpf_ops */
ops_hid_id = bpf_map__initial_value(map, NULL);
ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data");
@@ -109,13 +127,6 @@ static void load_programs(const struct test_program programs[],
*ops_hid_id = self->hid.hid_id;
}
/* we disable the auto-attach feature of all maps because we
* only want the tested one to be manually attached in the next
* call to bpf_map__attach_struct_ops()
*/
bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj)
bpf_map__set_autoattach(iter_map, false);
err = hid__load(self->skel);
ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err);
@@ -887,6 +898,17 @@ TEST_F(hid_bpf, test_rdesc_fixup)
ASSERT_EQ(rpt_desc.value[4], 0x42);
}
TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow)
{
const struct test_program progs[] = {
{ .name = "hid_rdesc_fixup_get_data_overflow" },
};
LOAD_PROGRAMS(progs);
ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1);
}
static int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{

View File

@@ -13,6 +13,7 @@ struct attach_prog_args {
__u64 callback_check = 52;
__u64 callback2_check = 52;
__u64 get_data_overflow_check;
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
@@ -240,6 +241,20 @@ struct hid_bpf_ops rdesc_fixup = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
};
SEC("?struct_ops.s/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup_get_data_overflow, struct hid_bpf_ctx *hid_ctx)
{
if (!hid_bpf_get_data(hid_ctx, 2 /* offset */, ~0ULL /* size */))
get_data_overflow_check = 1;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops rdesc_fixup_get_data_overflow = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{

View File

@@ -513,6 +513,79 @@ class SmartTechDigitizer(Digitizer):
return absinfo is not None and absinfo.resolution == 3
class MinWin8TSParallelBigContactMax(Digitizer):
"""A parallel Win8 touchscreen that advertises a ContactCountMaximum much
larger than the number of contacts it actually reports.
Such firmware makes the driver allocate that many input slots (up to 255)
while the input report only carries a few contacts. This is what used to
drive the per-slot bit operations on mt_io_flags out of bounds. The number
of contacts a HID report can describe is limited by the descriptor size,
so a large ContactCountMaximum can only be expressed this way, decoupled
from the number of finger collections."""
def __init__(self, n_fingers=5, contact_max=250):
self.phys_max = 120, 90
rdesc_finger_str = f"""
Usage Page (Digitizers)
Usage (Finger)
Collection (Logical)
Report Size (1)
Report Count (1)
Logical Minimum (0)
Logical Maximum (1)
Usage (Tip Switch)
Input (Data,Var,Abs)
Report Size (7)
Logical Maximum (127)
Input (Cnst,Var,Abs)
Report Size (8)
Logical Maximum (255)
Usage (Contact Id)
Input (Data,Var,Abs)
Report Size (16)
Unit Exponent (-1)
Unit (SILinear: cm)
Logical Maximum (4095)
Physical Minimum (0)
Physical Maximum ({self.phys_max[0]})
Usage Page (Generic Desktop)
Usage (X)
Input (Data,Var,Abs)
Physical Maximum ({self.phys_max[1]})
Usage (Y)
Input (Data,Var,Abs)
End Collection
"""
rdesc_str = f"""
Usage Page (Digitizers)
Usage (Touch Screen)
Collection (Application)
Report ID (1)
{rdesc_finger_str * n_fingers}
Unit Exponent (-4)
Unit (SILinear: s)
Logical Maximum (65535)
Physical Maximum (65535)
Usage Page (Digitizers)
Usage (Scan Time)
Input (Data,Var,Abs)
Report Size (8)
Logical Maximum (255)
Usage (Contact Count)
Input (Data,Var,Abs)
Report ID (2)
Logical Maximum ({contact_max})
Usage (Contact Max)
Feature (Data,Var,Abs)
End Collection
{Digitizer.msCertificationBlob(68)}
"""
super().__init__(
f"uhid test parallel big contact max {contact_max}", rdesc_str
)
class BaseTest:
class TestMultitouch(base.BaseTestCase.TestUhid):
kernel_modules = [KERNEL_MODULE]
@@ -1735,6 +1808,47 @@ class TestMinWin8TSParallel(BaseTest.TestWin8Multitouch):
return MinWin8TSParallel(10)
class TestMinWin8TSParallelBigContactMax(base.BaseTestCase.TestUhid):
"""Regression test for the out-of-bounds bit operations on
struct mt_device.mt_io_flags.
A Win8 touchscreen may advertise a ContactCountMaximum much larger than
the number of contacts it reports. The driver used to keep the per-slot
active state in the bits of a single unsigned long while indexing
set_bit()/clear_bit() by the slot number, so such a device drove those bit
operations out of bounds. The sticky-fingers release timer made it fatal:
mt_release_contacts() cleared one bit per slot, overwrote the adjacent
struct mt_device members and panicked the kernel.
Send a single contact, let the 100ms sticky-fingers timer release it, and
check that the kernel reports the release cleanly instead of crashing."""
kernel_modules = [KERNEL_MODULE]
def create_device(self):
return MinWin8TSParallelBigContactMax()
def test_sticky_fingers_release_big_contact_max(self):
uhdev = self.uhdev
evdev = uhdev.get_evdev()
assert evdev.num_slots == uhdev.max_contacts
t0 = Touch(1, 5, 10)
r = uhdev.event([t0])
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
# do not release the contact; the sticky-fingers timer must do it
# after 100ms, which is where the out-of-bounds release used to hit
time.sleep(0.2)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
class TestMinWin8TSHybrid(BaseTest.TestWin8Multitouch):
def create_device(self):
return MinWin8TSHybrid()