mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 03:27:30 -04:00
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:
@@ -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;
|
||||
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user