hid-for-linus-2026090401

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEoEVH9lhNrxiMPSyI7MXwXhnZSjYFAmqa4YwACgkQ7MXwXhnZ
 SjaNsQ/9Ff0KeKgaQUZHLE47SpOlXKWQaDJmrodDkngQh+9KaZj3NgmZD5BR2Z2p
 5v/6dhs4gFoFzQtXjuR0GvDTWzu0bx1IV3IsXhqTdhQ2fLAeu8IxUL0DEOtg3lUO
 Vlf67UagvmC+K01UWkbloS3f8dEt8tg3CXyg0Uy7f23QgBFa/TbtQpXTNGlMHOqv
 G6qE1PPBqGmPI74E/5uusI8L3tw4t8A4ylHi3UcQhTxGaUGK+Ew8GCeDsIUrwrzM
 A8Um5GHdBCWZAqluT8HPnBI2wgnUR+pvda4UdqMSYkBJW2Rz1FFaOhgLkHt+azRX
 F7RhjuxcBlaZsXIaCmIZEW6rEr0QIeUPeFK6ML3uswLtFdh/yWASUMo84Ev08Z9N
 iB7qm0+S9AZSDknINAtRRcOXsOgjvug00xMf6zcUvcP66mP1Rj/PnOGb5Lqm4icp
 SiXBF+CpN0qn3h8TWG5+GvEX0AnGcmkpL0Vx7noVHJeK8Z+Yroozv+vGi1/29pxo
 ML4QEUIYV3Uj0rU1Azgd/rKiaxnizpczeJ5ViW4+ozpT4nPHjTxcz5kVsqJ6gGV3
 XTsV9YW13xgjuB0objDDeGjYRku7MtTWUfdQiKCE71a+L9nYWr5/q2mp+8QTKWqy
 /XnK5I8o2dGWhMWCPmwoqCNO/f4FGT+J2Ok6yisG8jJ/9j6Dovk=
 =a+wk
 -----END PGP SIGNATURE-----

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

Pull HID fixes from Benjamin Tissoires:

 - hid-hyperv build fixes on certain configs (Jiri Kosina)

 - HID-BPF fix and selftests now that the bpf verifier is more
   restrictive (Benjamin Tissoires)

 - Some AI detected fixes for OOB, errors and validation (Ibrahim
   Hashimov, Shen Yongchao, Wei Jie Law)

 - various device fixes (Dave Carey and Vadim Klishko)

* tag 'hid-for-linus-2026090401' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid:
  HID: bpf: serialize device reference release in struct_ops destroy path
  HID: rmi: fix OOB access with undersized RMI reports
  selftests/hid: prepare test_rdesc_fixup_get_data_overflow for the new verifier
  selftests/hid: Add a test to ensure we can write fields in hid_device
  HID: bpf: mark struct hid_device as safe BPF pointer
  HID: wacom: validate report length in wacom_intuos_pro2_bt_irq
  HID: multitouch: Fix stale MT slots when contact count drops to zero
  HID: i2c-hid: Add a quirk for a Cirque I2C device.
  HID: hyperv: make pointer arithmetics understandable for FORTIFY_SOURCE
  HID: hyperv: fix build breakage with certain configs
This commit is contained in:
Linus Torvalds 2026-09-04 09:25:38 -07:00
commit 986c24e0fe
11 changed files with 161 additions and 29 deletions

View File

@ -1253,7 +1253,7 @@ config HID_HYPERV_MOUSE
config HID_HYPERV_MOUSE_KUNIT_TEST
bool "KUnit tests for Hyper-V mouse driver" if !KUNIT_ALL_TESTS
depends on KUNIT && HID_HYPERV_MOUSE
depends on KUNIT && (HID_HYPERV_MOUSE = KUNIT || KUNIT = y)
default KUNIT_ALL_TESTS
help
Builds unit tests for the Hyper-V synthetic HID driver.

View File

@ -62,6 +62,10 @@ struct hid_bpf_offset_write_range {
u32 end;
};
struct hid_bpf_ctx__safe_trusted {
struct hid_device *hid;
};
static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
const struct bpf_reg_state *reg,
int off, int size)
@ -86,6 +90,8 @@ static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
const char *cur = NULL;
int i;
BTF_TYPE_EMIT(struct hid_bpf_ctx__safe_trusted);
t = btf_type_by_id(reg->btf, reg->btf_id);
for (i = 0; i < ARRAY_SIZE(write_ranges); i++) {
@ -250,6 +256,11 @@ static void hid_bpf_unreg(void *kdata, struct bpf_link *link)
mutex_lock(&hdev->bpf.prog_list_lock);
if (!ops->hdev) {
mutex_unlock(&hdev->bpf.prog_list_lock);
return;
}
list_del_rcu(&ops->list);
synchronize_srcu(&hdev->bpf.srcu);
ops->hdev = NULL;
@ -310,13 +321,17 @@ static struct bpf_struct_ops bpf_hid_bpf_ops = {
void __hid_bpf_ops_destroy_device(struct hid_device *hdev)
{
struct hid_bpf_ops *e;
int count = 0;
rcu_read_lock();
list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
hid_put_device(hdev);
mutex_lock(&hdev->bpf.prog_list_lock);
list_for_each_entry(e, &hdev->bpf.prog_list, list) {
e->hdev = NULL;
count++;
}
rcu_read_unlock();
mutex_unlock(&hdev->bpf.prog_list_lock);
while (count--)
hid_put_device(hdev);
}
static int __init hid_bpf_struct_ops_init(void)

View File

@ -687,7 +687,7 @@ static void mousevsc_device_info_valid_descriptor(struct kunit *test)
info->hid_descriptor.bLength = sizeof(struct hid_descriptor);
info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(4);
report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength;
report = (u8 *)(info + 1);
memset(report, 0x42, 4);
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 4);
@ -713,7 +713,7 @@ static void mousevsc_device_info_report_desc_oob(struct kunit *test)
info->hid_descriptor.bLength = sizeof(struct hid_descriptor);
info->hid_descriptor.rpt_desc.wDescriptorLength = cpu_to_le16(64);
report = ((u8 *)&info->hid_descriptor) + info->hid_descriptor.bLength;
report = (u8 *)(info + 1);
memset(report, 0x42, 8);
mousevsc_on_receive_device_info(input_dev, info, sizeof(*info) + 8);

View File

@ -338,6 +338,7 @@
#define I2C_VENDOR_ID_CIRQUE 0x0488
#define I2C_PRODUCT_ID_CIRQUE_1063 0x1063
#define I2C_PRODUCT_ID_CIRQUE_D0C1 0xD0C1
#define USB_VENDOR_ID_CJTOUCH 0x24b8
#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020 0x0020

View File

@ -1321,21 +1321,18 @@ static void mt_touch_report(struct hid_device *hid,
* Includes multi-packet support where subsequent
* packets are sent with zero contactcount.
*/
if (contact_count >= 0) {
if (contact_count > 0)
app->num_expected = contact_count;
else if (app->num_received == 0 && app->prev_scantime != scantime) {
/*
* New multi-report frame:
*
* For Win8 PTPs the first packet (td->num_received == 0) may
* have a contactcount of 0 if there only is a button event.
* We double check that this is not a continuation packet
* of a possible multi-packet frame be checking that the
* timestamp has changed.
*
* Some other devices use a sentinel frame with 0 to release all contacts
*/
if ((app->quirks & MT_QUIRK_WIN8_PTP_BUTTONS) &&
app->num_received == 0 &&
app->prev_scantime != scantime)
app->num_expected = contact_count;
/* A non 0 contact count always indicates a first packet */
else if (contact_count)
app->num_expected = contact_count;
app->num_expected = 0;
}
app->prev_scantime = scantime;

View File

@ -235,7 +235,23 @@ static int rmi_hid_read_block(struct rmi_transport_dev *xport, u16 addr,
break;
}
read_input_count = data->readReport[1];
read_input_count = min_t(int, data->readReport[1],
data->input_report_size - 2);
if (!read_input_count) {
/*
* A zero length reply advances neither
* bytes_read nor bytes_needed, and because a
* reply did arrive the wait above does not
* time out either, so a device answering 0
* forever would spin here indefinitely with
* page_mutex held.
*/
hid_warn(hdev, "%s: zero-length read reply\n",
__func__);
clear_bit(RMI_READ_DATA_PENDING, &data->flags);
ret = -EIO;
break;
}
memcpy(buf + bytes_read, &data->readReport[2],
min(read_input_count, bytes_needed));
@ -271,6 +287,11 @@ static int rmi_hid_write_block(struct rmi_transport_dev *xport, u16 addr,
goto exit;
}
if (len + 4 > data->output_report_size) {
ret = -EINVAL;
goto exit;
}
data->writeReport[0] = RMI_WRITE_REPORT_ID;
data->writeReport[1] = len;
data->writeReport[2] = addr & 0xFF;
@ -666,8 +687,16 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
if (id->driver_data)
data->device_flags = id->driver_data;
/*
* RMI_DEVICE can only mean "this probe validated the RMI reports and
* allocated writeReport": every bail-out to start below skips that
* allocation, and device_flags left carrying RMI_DEVICE from
* driver_data would send rmi_input_configured() into rmi_set_page()
* with writeReport still NULL. A bind through the new_id sysfs
* attribute can supply driver_data with the bit set, so do not let
* driver_data grant it.
*/
data->device_flags = id->driver_data & ~RMI_DEVICE;
/*
* Check for the RMI specific report ids. If they are misisng
@ -696,6 +725,17 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
data->output_report_size = hid_report_len(output_report);
/*
* The write reports built by this driver occupy 6 bytes and the read
* handshake looks at the first 3 bytes of an input report, so refuse
* to drive a device whose reports cannot hold them.
*/
if (data->output_report_size < 6 || data->input_report_size < 3) {
hid_err(hdev, "rmi reports too small (out=%u in=%u)\n",
data->output_report_size, data->input_report_size);
goto start;
}
data->device_flags |= RMI_DEVICE;
alloc_size = data->output_report_size + data->input_report_size;

View File

@ -136,6 +136,8 @@ static const struct i2c_hid_quirks {
I2C_HID_QUIRK_BAD_INPUT_SIZE },
{ I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_1063,
I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND },
{ I2C_VENDOR_ID_CIRQUE, I2C_PRODUCT_ID_CIRQUE_D0C1,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
/*
* Without additional power on command, at least some QTEC devices send garbage
*/

View File

@ -1550,6 +1550,19 @@ static int wacom_intuos_pro2_bt_irq(struct wacom_wac *wacom, size_t len)
return 0;
}
if (wacom->features.type == INTUOSP2_BT ||
wacom->features.type == INTUOSP2S_BT) {
if (len < 286) {
dev_warn(wacom->pen_input->dev.parent,
"Pro2 BT report too short: %zu bytes\n", len);
return 0;
}
} else if (len < 46) {
dev_warn(wacom->pen_input->dev.parent,
"Pro2 BT report too short: %zu bytes\n", len);
return 0;
}
wacom_intuos_pro2_bt_pen(wacom);
if (wacom->features.type == INTUOSP2_BT ||
wacom->features.type == INTUOSP2S_BT) {

View File

@ -67,14 +67,17 @@ struct test_program {
int insert_head;
};
#define LOAD_PROGRAMS(progs) \
load_programs(progs, ARRAY_SIZE(progs), _metadata, self, variant)
load_programs(progs, ARRAY_SIZE(progs), false, _metadata, self, variant)
#define LOAD_PROGRAMS_MAY_FAIL(progs) \
load_programs(progs, ARRAY_SIZE(progs), true, _metadata, self, variant)
#define LOAD_BPF \
load_programs(NULL, 0, _metadata, self, variant)
static void load_programs(const struct test_program programs[],
const size_t progs_count,
struct __test_metadata *_metadata,
FIXTURE_DATA(hid_bpf) * self,
const FIXTURE_VARIANT(hid_bpf) * variant)
load_programs(NULL, 0, false, _metadata, self, variant)
static int load_programs(const struct test_program programs[],
const size_t progs_count,
bool load_may_fail,
struct __test_metadata *_metadata,
FIXTURE_DATA(hid_bpf) * self,
const FIXTURE_VARIANT(hid_bpf) * variant)
{
struct bpf_map *iter_map;
int err = -EINVAL;
@ -128,6 +131,9 @@ static void load_programs(const struct test_program programs[],
}
err = hid__load(self->skel);
if (err && load_may_fail)
return err;
ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err);
for (int i = 0; i < progs_count; i++) {
@ -147,6 +153,7 @@ static void load_programs(const struct test_program programs[],
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
return 0;
}
/*
@ -904,11 +911,39 @@ TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow)
{ .name = "hid_rdesc_fixup_get_data_overflow" },
};
LOAD_PROGRAMS(progs);
/* newer verifier can detect the overflow at load time */
if (LOAD_PROGRAMS_MAY_FAIL(progs))
return;
ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1);
}
TEST_F(hid_bpf, test_rdesc_fixup_change_uniq_name_phys)
{
const struct test_program progs[] = {
{ .name = "hid_rdesc_fixup_change_uniq_name_phys" },
};
char expected[256], buf[256] = {};
int err;
LOAD_PROGRAMS(progs);
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(buf)), buf);
ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWNAME");
ASSERT_STREQ("name coming from bpf", buf);
snprintf(expected, sizeof(expected), "%d phys:coming:from:bpf", self->hid.dev_id);
err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(buf)), buf);
ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWPHYS");
ASSERT_STREQ(expected, buf);
err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(buf)), buf);
ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ");
ASSERT_STREQ("uniq:coming:from:bpf", buf);
}
static int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{

View File

@ -255,6 +255,32 @@ struct hid_bpf_ops rdesc_fixup_get_data_overflow = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow,
};
SEC("?struct_ops.s/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup_change_uniq_name_phys, struct hid_bpf_ctx *hid_ctx)
{
#define HID_BPF_MEMCPY(target, str) \
__builtin_memcpy(target, str, sizeof(str))
HID_BPF_MEMCPY(hid_ctx->hid->name, "name coming from bpf");
HID_BPF_MEMCPY(hid_ctx->hid->uniq, "uniq:coming:from:bpf");
/* hid_bpf relies on a phys being a rand % 1024 */
for (int i = 0; i < 5; i++) {
if (!hid_ctx->hid->phys[i]) {
HID_BPF_MEMCPY(hid_ctx->hid->phys + i, " phys:coming:from:bpf");
break;
}
}
#undef HID_BPF_MEMCPY
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops rdesc_fixup_change_uniq_name_phys = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_change_uniq_name_phys,
};
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

@ -61,6 +61,9 @@ enum hid_report_type {
struct hid_device {
unsigned int id;
char name[128];
char phys[64];
char uniq[64];
} __attribute__((preserve_access_index));
struct bpf_wq {