* Re: [PATCH v3 6/8] HID: asus: prevent a late KEY_FN_ESC to trigger a use-after-free
From: Antheas Kapenekakis @ 2026-06-13 16:04 UTC (permalink / raw)
To: Denis Benato
Cc: linux-kernel, linux-input, Benjamin Tissoires, Jiri Kosina,
Luke D . Jones, Mateusz Schyboll, Denis Benato, Connor Belli,
sahiko-bot
In-Reply-To: <20260613153029.2559774-7-denis.benato@linux.dev>
On Sat, 13 Jun 2026 at 17:30, Denis Benato <denis.benato@linux.dev> wrote:
>
> A late KEY_FN_ESC event can trigger asus_event() and unconditionally call
> schedule_work() after the work was supposedly canceled.
>
> Once the driver is removed and drvdata is freed by devres, the workqueue
> could execute asus_sync_fn_lock() and dereference the freed drvdata.
>
> Fixes: f631011e36b8 ("HID: hid-asus: Implement fn lock for Asus ProArt P16")
> Reported-by: sahiko-bot@kernel.org
> Signed-off-by: Denis Benato <denis.benato@linux.dev>
> ---
> drivers/hid/hid-asus.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index a6467172c455..a65a28137f0d 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -148,6 +148,7 @@ struct asus_drvdata {
> int battery_stat;
> bool battery_in_query;
> unsigned long battery_next_query;
> + bool fn_lock_sync_work_disabled;
> struct work_struct fn_lock_sync_work;
> bool fn_lock;
> #if IS_REACHABLE(CONFIG_ASUS_WMI)
> @@ -416,7 +417,7 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
> case KEY_KBDILLUMTOGGLE:
> return !asus_hid_event(ASUS_EV_BRTTOGGLE);
> case KEY_FN_ESC:
> - if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
> + if (!drvdata->fn_lock_sync_work_disabled && (drvdata->quirks & QUIRK_HID_FN_LOCK)) {
> drvdata->fn_lock = !drvdata->fn_lock;
> schedule_work(&drvdata->fn_lock_sync_work);
> }
> @@ -1497,8 +1498,10 @@ static void asus_remove(struct hid_device *hdev)
> cancel_work_sync(&drvdata->kbd_backlight->work);
> }
>
> - if (drvdata->quirks & QUIRK_HID_FN_LOCK)
> + if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
> + drvdata->fn_lock_sync_work_disabled = true;
> cancel_work_sync(&drvdata->fn_lock_sync_work);
> + }
I am not sure this is a real bug. can _event be called during _remove?
Doesn't the core prevent that?
> #if IS_REACHABLE(CONFIG_ASUS_WMI)
> drvdata->wmi_work_disabled = true;
> --
> 2.47.3
>
>
^ permalink raw reply
* Re: [PATCH v3 5/8] HID: asus: avoid sleeping calls in atomic context
From: Antheas Kapenekakis @ 2026-06-13 16:15 UTC (permalink / raw)
To: Denis Benato
Cc: linux-kernel, linux-input, Benjamin Tissoires, Jiri Kosina,
Luke D . Jones, Mateusz Schyboll, Denis Benato, Connor Belli,
sahiko-bot
In-Reply-To: <20260613153029.2559774-6-denis.benato@linux.dev>
On Sat, 13 Jun 2026 at 17:30, Denis Benato <denis.benato@linux.dev> wrote:
>
> Avoid possibly calling asus_wmi_send_event(): a method that can sleep in
> asus_raw_event() that is called in atomic context.
>
> This commit changes when methods are called, not method parameters:
> the driver behaves as before.
Observe the new AI guidelines and add assisted by tags if necessary.
Or write the patches manually for mainline. This sentence is an AI
euphemism
Who let 1489a34e97ef through? I implemented the plumbing for WMI
callbacks with asus_hid_event last year because WMI cannot be called
through HID. Brightness events had the exact same problem.
kbd_led_work/kbd_led_update_all could be renamed and used for this. I
would rather avoid introducing a new work queue. The one I added is
enough if not too much
Antheas
> Fixes: 1489a34e97ef ("HID: asus: Implement Fn+F5 fan control key handler")
> Reported-by: sahiko-bot@kernel.org
> Signed-off-by: Denis Benato <denis.benato@linux.dev>
> ---
> drivers/hid/hid-asus.c | 67 +++++++++++++++++++++++++++++++++++-------
> 1 file changed, 56 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index f38b18ad65c6..a6467172c455 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -150,6 +150,13 @@ struct asus_drvdata {
> unsigned long battery_next_query;
> struct work_struct fn_lock_sync_work;
> bool fn_lock;
> +#if IS_REACHABLE(CONFIG_ASUS_WMI)
> + struct work_struct wmi_work;
> + bool wmi_work_disabled;
> + u8 wmi_data[FEATURE_KBD_REPORT_SIZE];
> + int wmi_size;
> + spinlock_t wmi_lock;
> +#endif
> struct asus_xgm_led *xgm_led;
> };
>
> @@ -338,6 +345,7 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
> return 0;
> }
>
> +#if IS_REACHABLE(CONFIG_ASUS_WMI)
> /*
> * Send events to asus-wmi driver for handling special keys
> */
> @@ -361,6 +369,32 @@ static int asus_wmi_send_event(struct asus_drvdata *drvdata, u8 code)
> return 0;
> }
>
> +static void asus_wmi_work(struct work_struct *work)
> +{
> + struct asus_drvdata *drvdata = container_of(work, struct asus_drvdata, wmi_work);
> + u8 report_data[FEATURE_KBD_REPORT_SIZE];
> + int report_size;
> + unsigned long flags;
> + int ret;
> +
> + spin_lock_irqsave(&drvdata->wmi_lock, flags);
> + memcpy(report_data, drvdata->wmi_data, drvdata->wmi_size);
> + report_size = drvdata->wmi_size;
> + spin_unlock_irqrestore(&drvdata->wmi_lock, flags);
> +
> + ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE);
> + if (ret) {
> + if (ret != -ENODEV)
> + hid_warn(drvdata->hdev, "Failed to notify asus-wmi: %d\n", ret);
> +
> + /* Fallback: pass the raw event to the HID core */
> + hid_report_raw_event(drvdata->hdev, HID_INPUT_REPORT,
> + report_data, report_size,
> + report_size, 1);
> + }
> +}
> +#endif
> +
> static int asus_event(struct hid_device *hdev, struct hid_field *field,
> struct hid_usage *usage, __s32 value)
> {
> @@ -397,6 +431,9 @@ static int asus_raw_event(struct hid_device *hdev,
> struct hid_report *report, u8 *data, int size)
> {
> struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
> +#if IS_REACHABLE(CONFIG_ASUS_WMI)
> + unsigned long flags;
> +#endif
>
> if (drvdata->battery && data[0] == BATTERY_REPORT_ID)
> return asus_report_battery(drvdata, data, size);
> @@ -422,19 +459,18 @@ static int asus_raw_event(struct hid_device *hdev,
> * pass to userspace so it can implement its own fan control.
> */
> if (data[1] == ASUS_FAN_CTRL_KEY_CODE) {
> - int ret = asus_wmi_send_event(drvdata, ASUS_FAN_CTRL_KEY_CODE);
> -
> - if (ret == 0) {
> - /* Successfully handled by asus-wmi, block event */
> +#if IS_REACHABLE(CONFIG_ASUS_WMI)
> + spin_lock_irqsave(&drvdata->wmi_lock, flags);
> + memcpy(drvdata->wmi_data, data, min_t(int, size, sizeof(drvdata->wmi_data)));
> + drvdata->wmi_size = size;
> + spin_unlock_irqrestore(&drvdata->wmi_lock, flags);
> +
> + if (!drvdata->wmi_work_disabled) {
> + schedule_work(&drvdata->wmi_work);
> + /* Successfully handled asynchronously, block event */
> return -1;
> }
> -
> - /*
> - * Warn if asus-wmi failed (but not if it's unavailable).
> - * Let the event reach userspace in all failure cases.
> - */
> - if (ret != -ENODEV)
> - hid_warn(hdev, "Failed to notify asus-wmi: %d\n", ret);
> +#endif
> }
>
> /*
> @@ -1350,6 +1386,10 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
> hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
>
> drvdata->hdev = hdev;
> +#if IS_REACHABLE(CONFIG_ASUS_WMI)
> + INIT_WORK(&drvdata->wmi_work, asus_wmi_work);
> + spin_lock_init(&drvdata->wmi_lock);
> +#endif
>
> if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
> ret = asus_battery_probe(hdev);
> @@ -1460,6 +1500,11 @@ static void asus_remove(struct hid_device *hdev)
> if (drvdata->quirks & QUIRK_HID_FN_LOCK)
> cancel_work_sync(&drvdata->fn_lock_sync_work);
>
> +#if IS_REACHABLE(CONFIG_ASUS_WMI)
> + drvdata->wmi_work_disabled = true;
> + cancel_work_sync(&drvdata->wmi_work);
> +#endif
> +
> if (drvdata->xgm_led)
> led_classdev_unregister(&drvdata->xgm_led->cdev);
>
> --
> 2.47.3
>
>
^ permalink raw reply
* Re: [PATCH 1/7] hid-asus: Fix up Zenbook Duo report descriptors
From: Nathan Chancellor @ 2026-06-13 16:19 UTC (permalink / raw)
To: Paolo Pisati; +Cc: Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260513163248.16483-2-p.pisati@gmail.com>
Hi Paolo,
On Wed, May 13, 2026 at 06:32:42PM +0200, Paolo Pisati wrote:
> From: Joshua Leivenzon <hacker1024@users.sourceforge.net>
>
> This is similar to the T100CHI/T90CHI keyboard dock fix.
> Without it, all reports log:
>
> Unmapped Asus vendor usagepage code 0x76
>
> Signed-off-by: Joshua Leivenzon <hacker1024@users.sourceforge.net>
> ---
> drivers/hid/hid-asus.c | 13 +++++++++----
> 1 file changed, 9 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index 3f5e96900b67a..ce246efba74d3 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -99,6 +99,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
> #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
> #define QUIRK_ROG_ALLY_XPAD BIT(13)
> #define QUIRK_HID_FN_LOCK BIT(14)
> +#define QUIRK_ZENBOOK_DUO_KEYBOARD BIT(15)
>
> #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
> QUIRK_NO_INIT_REPORTS | \
> @@ -1384,17 +1385,20 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
> rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
> }
> - /* For the T100CHI/T90CHI keyboard dock */
> - if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
> + /* For the T100CHI/T90CHI keyboard dock and Zenbook Duo 2024+ keyboards */
> + if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI | QUIRK_ZENBOOK_DUO_KEYBOARD)) {
> int rsize_orig;
> int offs;
>
> if (drvdata->quirks & QUIRK_T100CHI) {
> rsize_orig = 403;
> offs = 388;
> - } else {
> + } else if (drvdata->quirks & QUIRK_T90CHI) {
> rsize_orig = 306;
> offs = 291;
> + } else if (drvdata->quirks & QUIRK_ZENBOOK_DUO_KEYBOARD) {
> + rsize_orig = 257;
> + offs = 176;
> }
The way this if block is now written will cause a clang warning:
https://lore.kernel.org/202606110526.QfgiXQTQ-lkp@intel.com/
It is obviously a false positive since we know one of those branches
will be taken from the parent if statement's condition.
Consider keeping the else, maybe with a comment, to make it clear to
both humans and the compiler that all cases are covered?
} else { /* QUIRK_ZENBOOK_DUO_KEYBOARD */
rsize_orig = 257;
offs = 176;
}
> /*
> @@ -1414,7 +1418,8 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
>
> hid_info(hdev, "Fixing up %s keyb report descriptor\n",
> drvdata->quirks & QUIRK_T100CHI ?
> - "T100CHI" : "T90CHI");
> + "T100CHI" : drvdata->quirks & QUIRK_T90CHI ?
> + "T90CHI" : "ZENBOOK DUO");
>
> memcpy(new_rdesc, rdesc, rsize_orig);
> *rsize = rsize_orig + 1;
> --
> 2.53.0
>
--
Cheers,
Nathan
^ permalink raw reply
* [PATCH 0/2] HID: logitech-hidpp: fix Signature M650 side button timing
From: Elliot Douglas @ 2026-06-13 17:51 UTC (permalink / raw)
To: linux-input; +Cc: lains, hadess, jikos, bentiss, linux-kernel, edouglas7358
The Logitech Signature M650 over Bluetooth exposes its side buttons in the
normal mouse report, but the reported BTN_SIDE/BTN_EXTRA events are short
click-like events emitted around button release rather than physical
press/release events with the real hold duration. The device appears to reserve
the held side-button state for a built-in gesture mode: holding a side button
long enough, or holding it while using the wheel for horizontal scrolling, can
mean the normal mouse report never emits a usable side-button press at all.
That makes the buttons unusable for standard Linux hold actions such as
push-to-talk, drag modifiers, or remapping rules that depend on key-up timing.
When HID++ 2.0 feature 0x1b04, SpecialKeysMseButtons /
REPROG_CONTROLS_V4, temporarily diverts the same controls, the device sends
diverted-control notifications with real press and release timing. This series
adds quirk-gated support for those notifications and enables it for the
Bluetooth Signature M650.
Before enabling diversion, the driver verifies that each mapped control is
present in the device's HID++ control table and is advertised as a divertable
mouse control.
The diverted M650 controls are reported as BTN_BACK and BTN_FORWARD. Logitech's
Signature M650 getting-started page labels these physical controls as
Back/Forward buttons and describes their default page-navigation behavior:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650
The reprogrammable-control support is per-product and parses the full HID++
divertedButtonsEvent pressed-control list, so it can support devices with more
buttons without relying on a single last-control release heuristic. Only the
Signature M650 opts in for now. Other Logitech devices should only be enabled
after their HID++ control IDs and divertedButtonsEvent behavior are captured
and verified.
There is evidence that this is not unique to the M650. A prior MX Anywhere 3
patch used the same HID++ feature to fix thumb buttons that only activated on
release, and Logitech documents side-button + wheel horizontal scrolling for
both the MX Anywhere 3/3S and Signature M650. Solaar's device reports and rules
documentation also show HID++ divertable back/forward controls on MX Master 3
and MX Master 3S class devices. This series remains conservative and only
enables the device tested here.
Tested with a Logitech Signature M650 L over Bluetooth, HID ID
0005:046D:B02A. Baseline evtest showed short release-time BTN_SIDE/BTN_EXTRA
events. Earlier local testing of the same HID++ diversion path showed real
hold-duration press/release events, including holds longer than 4 seconds for
both buttons.
Elliot Douglas (2):
HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
HID: logitech-hidpp: enable reprogrammable buttons on Signature M650
drivers/hid/hid-logitech-hidpp.c | 236 ++++++++++++++++++++++++++++++-
1 file changed, 235 insertions(+), 1 deletion(-)
base-commit: f0866517be9345d8245d32b722574b8aecccb348
--
2.54.0
^ permalink raw reply
* [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
From: Elliot Douglas @ 2026-06-13 17:51 UTC (permalink / raw)
To: linux-input; +Cc: lains, hadess, jikos, bentiss, linux-kernel, edouglas7358
In-Reply-To: <20260613175109.44365-1-edouglas7358@gmail.com>
Some Logitech HID++ 2.0 mice can report diverted reprogrammable
controls through HID++ feature 0x1b04, SpecialKeysMseButtons /
REPROG_CONTROLS_V4, instead of the normal HID mouse report.
Add a quirk-gated event path for those controls. The handler temporarily
diverts verified per-product controls, parses divertedButtonsEvent as the
current pressed-control list, and reports the corresponding evdev key state
for every mapped control.
Keep the control mappings in per-product profiles so adding support for
another mouse does not change the evdev capabilities advertised by
already-supported devices.
Documentation for feature 0x1b04 describes divertedButtonsEvent as a list
of currently pressed diverted buttons, which is the event format handled
here.
Link: https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html
Signed-off-by: Elliot Douglas <edouglas7358@gmail.com>
---
drivers/hid/hid-logitech-hidpp.c | 215 +++++++++++++++++++++++++++++++
1 file changed, 215 insertions(+)
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 70ba1a5e40d8..24c9cfaa4f37 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(28)
#define HIDPP_QUIRK_WIRELESS_STATUS BIT(29)
#define HIDPP_QUIRK_RESET_HI_RES_SCROLL BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS BIT(31)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -205,6 +206,7 @@ struct hidpp_device {
struct hidpp_scroll_counter vertical_wheel_counter;
u8 wireless_feature_index;
+ u8 reprog_controls_feature_index;
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
@@ -3601,6 +3603,209 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
return 1;
}
+/* -------------------------------------------------------------------------- */
+/* HID++2.0 reprogrammable controls */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4 0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT 0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO 0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING 0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT BIT(1)
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
+
+struct hidpp_reprog_control_mapping {
+ u16 control;
+ u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+ const struct hidpp_reprog_control_mapping *mappings;
+ unsigned int mapping_count;
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
+{
+ return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
+{
+ struct hidpp_report response;
+ u8 feature_index = hidpp->reprog_controls_feature_index;
+ u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+ int ret;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
+ &response);
+ if (ret > 0)
+ return -EPROTO;
+ if (ret)
+ return ret;
+
+ return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
+ u8 index, u16 *control,
+ u8 *flags)
+{
+ struct hidpp_report response;
+ u8 feature_index = hidpp->reprog_controls_feature_index;
+ u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+ int ret;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
+ sizeof(index), &response);
+ if (ret > 0)
+ return -EPROTO;
+ if (ret)
+ return ret;
+
+ *control = get_unaligned_be16(&response.fap.params[0]);
+ *flags = response.fap.params[4];
+
+ return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
+ u16 control)
+{
+ int count, ret;
+ u16 cid;
+ u8 flags;
+ int i;
+
+ count = hidpp20_reprog_controls_get_count(hidpp);
+ if (count < 0)
+ return false;
+
+ for (i = 0; i < count; i++) {
+ ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
+ &flags);
+ if (ret)
+ return false;
+
+ if (cid == control)
+ return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+ (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+ }
+
+ return false;
+}
+
+static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+ u16 control, u8 flags)
+{
+ struct hidpp_report response;
+ u8 params[5];
+
+ put_unaligned_be16(control, ¶ms[0]);
+ params[2] = flags;
+ put_unaligned_be16(control, ¶ms[3]);
+
+ return hidpp_send_fap_command_sync(hidpp,
+ hidpp->reprog_controls_feature_index,
+ HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+ params, sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)
+{
+ const struct hidpp_reprog_controls_profile *profile;
+ u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+ HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+ unsigned int i;
+
+ if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+ return;
+
+ profile = hidpp20_reprog_controls_get_profile(hidpp);
+ if (!profile)
+ return;
+
+ if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
+ &hidpp->reprog_controls_feature_index))
+ return;
+
+ for (i = 0; i < profile->mapping_count; i++) {
+ u16 control = profile->mappings[i].control;
+
+ if (!hidpp20_reprog_controls_find_control(hidpp, control))
+ continue;
+
+ hidpp20_reprog_controls_set_control_reporting(hidpp, control, flags);
+ }
+}
+
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+ u8 *data, int size)
+{
+ const struct hidpp_reprog_controls_profile *profile;
+ const struct hidpp_reprog_control_mapping *mapping;
+ struct hidpp_report *report = (struct hidpp_report *)data;
+ u16 controls[4];
+ bool pressed;
+ unsigned int i, j;
+
+ if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+ !hidpp->input ||
+ hidpp->reprog_controls_feature_index == 0xff)
+ return 0;
+
+ profile = hidpp20_reprog_controls_get_profile(hidpp);
+ if (!profile)
+ return 0;
+
+ if (size < HIDPP_REPORT_LONG_LENGTH ||
+ report->fap.feature_index != hidpp->reprog_controls_feature_index ||
+ report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(controls); i++)
+ controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
+
+ for (i = 0; i < profile->mapping_count; i++) {
+ mapping = &profile->mappings[i];
+ pressed = false;
+
+ for (j = 0; j < ARRAY_SIZE(controls); j++) {
+ if (controls[j] == mapping->control) {
+ pressed = true;
+ break;
+ }
+ }
+
+ input_report_key(hidpp->input, mapping->code, pressed);
+ }
+
+ input_sync(hidpp->input);
+
+ return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
+ struct input_dev *input_dev)
+{
+ const struct hidpp_reprog_controls_profile *profile;
+ unsigned int i;
+
+ profile = hidpp20_reprog_controls_get_profile(hidpp);
+ if (!profile)
+ return;
+
+ for (i = 0; i < profile->mapping_count; i++)
+ input_set_capability(input_dev, EV_KEY, profile->mappings[i].code);
+}
+
static void hidpp10_extra_mouse_buttons_populate_input(
struct hidpp_device *hidpp, struct input_dev *input_dev)
{
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
+
+ if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+ hidpp20_reprog_controls_populate_input(hidpp, input);
}
static int hidpp_input_configured(struct hid_device *hdev,
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
return ret;
}
+ ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+ if (ret != 0)
+ return ret;
+
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
if (ret != 0)
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct work_struct *work)
return;
}
+ hidpp20_reprog_controls_connect(hidpp);
+
if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
ret = hidpp10_consumer_keys_connect(hidpp);
if (ret)
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp->hid_dev = hdev;
hidpp->name = hdev->name;
hidpp->quirks = id->driver_data;
+ hidpp->reprog_controls_feature_index = 0xff;
hid_set_drvdata(hdev, hidpp);
ret = hid_parse(hdev);
--
2.54.0
^ permalink raw reply related
* [PATCH 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650
From: Elliot Douglas @ 2026-06-13 17:51 UTC (permalink / raw)
To: linux-input; +Cc: lains, hadess, jikos, bentiss, linux-kernel, edouglas7358
In-Reply-To: <20260613175109.44365-1-edouglas7358@gmail.com>
The Bluetooth Signature M650 exposes its side buttons through the normal
mouse report, but the observed events are short click-like events emitted
around release rather than physical press/release state.
The device appears to use the held side-button state for its built-in
gesture and side-button + wheel horizontal-scroll mode. As a result,
holding a side button long enough can prevent the normal mouse report from
emitting a usable button event at all.
HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056
provides real press and release timing for those same controls. Logitech
documents the Signature M650 side buttons as Back/Forward buttons, so
report the diverted controls as BTN_BACK and BTN_FORWARD.
Link: https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650
Signed-off-by: Elliot Douglas <edouglas7358@gmail.com>
---
drivers/hid/hid-logitech-hidpp.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 24c9cfaa4f37..80108778ee80 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -3621,6 +3621,9 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
+#define HIDPP_REPROG_CONTROL_M650_BACK 0x0053
+#define HIDPP_REPROG_CONTROL_M650_FORWARD 0x0056
+
struct hidpp_reprog_control_mapping {
u16 control;
u16 code;
@@ -3631,9 +3634,24 @@ struct hidpp_reprog_controls_profile {
unsigned int mapping_count;
};
+static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = {
+ { HIDPP_REPROG_CONTROL_M650_BACK, BTN_BACK },
+ { HIDPP_REPROG_CONTROL_M650_FORWARD, BTN_FORWARD },
+};
+
+static const struct hidpp_reprog_controls_profile m650_reprog_controls_profile = {
+ .mappings = m650_reprog_control_mappings,
+ .mapping_count = ARRAY_SIZE(m650_reprog_control_mappings),
+};
+
static const struct hidpp_reprog_controls_profile *
hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
{
+ switch (hidpp->hid_dev->product) {
+ case 0xb02a:
+ return &m650_reprog_controls_profile;
+ }
+
return NULL;
}
@@ -4921,7 +4939,8 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* MX Vertical mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
{ /* Signature M650 over Bluetooth */
- HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a),
+ .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
{ /* MX Master 3 mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
{ /* MX Anywhere 3 mouse over Bluetooth */
--
2.54.0
^ permalink raw reply related
* [PATCH] Input: edt-ft5x06: correct CONFIG_DEBUG_FS macro name in comment
From: Ethan Nelson-Moore @ 2026-06-13 20:50 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Ethan Nelson-Moore
A comment in the edt-ft5x06 driver incorrectly refers to CONFIG_DEBUGFS
instead of CONFIG_DEBUG_FS. Correct it.
Discovered while searching for CONFIG_* symbols referenced in code but
not defined in any Kconfig file.
Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index d3b1177185a3..410be061965e 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -825,7 +825,7 @@ static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
{
}
-#endif /* CONFIG_DEBUGFS */
+#endif /* CONFIG_DEBUG_FS */
static int edt_ft5x06_ts_identify(struct i2c_client *client,
struct edt_ft5x06_ts_data *tsdata)
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] Input: edt-ft5x06: correct CONFIG_DEBUG_FS macro name in comment
From: sashiko-bot @ 2026-06-13 20:59 UTC (permalink / raw)
To: Ethan Nelson-Moore; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260613205042.80181-1-enelsonmoore@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Imbalanced disable_irq() causes the touchscreen to remain disabled after resume if suspended while in factory mode.
--
commit c833a423ddc21c60c7ceaf7761d2c4e876b25fee
Author: Ethan Nelson-Moore <enelsonmoore@gmail.com>
Input: edt-ft5x06: correct CONFIG_DEBUG_FS macro name in comment
A comment in the edt-ft5x06 driver incorrectly refers to CONFIG_DEBUGFS
instead of CONFIG_DEBUG_FS. Correct it.
> diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
> index d3b1177185a3b..410be061965ec 100644
> --- a/drivers/input/touchscreen/edt-ft5x06.c
> +++ b/drivers/input/touchscreen/edt-ft5x06.c
> @@ -825,7 +825,7 @@ static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
> {
> }
>
> -#endif /* CONFIG_DEBUGFS */
> +#endif /* CONFIG_DEBUG_FS */
[Severity: High]
This is a pre-existing issue, but I noticed an IRQ depth imbalance that
causes the touchscreen to remain disabled after resume if suspended while in
factory mode.
If the device is placed into factory mode via debugfs,
edt_ft5x06_factory_mode() calls disable_irq(), increasing the IRQ disable
depth to 1. During system suspend, edt_ft5x06_ts_suspend() unconditionally
calls disable_irq(), increasing the depth to 2.
On resume, edt_ft5x06_ts_resume() calls enable_irq() (reducing depth to 1),
but then it invokes edt_ft5x06_factory_mode() to restore state:
drivers/input/touchscreen/edt-ft5x06.c:edt_ft5x06_ts_resume() {
...
if (tsdata->factory_mode)
ret = edt_ft5x06_factory_mode(tsdata);
...
}
Because edt_ft5x06_factory_mode() unconditionally calls disable_irq()
again, the depth goes back to 2:
drivers/input/touchscreen/edt-ft5x06.c:edt_ft5x06_factory_mode() {
...
disable_irq(client->irq);
...
}
When the user later switches back to work mode, edt_ft5x06_work_mode() only
calls enable_irq() once. Does this leave the IRQ permanently disabled,
making the touchscreen entirely unusable?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613205042.80181-1-enelsonmoore@gmail.com?part=1
^ permalink raw reply
* [PATCH] Input: apple_z2 - bound the device-reported packet length
From: Bryam Vargas via B4 Relay @ 2026-06-13 21:42 UTC (permalink / raw)
To: Dmitry Torokhov, Sasha Finkelstein
Cc: linux-arm-kernel, Neal Gompa, linux-input, asahi, linux-kernel,
Sven Peter, Janne Grunau
From: Bryam Vargas <hexlabsecurity@proton.me>
apple_z2_read_packet() takes a 16-bit length from the touch controller's
interrupt-data reply (rx_buf[1..2]) and, only rounded down to a multiple of
four, uses it as the size of the second SPI transfer into the fixed-size
rx_buf:
pkt_len = (get_unaligned_le16(z2->rx_buf + 1) + 8) & 0xfffffffc;
error = spi_read(z2->spidev, z2->rx_buf, pkt_len);
rx_buf is a fixed 4000-byte buffer, but pkt_len is fully controlled by the
device and is never checked against it, so a malicious, malfunctioning or
counterfeit controller (or an interposer on the SPI bus) that reports a
large length makes spi_read() write up to 65540 device-supplied bytes into
the 4000-byte buffer -- a controller-driven heap out-of-bounds write of up
to about 61 KiB. The recently added reply-type check only validates
rx_buf[0], not the length.
Reject any packet whose length exceeds the receive buffer.
Fixes: 471a92f8a21a ("Input: apple_z2 - add a driver for Apple Z2 touchscreens")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Reachable on every touch interrupt once the controller is booted
(apple_z2_irq -> apple_z2_read_packet).
Verified with a faithful in-kernel KASAN litmus (the verbatim 4000-byte
allocation, the exact pkt_len computation, and the spi_read transfer modelled
as a memset of pkt_len controller bytes), CONFIG_KASAN_INLINE=y:
Arm A, reported length 0x1000 -> pkt_len 4104:
BUG: KASAN: slab-out-of-bounds in apple_z2_read_packet
Write of size 4104 ... located 0 bytes inside of allocated 4000-byte region
... which belongs to the cache kmalloc-4k of size 4096
Arm B, with this patch (length rejected): clean
Arm C, benign length: clean
AddressSanitizer (x86_64 and i386), reported length 0xffff -> pkt_len 65540:
heap-buffer-overflow WRITE of size 65540, both ABIs.
Reproducer and full logs available on request.
---
drivers/input/touchscreen/apple_z2.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
index 271ababf0ad5..ff9ff97be185 100644
--- a/drivers/input/touchscreen/apple_z2.c
+++ b/drivers/input/touchscreen/apple_z2.c
@@ -22,6 +22,7 @@
#define APPLE_Z2_TOUCH_MOVED 4
#define APPLE_Z2_CMD_READ_INTERRUPT_DATA 0xEB
#define APPLE_Z2_REPLY_INTERRUPT_DATA 0xE1
+#define APPLE_Z2_MAX_PACKET 4000
#define APPLE_Z2_HBPP_CMD_BLOB 0x3001
#define APPLE_Z2_FW_MAGIC 0x5746325A
#define LOAD_COMMAND_INIT_PAYLOAD 0
@@ -147,6 +148,8 @@ static int apple_z2_read_packet(struct apple_z2 *z2)
return 0;
pkt_len = (get_unaligned_le16(z2->rx_buf + 1) + 8) & 0xfffffffc;
+ if (pkt_len > APPLE_Z2_MAX_PACKET)
+ return -EMSGSIZE;
error = spi_read(z2->spidev, z2->rx_buf, pkt_len);
if (error)
@@ -363,7 +366,7 @@ static int apple_z2_probe(struct spi_device *spi)
if (!z2->tx_buf)
return -ENOMEM;
/* 4096 will end up being rounded up to 8192 due to devres header */
- z2->rx_buf = devm_kzalloc(dev, 4000, GFP_KERNEL);
+ z2->rx_buf = devm_kzalloc(dev, APPLE_Z2_MAX_PACKET, GFP_KERNEL);
if (!z2->rx_buf)
return -ENOMEM;
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260613-b4-disp-b1926f1a-caad8f942af9
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply related
* Re: [PATCH] Input: apple_z2 - bound the device-reported packet length
From: sashiko-bot @ 2026-06-13 21:53 UTC (permalink / raw)
To: Bryam Vargas via B4 Relay; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260613-b4-disp-b1926f1a-v1-1-3a277b7c0cfa@proton.me>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing bounds check on the number of fingers (`nfingers`) reported by the device in `apple_z2_parse_touches` leads to a kernel heap out-of-bounds read and potential information leak.
- [High] Concurrent execution of `apple_z2_read_packet()` from the probe/resume thread and the IRQ handler causes a data race and shared SPI buffer corruption.
- [Medium] If `apple_z2_boot()` times out waiting for the boot IRQ, it returns directly without disabling the IRQ or reverting the hardware reset state, leading to state inconsistencies.
- [Low] The input device is registered and exposed to userspace before the hardware is initialized and firmware is uploaded, violating input subsystem readiness rules.
--
commit 0ef574cc6b6df1efb30f7e523d3cf505e551ae3c
Author: Bryam Vargas <hexlabsecurity@proton.me>
Input: apple_z2 - bound the device-reported packet length
apple_z2_read_packet() takes a 16-bit length from the touch controller's
interrupt-data reply and uses it as the size of the second SPI transfer into
the fixed-size rx_buf. This bounds the device-reported packet length to reject
any packet whose length exceeds the receive buffer.
> diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
> index 271ababf0ad55..ff9ff97be185a 100644
> --- a/drivers/input/touchscreen/apple_z2.c
> +++ b/drivers/input/touchscreen/apple_z2.c
[ ... ]
> @@ -147,6 +148,8 @@ static int apple_z2_read_packet(struct apple_z2 *z2)
> return 0;
>
> pkt_len = (get_unaligned_le16(z2->rx_buf + 1) + 8) & 0xfffffffc;
> + if (pkt_len > APPLE_Z2_MAX_PACKET)
> + return -EMSGSIZE;
>
> error = spi_read(z2->spidev, z2->rx_buf, pkt_len);
> if (error)
[Severity: High]
This isn't a bug introduced by this patch, but does apple_z2_parse_touches()
lack a bounds check on the number of fingers?
It appears that apple_z2_parse_touches() reads the unvalidated nfingers count
from msg[APPLE_Z2_NUM_FINGERS_OFFSET] and blindly iterates over it:
drivers/input/touchscreen/apple_z2.c:apple_z2_parse_touches() {
...
nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
for (i = 0; i < nfingers; i++) {
...
}
Could a spoofed device set nfingers to 255, causing the loop to read
out-of-bounds past the rx_buf allocation, and potentially leading to an
information leak when those bytes are parsed as touch coordinates?
[Severity: High]
This is a pre-existing issue, but is there a data race when
apple_z2_read_packet() is executed concurrently?
In apple_z2_boot(), the device interrupt is unmasked via enable_irq() before
firmware is uploaded. Then in apple_z2_upload_firmware(), the driver sets
z2->booted = true and manually calls apple_z2_read_packet().
If the device asserts the interrupt concurrently, the threaded IRQ handler
apple_z2_irq() will wake up, observe z2->booted == true, and also invoke
apple_z2_read_packet().
Since there is no lock protecting apple_z2_read_packet(), could this
concurrently overwrite z2->tx_buf, mutate z2->index_parity, and corrupt the
shared SPI buffers?
[ ... ]
> @@ -363,7 +366,7 @@ static int apple_z2_probe(struct spi_device *spi)
> if (!z2->tx_buf)
> return -ENOMEM;
> /* 4096 will end up being rounded up to 8192 due to devres header */
> - z2->rx_buf = devm_kzalloc(dev, 4000, GFP_KERNEL);
> + z2->rx_buf = devm_kzalloc(dev, APPLE_Z2_MAX_PACKET, GFP_KERNEL);
> if (!z2->rx_buf)
> return -ENOMEM;
[Severity: Medium]
This is a pre-existing issue, but does apple_z2_boot() miss cleanup when
wait_for_completion_timeout() fails?
In apple_z2_boot(), the IRQ enable depth is incremented and the device is
taken out of reset. If it times out waiting for the boot_irq completion, it
directly returns -ETIMEDOUT:
drivers/input/touchscreen/apple_z2.c:apple_z2_boot() {
...
if (!wait_for_completion_timeout(&z2->boot_irq, msecs_to_jiffies(20)))
return -ETIMEDOUT;
...
}
If this happens during apple_z2_resume(), devm teardown does not occur. Does
this leave the IRQ permanently enabled and the reset GPIO active, leading to
state inconsistency?
[Severity: Low]
This is a pre-existing issue, but is the input device registered before the
hardware is fully initialized?
In apple_z2_probe(), input_register_device() is called before apple_z2_boot()
has completed:
drivers/input/touchscreen/apple_z2.c:apple_z2_probe() {
...
error = input_register_device(z2->input_dev);
if (error)
return dev_err_probe(dev, error, "unable to register input device\n");
/* Wait for device reset to finish */
usleep_range(5000, 10000);
error = apple_z2_boot(z2);
...
}
If apple_z2_boot() fails, the momentarily registered device is removed. Does
this expose an uninitialized device to userspace prematurely?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613-b4-disp-b1926f1a-v1-1-3a277b7c0cfa@proton.me?part=1
^ permalink raw reply
* Re: [PATCH] Input: apple_z2 - bound the device-reported packet length
From: Sasha Finkelstein @ 2026-06-13 21:57 UTC (permalink / raw)
To: hexlabsecurity
Cc: Dmitry Torokhov, linux-arm-kernel, Neal Gompa, linux-input, asahi,
linux-kernel, Sven Peter, Janne Grunau
In-Reply-To: <20260613-b4-disp-b1926f1a-v1-1-3a277b7c0cfa@proton.me>
> On Jun 13, 2026, at 23:42, Bryam Vargas via B4 Relay <devnull+hexlabsecurity.proton.me@kernel.org> wrote:
>
> From: Bryam Vargas <hexlabsecurity@proton.me>
>
> [...]
> Reject any packet whose length exceeds the receive buffer.
>
Reviewed-by: Sasha Finkelstein <k@chaosmail.tech>
Although, if you reject the packet, the datastream will be desynced and
the device should be reset before it can operate again.
^ permalink raw reply
* [PATCH] Input: synaptics-rmi4 - correct CONFIG_RMI4_F34 macro name in comment
From: Ethan Nelson-Moore @ 2026-06-13 22:21 UTC (permalink / raw)
To: Dmitry Torokhov, Marge Yang, André Apitzsch,
Ethan Nelson-Moore, linux-input
A comment in the synaptics-rmi4 driver incorrectly refers to
CONFIG_RMI4_F34 instead of CONFIG_RMI4_F34. Correct it.
Discovered while searching for CONFIG_* symbols referenced in code but
not defined in any Kconfig file.
Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
---
drivers/input/rmi4/rmi_driver.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h
index e84495caab15..7d0ae80dc8ea 100644
--- a/drivers/input/rmi4/rmi_driver.h
+++ b/drivers/input/rmi4/rmi_driver.h
@@ -127,7 +127,7 @@ static inline int rmi_f34_create_sysfs(struct rmi_device *rmi_dev)
static inline void rmi_f34_remove_sysfs(struct rmi_device *rmi_dev)
{
}
-#endif /* CONFIG_RMI_F34 */
+#endif /* CONFIG_RMI4_F34 */
extern struct rmi_function_handler rmi_f01_handler;
extern struct rmi_function_handler rmi_f03_handler;
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] Input: synaptics-rmi4 - correct CONFIG_RMI4_F34 macro name in comment
From: sashiko-bot @ 2026-06-13 22:23 UTC (permalink / raw)
To: Ethan Nelson-Moore; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260613222105.101418-1-enelsonmoore@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The commit message contains a typo and repeats the same macro name twice when describing the change. It states that the comment 'incorrectly refers to CONFIG_RMI4_F34 instead of CONFIG_RMI4_F34', whereas the actual code diff shows the old macro name was 'CONFIG_RMI_F34'.
--
commit 0b9599ce581aac6de8ca3435e1928df9a8121100
Author: Ethan Nelson-Moore <enelsonmoore@gmail.com>
Input: synaptics-rmi4 - correct CONFIG_RMI4_F34 macro name in comment
This commit fixes a typo in a preprocessor endif comment in the
synaptics-rmi4 driver, changing CONFIG_RMI_F34 to CONFIG_RMI4_F34.
> A comment in the synaptics-rmi4 driver incorrectly refers to
> CONFIG_RMI4_F34 instead of CONFIG_RMI4_F34. Correct it.
[Severity: Low]
This isn't a bug, but there is a typo in the commit message description.
It states the comment refers to CONFIG_RMI4_F34 instead of CONFIG_RMI4_F34,
repeating the same macro name twice. The actual code diff shows the old
macro name was CONFIG_RMI_F34.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613222105.101418-1-enelsonmoore@gmail.com?part=1
^ permalink raw reply
* [PATCH] Input: apple_z2 - bound the device-reported finger count
From: Bryam Vargas via B4 Relay @ 2026-06-13 23:57 UTC (permalink / raw)
To: Sasha Finkelstein, Dmitry Torokhov
Cc: linux-arm-kernel, linux-kernel, Janne Grunau, asahi, linux-input,
Neal Gompa, Sven Peter
From: Bryam Vargas <hexlabsecurity@proton.me>
apple_z2_parse_touches() takes the finger count from the touch
controller's report and loops over that many fixed-size finger records
without ever checking the count against the length of the report:
nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
for (i = 0; i < nfingers; i++)
/* read fingers[i] ... */
msg points into the fixed 4000-byte z2->rx_buf and nfingers is a single
device-supplied byte, so it can be as large as 255. A malicious,
malfunctioning or counterfeit controller (or an interposer on the SPI
bus) can report a large finger count in a short packet, making the loop
read up to 255 * sizeof(struct apple_z2_finger) bytes starting 24 bytes
into msg -- far past the 4000-byte buffer. This is a controller-driven
heap out-of-bounds read, and the finger fields that are read (position,
pressure, touch and tool dimensions) are forwarded to userspace as input
events, leaking adjacent kernel memory.
Bound the device-reported count to the number of finger records the
report actually carries.
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/all/20260613215358.329921F000E9@smtp.kernel.org/
Fixes: 471a92f8a21a ("Input: apple_z2 - add a driver for Apple Z2 touchscreens")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Reachable on every touch interrupt once the controller is booted
(apple_z2_irq -> apple_z2_read_packet -> apple_z2_parse_touches).
nfingers is a different device field from the packet length handled by
the in-flight "Input: apple_z2 - bound the device-reported packet length"
patch, so the two are orthogonal: that one bounds the spi_read() length
(rx_buf[1..2]); this one bounds the per-report finger count (msg[16]).
The early-return is tightened from NUM_FINGERS_OFFSET (16) to
FINGERS_OFFSET (24) so the subtraction below cannot underflow; since
msg_len == pkt_len - 5 and pkt_len is rounded to a multiple of four, the
only reachable lengths the tighter guard now drops are 19 and 23, both of
which are too short to hold even the finger-array header.
Verified with a faithful in-kernel KASAN litmus (the verbatim 4000-byte
buffer, the struct apple_z2_finger layout and the parse loop),
CONFIG_KASAN=y on x86_64:
Arm A, nfingers = 255 in a short packet (msg_len 19):
BUG: KASAN: slab-out-of-bounds in apple_z2_parse_touches
Read of size 2 ... 1 bytes to the right of allocated 4000-byte region
... cache kmalloc-4k of size 4096
Arm B, with this patch (count clamped to what the packet holds): clean
Arm C, benign device (3 fingers): clean
AddressSanitizer (x86_64 and i386): heap-buffer-overflow READ, both ABIs.
Reproducer and full logs available on request.
---
drivers/input/touchscreen/apple_z2.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
index 271ababf0ad5..61f353553e7c 100644
--- a/drivers/input/touchscreen/apple_z2.c
+++ b/drivers/input/touchscreen/apple_z2.c
@@ -88,10 +88,13 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
int slot_valid;
struct apple_z2_finger *fingers;
- if (msg_len <= APPLE_Z2_NUM_FINGERS_OFFSET)
+ if (msg_len <= APPLE_Z2_FINGERS_OFFSET)
return;
nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
+ /* a malicious controller can claim more fingers than the packet holds */
+ nfingers = min_t(int, nfingers,
+ (msg_len - APPLE_Z2_FINGERS_OFFSET) / sizeof(*fingers));
for (i = 0; i < nfingers; i++) {
slot = input_mt_get_slot_by_key(z2->input_dev, fingers[i].finger);
if (slot < 0) {
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260613-b4-disp-f0148c89-dfafdfb84b3f
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply related
* Re: [PATCH] Input: apple_z2 - bound the device-reported finger count
From: sashiko-bot @ 2026-06-14 0:07 UTC (permalink / raw)
To: Bryam Vargas via B4 Relay; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260613-b4-disp-f0148c89-v1-1-868a48b2a187@proton.me>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] The patch incorrectly discards valid zero-finger reports, resulting in stuck touches.
Pre-existing issues:
- [Critical] Unbounded device-reported packet length leads to a massive heap buffer overflow during `spi_read()` and completely bypasses the patch's intended fix for out-of-bounds reads.
--
commit 3a1e80d03b931df0a0db172ea0ca1a4ea3349e47
Author: Bryam Vargas <hexlabsecurity@proton.me>
Input: apple_z2 - bound the device-reported finger count
This commit attempts to fix a heap out-of-bounds read by bounding the number
of reported fingers against the message length.
Link: https://lore.kernel.org/all/20260613215358.329921F000E9@smtp.kernel.org/
> diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
> index 271ababf0ad55..61f353553e7c5 100644
> --- a/drivers/input/touchscreen/apple_z2.c
> +++ b/drivers/input/touchscreen/apple_z2.c
> @@ -88,10 +88,13 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
> int slot_valid;
> struct apple_z2_finger *fingers;
>
> - if (msg_len <= APPLE_Z2_NUM_FINGERS_OFFSET)
> + if (msg_len <= APPLE_Z2_FINGERS_OFFSET)
> return;
[Severity: High]
Will this new check discard valid zero-finger reports?
With SPI length rounding, an empty report indicating all fingers have been
lifted usually lacks the finger array, meaning its payload size can fall
between 17 and 24 bytes.
If a 0-finger packet is dropped here, the input subsystem won't be notified
via input_mt_sync_frame() and input_sync(). Could this result in touches
remaining stuck on the screen?
> nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
> fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
> + /* a malicious controller can claim more fingers than the packet holds */
> + nfingers = min_t(int, nfingers,
> + (msg_len - APPLE_Z2_FINGERS_OFFSET) / sizeof(*fingers));
[Severity: Critical]
This isn't a bug introduced by this patch, but does deriving msg_len from
the untrusted packet length bypass this new boundary check entirely?
In apple_z2_read_packet(), pkt_len is derived directly from an
unauthenticated SPI read:
apple_z2_read_packet() {
...
pkt_len = (get_unaligned_le16(z2->rx_buf + 1) + 8) & 0xfffffffc;
error = spi_read(z2->spidev, z2->rx_buf, pkt_len);
...
}
Since z2->rx_buf is a fixed 4000-byte allocation, a malicious controller
could send a large length field (up to 65540) to cause a heap buffer
overflow during spi_read().
Furthermore, because msg_len is derived directly from this untrusted
pkt_len, an artificially large pkt_len bypasses the new clamping logic
above. This allows nfingers to still evaluate to 255.
Does this render the out-of-bounds read fix ineffective against malicious
controllers?
> for (i = 0; i < nfingers; i++) {
> slot = input_mt_get_slot_by_key(z2->input_dev, fingers[i].finger);
> if (slot < 0) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613-b4-disp-f0148c89-v1-1-868a48b2a187@proton.me?part=1
^ permalink raw reply
* [PATCH] Input: touchwin - reset the packet index on every complete packet
From: Bryam Vargas via B4 Relay @ 2026-06-14 1:07 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, Rick Koch, linux-kernel
From: Bryam Vargas <hexlabsecurity@proton.me>
tw_interrupt() accumulates each non-zero serial byte into a fixed
three-byte buffer with a running index that is only reset once a full
packet has been received *and* the device's two Y bytes agree:
tw->data[tw->idx++] = data;
if (tw->idx == TW_LENGTH && tw->data[1] == tw->data[2]) {
...
tw->idx = 0;
}
The reset is gated on tw->data[1] == tw->data[2], a value the device
controls. A malicious, malfunctioning or counterfeit Touchwindow
peripheral can stream non-zero bytes whose 2nd and 3rd bytes differ: the
index reaches TW_LENGTH without the equality holding, is never reset, and
keeps growing, so tw->data[tw->idx++] walks off the end of the three-byte
array and the rest of the heap-allocated struct tw, one attacker-chosen
byte at a time -- an unbounded, device-driven heap out-of-bounds write.
Reset the index on every completed packet and report an event only when
the two Y bytes match, like the other serio touchscreen drivers do.
Fixes: 11ea3173d5f2 ("Input: add driver for Touchwin serial touchscreens")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Reachable on every serial byte once the device is bound as SERIO_TOUCHWIN
(inputattach --touchwin): serport_ldisc_receive -> serio_interrupt ->
tw_interrupt, with no length validation on the path. The peripheral need
only avoid zero bytes (any data != 0) and send a 2nd byte different from
the 3rd to defeat the reset.
struct tw is a 64-byte kmalloc-64 object; the unbounded store crosses the
allocation once the running index reaches 40.
Verified with a faithful in-kernel KASAN litmus (the verbatim struct tw
and the tw_interrupt accumulation), CONFIG_KASAN=y on x86_64:
Arm A, 64 non-zero bytes with byte[1] != byte[2]:
BUG: KASAN: slab-out-of-bounds in ... Write of size 1
located 0 bytes to the right of the allocated 64-byte region
... cache kmalloc-64 of size 64
Arm B, with this patch (index reset every packet): clean
Arm C, benign device (byte[1] == byte[2]): clean
AddressSanitizer (x86_64 and i386): heap-buffer-overflow WRITE, both ABIs.
Reproducer and full logs available on request.
---
drivers/input/touchscreen/touchwin.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/drivers/input/touchscreen/touchwin.c b/drivers/input/touchscreen/touchwin.c
index 099fd88e65d8..3ed33378dfcd 100644
--- a/drivers/input/touchscreen/touchwin.c
+++ b/drivers/input/touchscreen/touchwin.c
@@ -63,12 +63,15 @@ static irqreturn_t tw_interrupt(struct serio *serio,
if (data) { /* touch */
tw->touched = 1;
tw->data[tw->idx++] = data;
- /* verify length and that the two Y's are the same */
- if (tw->idx == TW_LENGTH && tw->data[1] == tw->data[2]) {
- input_report_abs(dev, ABS_X, tw->data[0]);
- input_report_abs(dev, ABS_Y, tw->data[1]);
- input_report_key(dev, BTN_TOUCH, 1);
- input_sync(dev);
+ /* a full packet ends the accumulation, valid or not */
+ if (tw->idx == TW_LENGTH) {
+ /* report only if the two Y's are the same */
+ if (tw->data[1] == tw->data[2]) {
+ input_report_abs(dev, ABS_X, tw->data[0]);
+ input_report_abs(dev, ABS_Y, tw->data[1]);
+ input_report_key(dev, BTN_TOUCH, 1);
+ input_sync(dev);
+ }
tw->idx = 0;
}
} else if (tw->touched) { /* untouch */
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260613-b4-disp-69921bfd-051063dda4b7
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply related
* [PATCH v2] Input: apple_z2 - bound the device-reported finger count
From: Bryam Vargas via B4 Relay @ 2026-06-14 1:22 UTC (permalink / raw)
To: Sasha Finkelstein, Dmitry Torokhov
Cc: linux-kernel, Janne Grunau, linux-arm-kernel, linux-input,
Sven Peter, asahi, Neal Gompa
From: Bryam Vargas <hexlabsecurity@proton.me>
apple_z2_parse_touches() takes the finger count from the touch
controller's report and loops over that many fixed-size finger records
without ever checking the count against the length of the report:
nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
for (i = 0; i < nfingers; i++)
/* read fingers[i] ... */
msg points into the fixed 4000-byte z2->rx_buf and nfingers is a single
device-supplied byte, so it can be as large as 255. A malicious,
malfunctioning or counterfeit controller (or an interposer on the SPI
bus) can report a large finger count in a short packet, making the loop
read up to 255 * sizeof(struct apple_z2_finger) bytes starting 24 bytes
into msg -- far past the 4000-byte buffer. This is a controller-driven
heap out-of-bounds read, and the finger fields that are read (position,
pressure, touch and tool dimensions) are forwarded to userspace as input
events, leaking adjacent kernel memory.
Bound the device-reported count to the number of finger records the
report actually carries.
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/all/20260613215358.329921F000E9@smtp.kernel.org/
Fixes: 471a92f8a21a ("Input: apple_z2 - add a driver for Apple Z2 touchscreens")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Changes since v1 [1]:
- Keep the early-return at NUM_FINGERS_OFFSET instead of moving it to
FINGERS_OFFSET, so a short zero-finger ("all lifted") report still
reaches input_mt_sync_frame()/input_sync() and does not leave touches
stuck on the screen (caught by the sashiko-bot review of v1 [2]). A
packet too short to hold even one finger record clamps nfingers to 0
instead of being dropped.
[1] https://lore.kernel.org/all/20260613-b4-disp-f0148c89-v1-1-868a48b2a187@proton.me/
[2] https://lore.kernel.org/all/20260614000725.6B8D11F000E9@smtp.kernel.org/
Reachable on every touch interrupt once the controller is booted
(apple_z2_irq -> apple_z2_read_packet -> apple_z2_parse_touches).
nfingers is bounded here by the message length; the message length is in
turn bounded by the companion "Input: apple_z2 - bound the device-reported
packet length" change (in flight), which caps the device-reported pkt_len
to the 4000-byte receive buffer. The two together close the device-driven
out-of-bounds accesses in apple_z2_parse_touches() / apple_z2_read_packet().
Verified with a faithful in-kernel KASAN litmus (the verbatim 4000-byte
buffer, the struct apple_z2_finger layout and the parse loop),
CONFIG_KASAN=y on x86_64:
Arm A, nfingers = 255 in a short packet (msg_len 19):
BUG: KASAN: slab-out-of-bounds in apple_z2_parse_touches
Read of size 2 ... 1 bytes to the right of allocated 4000-byte region
... cache kmalloc-4k of size 4096
Arm B, with this patch: a zero-finger report (msg_len 19) reaches the
sync; a 255-finger claim is clamped to what the packet holds; clean.
Arm C, benign device (3 fingers): clean
AddressSanitizer (x86_64 and i386): heap-buffer-overflow READ, both ABIs.
Reproducer and full logs available on request.
---
drivers/input/touchscreen/apple_z2.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
index 271ababf0ad5..39ade83ef0de 100644
--- a/drivers/input/touchscreen/apple_z2.c
+++ b/drivers/input/touchscreen/apple_z2.c
@@ -92,6 +92,12 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
return;
nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
+ /* a malicious controller can claim more fingers than the packet holds */
+ if (msg_len < APPLE_Z2_FINGERS_OFFSET)
+ nfingers = 0;
+ else
+ nfingers = min_t(int, nfingers,
+ (msg_len - APPLE_Z2_FINGERS_OFFSET) / sizeof(*fingers));
for (i = 0; i < nfingers; i++) {
slot = input_mt_get_slot_by_key(z2->input_dev, fingers[i].finger);
if (slot < 0) {
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260613-b4-disp-4ebcbd68-ed8a28672ccc
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply related
* Re: [PATCH v2] Input: apple_z2 - bound the device-reported finger count
From: sashiko-bot @ 2026-06-14 1:34 UTC (permalink / raw)
To: Bryam Vargas via B4 Relay; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260613-b4-disp-4ebcbd68-v2-1-0161acfbd688@proton.me>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Unvalidated device-reported packet length leads to a severe heap buffer overflow in `spi_read`. The patch fails to address this root cause.
- [High] Using the `__free(kfree)` attribute on a variable that can hold an `ERR_PTR` causes a kernel panic during error handling.
- [High] Concurrent execution of `apple_z2_read_packet` causes data races on driver state, SPI buffers, and lockless input-mt helper states.
--
commit 9a7b4f864d6502dba7c8e2bed5aa7ed3358d9e4a
Author: Bryam Vargas <hexlabsecurity@proton.me>
Input: apple_z2 - bound the device-reported finger count
The commit adds bounds checking for the device-reported finger count against
the SPI packet length to prevent an out-of-bounds read in
apple_z2_parse_touches().
Closes: https://lore.kernel.org/all/20260613215358.329921F000E9@smtp.kernel.org/
> diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
> index 271ababf0ad55..39ade83ef0de7 100644
> --- a/drivers/input/touchscreen/apple_z2.c
> +++ b/drivers/input/touchscreen/apple_z2.c
> @@ -92,6 +92,12 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
> return;
> nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
> fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
> + /* a malicious controller can claim more fingers than the packet holds */
> + if (msg_len < APPLE_Z2_FINGERS_OFFSET)
> + nfingers = 0;
> + else
> + nfingers = min_t(int, nfingers,
> + (msg_len - APPLE_Z2_FINGERS_OFFSET) / sizeof(*fingers));
> for (i = 0; i < nfingers; i++) {
> slot = input_mt_get_slot_by_key(z2->input_dev, fingers[i].finger);
> if (slot < 0) {
[Severity: High]
This is a pre-existing issue, but does this code overflow the heap allocation
for rx_buf?
In apple_z2_read_packet():
pkt_len = (get_unaligned_le16(z2->rx_buf + 1) + 8) & 0xfffffffc;
error = spi_read(z2->spidev, z2->rx_buf, pkt_len);
Because rx_buf is allocated with a fixed size of 4000 bytes, couldn't a
malfunctioning device supply a large packet length and cause spi_read to
write past the end of the allocation?
[Severity: High]
This isn't a bug introduced by this patch, but will this cause a kernel panic
on error?
In apple_z2_upload_firmware():
const u8 *data __free(kfree) =
apple_z2_build_cal_blob(z2, address, &size);
if (IS_ERR(data))
return PTR_ERR(data);
Since kfree does not check for IS_ERR, doesn't returning here cause the
compiler-inserted kfree(data) to attempt freeing an invalid virtual address?
[Severity: High]
This is a pre-existing issue, but can this sequence lead to data races?
If a hardware interrupt arrives immediately after setting z2->booted to true,
could the threaded IRQ handler execute apple_z2_read_packet concurrently?
CPU1 in apple_z2_upload_firmware():
z2->booted = true;
apple_z2_read_packet(z2);
CPU2 in apple_z2_irq():
...
else
apple_z2_read_packet(z2);
Since apple_z2_read_packet modifies shared state without a lock:
apple_z2_read_packet():
len_cmd->counter = z2->index_parity + 1;
...
z2->index_parity = !z2->index_parity;
wouldn't this concurrent execution corrupt z2->tx_buf, z2->rx_buf, and
z2->index_parity?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613-b4-disp-4ebcbd68-v2-1-0161acfbd688@proton.me?part=1
^ permalink raw reply
* [PATCH] Input: iforce - bound the device-reported force-feedback effect index
From: Bryam Vargas via B4 Relay @ 2026-06-14 2:58 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel
From: Bryam Vargas <hexlabsecurity@proton.me>
iforce_process_packet() handles a status report (packet id 0x02) by
taking a force-feedback effect index straight from the device wire and
using it to address the per-effect state array:
i = data[1] & 0x7f;
if (data[1] & 0x80) {
if (!test_and_set_bit(FF_CORE_IS_PLAYED,
iforce->core_effects[i].flags))
...
} else if (test_and_clear_bit(FF_CORE_IS_PLAYED,
iforce->core_effects[i].flags)) {
...
}
The index is masked only with 0x7f, so it ranges 0..127, but
core_effects[] holds only IFORCE_EFFECTS_MAX (32) entries. For an index
of 32..127 the test_and_set_bit()/test_and_clear_bit() is an
out-of-bounds single-bit read-modify-write past the array. core_effects[]
is the second-to-last member of struct iforce, so the write lands in the
trailing members and beyond the embedding kzalloc()'d iforce_serio /
iforce_usb object.
data[1] is unvalidated device payload on both transports (the USB
interrupt endpoint and serio), and the status path is not gated on force
feedback being present, so a malicious or counterfeit device can set or
clear a bit at an attacker-chosen offset past the object.
Reject an out-of-range index instead of indexing with it. Bound against
the array dimension IFORCE_EFFECTS_MAX rather than dev->ff->max_effects so
the check guarantees memory safety regardless of how many effects the
device registered. A legitimate "effect started/stopped" status always
carries an index below IFORCE_EFFECTS_MAX, so well-formed devices are
unaffected; the neighbouring mark_core_as_ready() loop is already bounded
and is left untouched.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Reachable from a malicious, malfunctioning or counterfeit iforce
force-feedback device on either transport: a USB device matching the
iforce id table (iforce-usb.c forwards the interrupt-IN buffer as
iforce_process_packet(.., data_in[0], data_in + 1, ..)) or an RS232
iforce peripheral bound via inputattach (iforce-serio.c forwards data_in).
data[1] is never validated on the path, and the 0x02 status block runs
regardless of packet length or whether force feedback was created.
struct iforce embeds core_effects[32] as its second-to-last member and is
itself the first member of the kzalloc()'d iforce_serio / iforce_usb
object (~4.8 KB, kmalloc-8k). A status report with data[1] = 0x80 | idx
sets bit FF_CORE_IS_PLAYED in core_effects[idx].flags; for idx up to 127
that is about 13 KB past the end of the array.
Verified with a faithful in-kernel KASAN litmus (the verbatim struct
iforce / iforce_serio layout and the case-0x02 effect-index block),
Linux 7.1.0-rc5, CONFIG_KASAN=y, kasan.fault=panic, x86_64:
Arm A, status report effect index 57 (chosen to clear the kmalloc-8k
bucket; KASAN's kmalloc redzone already trips from index 32):
BUG: KASAN: slab-out-of-bounds in ... Write of size 8
located 3440 bytes to the right of the allocated 4840-byte region
... cache kmalloc-8k of size 8192
Kernel panic - not syncing: kasan.fault=panic set
Arm B, with this patch (index bounded): clean
Arm C, benign in-range index: clean
AddressSanitizer (x86_64 and i386): heap-buffer-overflow WRITE, both ABIs.
The "Write of size 8" is the test_and_set_bit() read-modify-write on the
flags long. Reproducer and full logs available on request.
---
drivers/input/joystick/iforce/iforce-packets.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c
index fd1cd731d781..ff266568d586 100644
--- a/drivers/input/joystick/iforce/iforce-packets.c
+++ b/drivers/input/joystick/iforce/iforce-packets.c
@@ -186,14 +186,18 @@ void iforce_process_packet(struct iforce *iforce,
/* Check if an effect was just started or stopped */
i = data[1] & 0x7f;
- if (data[1] & 0x80) {
- if (!test_and_set_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) {
- /* Report play event */
- input_report_ff_status(dev, i, FF_STATUS_PLAYING);
+ if (i < IFORCE_EFFECTS_MAX) {
+ unsigned long *flags = iforce->core_effects[i].flags;
+
+ if (data[1] & 0x80) {
+ if (!test_and_set_bit(FF_CORE_IS_PLAYED, flags)) {
+ /* Report play event */
+ input_report_ff_status(dev, i, FF_STATUS_PLAYING);
+ }
+ } else if (test_and_clear_bit(FF_CORE_IS_PLAYED, flags)) {
+ /* Report stop event */
+ input_report_ff_status(dev, i, FF_STATUS_STOPPED);
}
- } else if (test_and_clear_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) {
- /* Report stop event */
- input_report_ff_status(dev, i, FF_STATUS_STOPPED);
}
for (j = 3; j < len; j += 2)
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260613-b4-disp-4828d263-5f8b1a7c2346
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply related
* Re: [PATCH] Input: iforce - bound the device-reported force-feedback effect index
From: sashiko-bot @ 2026-06-14 3:10 UTC (permalink / raw)
To: Bryam Vargas via B4 Relay; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260613-b4-disp-4828d263-v1-1-02320e1a89dd@proton.me>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] A zero-length USB packet causes an integer underflow when calculating the packet length, leading to massive out-of-bounds reads and a kernel panic.
--
commit 03557dab27a995017f2c109d738ebd32fc4d550b
Author: Bryam Vargas <hexlabsecurity@proton.me>
Input: iforce - bound the device-reported force-feedback effect index
This commit bounds the force-feedback effect index reported by the device
to prevent an out-of-bounds read-modify-write past the core_effects[] array.
It fixes a memory corruption vulnerability caused by an unvalidated payload.
> diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c
> index fd1cd731d781a..ff266568d586c 100644
> --- a/drivers/input/joystick/iforce/iforce-packets.c
> +++ b/drivers/input/joystick/iforce/iforce-packets.c
> @@ -186,14 +186,18 @@ void iforce_process_packet(struct iforce *iforce,
>
> /* Check if an effect was just started or stopped */
> i = data[1] & 0x7f;
> - if (data[1] & 0x80) {
> - if (!test_and_set_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) {
> - /* Report play event */
> - input_report_ff_status(dev, i, FF_STATUS_PLAYING);
> + if (i < IFORCE_EFFECTS_MAX) {
> + unsigned long *flags = iforce->core_effects[i].flags;
> +
> + if (data[1] & 0x80) {
> + if (!test_and_set_bit(FF_CORE_IS_PLAYED, flags)) {
> + /* Report play event */
> + input_report_ff_status(dev, i, FF_STATUS_PLAYING);
> + }
> + } else if (test_and_clear_bit(FF_CORE_IS_PLAYED, flags)) {
> + /* Report stop event */
> + input_report_ff_status(dev, i, FF_STATUS_STOPPED);
> }
> - } else if (test_and_clear_bit(FF_CORE_IS_PLAYED, iforce->core_effects[i].flags)) {
> - /* Report stop event */
> - input_report_ff_status(dev, i, FF_STATUS_STOPPED);
> }
>
> for (j = 3; j < len; j += 2)
[Severity: Critical]
This is a pre-existing issue, but was not introduced by the patch under review.
Could a zero-length USB packet cause an integer underflow that triggers a
massive out-of-bounds read in this loop?
Looking at iforce_usb_irq() in drivers/input/joystick/iforce/iforce-usb.c:
iforce_process_packet(iforce, iforce_usb->data_in[0],
iforce_usb->data_in + 1, urb->actual_length - 1);
If urb->actual_length is 0, the subtraction underflows to a massive positive
unsigned value, which is passed as len.
If the first byte evaluates to a status report (packet ID 0x02), this loop
would execute against the underflowed len. Could this iterate billions of
times, repeatedly executing mark_core_as_ready() and causing uncontrolled
memory reads until an unmapped page is hit?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613-b4-disp-4828d263-v1-1-02320e1a89dd@proton.me?part=1
^ permalink raw reply
* [PATCH] Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer
From: Bryam Vargas via B4 Relay @ 2026-06-14 4:01 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input, linux-kernel, Guenter Roeck, Benjamin Tissoires
From: Bryam Vargas <hexlabsecurity@proton.me>
rmi_f54_work() reads a diagnostics report from the device into
f54->report_data, sizing the transfer with rmi_f54_get_report_size():
report_size = rmi_f54_get_report_size(f54);
...
for (i = 0; i < report_size; i += F54_REPORT_DATA_SIZE) {
int size = min(F54_REPORT_DATA_SIZE, report_size - i);
...
rmi_read_block(.., f54->report_data + i, size);
}
report_data is allocated once at probe from F54's own electrode counts
(array3_size(f54->num_tx_electrodes, f54->num_rx_electrodes, sizeof(u16))),
but rmi_f54_get_report_size() computes the size from
drv_data->num_*_electrodes when those are set, i.e. from the F55
function's electrode counts. Both counts come straight from device
queries (F54 and F55 each report up to 255 electrodes) and nothing
constrains the F55 counts to the F54 ones.
A malicious or malfunctioning RMI4 device that reports larger F55
electrode counts than its F54 counts makes report_size exceed the
allocation, so the read loop writes past report_data (and the V4L2
dequeue memcpy() then reads past it). On conforming hardware the F55
configured electrodes are a subset of the F54 physical electrodes, so
report_size never exceeds the buffer and well-behaved devices are
unaffected.
Record the allocation size and reject a report that does not fit,
mirroring the existing zero-size check.
Fixes: c762cc68b6a1 ("Input: synaptics-rmi4 - propagate correct number of rx and tx electrodes to F54")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
Reachable from a malicious or counterfeit RMI4 peripheral (HID/I2C/SPI)
that exposes both an F54 diagnostics function and an F55 sensor-tuning
function reporting more electrodes than F54; the report is filled when a
local client drives an F54 capture through the V4L2 touch device node.
Verified with an in-kernel KASAN litmus that allocates report_data from
the F54 dims and fills it from the F55-preferred dims (the verbatim
rmi_f54_get_report_size() preference and the rmi_f54_work() 32-byte read
loop), Linux 7.1.0-rc5, CONFIG_KASAN=y, kasan.fault=panic, x86_64:
Arm A, F54 8x8 (capacity 128) vs F55 8x24 (report_size 384):
BUG: KASAN: slab-out-of-bounds in ... Write of size 32
located 0 bytes to the right of the allocated 128-byte region
... cache kmalloc-128 of size 128
Kernel panic - not syncing: kasan.fault=panic set
Arm B, with this patch (oversize report rejected): clean
Arm C, conforming device (F55 dims == F54 dims): clean
AddressSanitizer (x86_64 and i386): heap-buffer-overflow WRITE of size 32,
0 bytes after the 128-byte region.
The same rmi_f54_get_report_size() value drives the V4L2 dequeue
memcpy(.., report_size) on the read side, so an oversize report also
over-reads report_data; rejecting it in rmi_f54_work() (report_size then
stays 0) closes both. Reproducer and full logs available on request.
This is independent of the in-flight "Input: rmi4 - release F54 queue on
video registration failure" change, which touches only the probe error
path (vb2_queue_release in rmi_f54_probe); it applies cleanly alongside it.
---
drivers/input/rmi4/rmi_f54.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c
index 61909e1a39e2..7a7508a4de85 100644
--- a/drivers/input/rmi4/rmi_f54.c
+++ b/drivers/input/rmi4/rmi_f54.c
@@ -104,6 +104,7 @@ struct f54_data {
enum rmi_f54_report_type report_type;
u8 *report_data;
+ size_t report_data_size;
int report_size;
bool is_busy;
@@ -548,6 +549,13 @@ static void rmi_f54_work(struct work_struct *work)
goto error; /* retry won't help */
}
+ if (report_size > f54->report_data_size) {
+ dev_err(&fn->dev, "Report size %d exceeds buffer size %zu\n",
+ report_size, f54->report_data_size);
+ error = -EINVAL;
+ goto error;
+ }
+
/*
* Need to check if command has completed.
* If not try again later.
@@ -678,8 +686,8 @@ static int rmi_f54_probe(struct rmi_function *fn)
rx = f54->num_rx_electrodes;
tx = f54->num_tx_electrodes;
- f54->report_data = devm_kzalloc(&fn->dev,
- array3_size(tx, rx, sizeof(u16)),
+ f54->report_data_size = array3_size(tx, rx, sizeof(u16));
+ f54->report_data = devm_kzalloc(&fn->dev, f54->report_data_size,
GFP_KERNEL);
if (f54->report_data == NULL)
return -ENOMEM;
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260613-b4-disp-6721686c-33a3bdacdf38
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply related
* Re: [PATCH] Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer
From: sashiko-bot @ 2026-06-14 4:14 UTC (permalink / raw)
To: Bryam Vargas via B4 Relay; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260613-b4-disp-6721686c-v1-1-4d5bb84ee520@proton.me>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Stale f54->report_size causes V4L2 buffer heap overflow.
- [High] Missing cancel_delayed_work_sync in rmi_f54_remove causes Use-After-Free.
- [High] Preexisting copy-paste typo in F55 properties parsing triggers the new oversize check, permanently breaking conforming devices.
- [High] Use-After-Free of devm-managed video_device on V4L2 file descriptor close.
- [Medium] V4L2 buffer queue silently returns uninitialized memory on hardware errors.
--
commit 08992eb2bae1821355b21e17559ef51ec82606cd
Author: Bryam Vargas <hexlabsecurity@proton.me>
Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer
This commit adds a bounds check to ensure the F54 diagnostic report size
does not exceed the allocated buffer. It rejects reports that are larger
than the allocation to prevent writing past the end of the report data
buffer.
> diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c
> index 61909e1a39e24..7a7508a4de85c 100644
> --- a/drivers/input/rmi4/rmi_f54.c
> +++ b/drivers/input/rmi4/rmi_f54.c
[ ... ]
> @@ -548,6 +549,13 @@ static void rmi_f54_work(struct work_struct *work)
> goto error; /* retry won't help */
> }
>
> + if (report_size > f54->report_data_size) {
[Severity: High]
This is a pre-existing issue, but does this new check permanently break
conforming hardware due to a copy-paste typo in the F55 parser?
Looking at rmi_f55_detect() in rmi_f55.c, it incorrectly assigns the
RX count to the TX count:
drv_data->num_rx_electrodes = f55->cfg_num_rx_electrodes;
drv_data->num_tx_electrodes = f55->cfg_num_rx_electrodes;
For hardware lacking F55_CAP_SENSOR_ASSIGN and having more RX electrodes
than TX electrodes, this inflates the perceived TX count. This inflated count
propagates to rmi_f54_get_report_size(), yielding a larger value that will
unconditionally fail this new bounds check.
> + dev_err(&fn->dev, "Report size %d exceeds buffer size %zu\n",
> + report_size, f54->report_data_size);
> + error = -EINVAL;
> + goto error;
[Severity: Critical]
This isn't a bug introduced by this patch, but does taking this goto error
path bypass the zeroing of f54->report_size and cause a V4L2 buffer heap
overflow?
In rmi_f54_work(), error paths jump to the error: label, bypassing the
abort: label where f54->report_size is normally zeroed out. This leaves
f54->report_size containing its previous successful payload size.
If a user then alters the V4L2 format to a smaller size, and an error occurs
(like this bounds check failing), rmi_f54_buffer_queue() will copy the
stale, larger payload size into the shrunken V4L2 buffer:
memcpy(ptr, f54->report_data, f54->report_size);
[Severity: Medium]
This is also a pre-existing issue, but does this error path cause the V4L2
buffer queue to silently return uninitialized memory?
When rmi_f54_work() triggers this bounds check and jumps to error, the
buffer is not updated. However, rmi_f54_buffer_queue() ignores the failure
and marks the buffer as fully populated:
vb2_set_plane_payload(vb, 0, rmi_f54_get_report_size(f54));
state = VB2_BUF_STATE_DONE;
This completes the buffer without indicating an error to userspace.
> + }
> +
> /*
> * Need to check if command has completed.
[ ... ]
> @@ -678,8 +686,8 @@ static int rmi_f54_probe(struct rmi_function *fn)
>
> rx = f54->num_rx_electrodes;
> tx = f54->num_tx_electrodes;
> - f54->report_data = devm_kzalloc(&fn->dev,
> - array3_size(tx, rx, sizeof(u16)),
> + f54->report_data_size = array3_size(tx, rx, sizeof(u16));
> + f54->report_data = devm_kzalloc(&fn->dev, f54->report_data_size,
> GFP_KERNEL);
[Severity: High]
This is a pre-existing issue, but does allocating the f54_data struct
(which embeds struct video_device vdev) with devm_kzalloc lead to a
use-after-free when the V4L2 file descriptor is closed?
In rmi_f54_probe(), f54 is allocated using devm_kzalloc(). When the driver
unbinds, devm automatically frees f54_data.
If a userspace application still holds /dev/videoX open, the V4L2 core
retains a reference to the freed vdev. Subsequent ioctl calls or the final
closing of the file descriptor will access the freed vdev structure.
[Severity: High]
This isn't a bug introduced by this patch, but does the lack of a timer
cancellation in rmi_f54_remove() cause a use-after-free?
While rmi_f54_probe() correctly uses cancel_delayed_work_sync(&f54->work)
in its error path, the teardown path in rmi_f54_remove() just destroys
the workqueue:
rmi_f54_remove() {
...
video_unregister_device(&f54->vdev);
v4l2_device_unregister(&f54->v4l2);
destroy_workqueue(f54->workqueue);
...
}
This does not cancel pending delayed_work timers. If the driver is unbound
while a V4L2 stream is active, the timer will fire after devm has freed
the f54 structure and attempt to dereference f54->workqueue.
> if (f54->report_data == NULL)
> return -ENOMEM;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260613-b4-disp-6721686c-v1-1-4d5bb84ee520@proton.me?part=1
^ permalink raw reply
* [PATCH 0/2] (no cover subject)
From: Bryam Vargas via B4 Relay @ 2026-06-14 5:36 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-kernel, Hans de Goede, Vincent Huang, linux-input,
Andrew Duggan
From be2c4843f6145374f28edd25cef43c9373542874 Mon Sep 17 00:00:00 2001
Message-ID: <cover.1781415009.git.hexlabsecurity@proton.me>
From: Bryam Vargas <hexlabsecurity@proton.me>
Date: Sun, 14 Jun 2026 00:30:09 -0500
Subject: [PATCH 0/2] Input: synaptics-rmi4 - fix out-of-bounds keymap access on large GPIO counts
The RMI4 F3A and F30 function handlers size their GPIO/LED key map to
min(device_gpio_count, TRACKSTICK_RANGE_END) == at most 6 entries, but
then consume that map with the full, unclamped device-reported count in
two places: the attention-interrupt report loop, and input->keycodemax
(while input->keycode points at the small allocation).
A device that reports a GPIO/LED count greater than 6 therefore yields a
key map of at most 12 bytes that is indexed up to count-1:
- rmi_f3a_attention() / rmi_f30_attention() read gpio[led]_key_map[i]
out of bounds on every attention interrupt (device-triggered), and
- the input core's default keymap ioctls bound the index only against
keycodemax, so EVIOCGKEYCODE reads adjacent slab memory back to user
space and EVIOCSKEYCODE writes a caller-controlled u16 past the
allocation -- a controlled out-of-bounds heap write reachable by any
process that can open the evdev node.
Both handlers have the same shape; F3A was copied from F30. Patch 1 fixes
F3A, patch 2 fixes F30. The one-line fix in each sizes the key map to the
full device count; the mapping loop is unchanged (it still populates only
the first min(count, 6) entries, the rest stay KEY_RESERVED and are
skipped on report), and keycodemax now matches the allocation, closing
the interrupt and both ioctl paths.
Reproduced with KASAN on a faithful in-kernel model of each handler
(kmalloc-16 bucket, 12-byte request, reported count = 127):
# without the patch -- attention read path
BUG: KASAN: slab-out-of-bounds in rmi_f3a_attention
Read of size 2 ... 0 bytes to the right of allocated 12-byte region
... cache kmalloc-16
# without the patch -- EVIOCSKEYCODE write path
BUG: KASAN: slab-out-of-bounds ...
Write of size 2 ... 0 bytes to the right of allocated 12-byte region
# with the patch (key map sized to the full count): clean
# benign device (reported count <= 6): clean
The full read and write splats and a 32/64-bit ASan model of the same
geometry are available on request.
Bryam Vargas (2):
Input: synaptics-rmi4 - bound the F3A keymap to the GPIO count
Input: synaptics-rmi4 - bound the F30 keymap to the GPIO/LED count
drivers/input/rmi4/rmi_f30.c | 2 +-
drivers/input/rmi4/rmi_f3a.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
---
Bryam Vargas (2):
Input: synaptics-rmi4 - bound the F3A keymap to the GPIO count
Input: synaptics-rmi4 - bound the F30 keymap to the GPIO/LED count
drivers/input/rmi4/rmi_f30.c | 2 +-
drivers/input/rmi4/rmi_f3a.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
---
base-commit: 8e65320d91cdc3b241d4b94855c88459b91abf66
change-id: 20260614-b4-disp-818d6bda-8df90972ace6
Best regards,
--
Bryam Vargas <hexlabsecurity@proton.me>
^ permalink raw reply
* [PATCH 2/2] Input: synaptics-rmi4 - bound the F30 keymap to the GPIO/LED count
From: Bryam Vargas via B4 Relay @ 2026-06-14 5:36 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-kernel, Hans de Goede, Vincent Huang, linux-input,
Andrew Duggan
In-Reply-To: <20260614-b4-disp-818d6bda-v1-0-cf39a3615085@proton.me>
From: Bryam Vargas <hexlabsecurity@proton.me>
rmi_f30_map_gpios() allocates gpioled_key_map with
min(gpioled_count, TRACKSTICK_RANGE_END) == at most 6 entries, but
rmi_f30_attention() iterates the full f30->gpioled_count (device query
register, range 0..31) and dereferences gpioled_key_map[i], and
input->keycodemax is set to the full gpioled_count while input->keycode
points at the 6-entry allocation.
A device that reports gpioled_count > 6 with GPIO support enabled
therefore causes an out-of-bounds read on the attention interrupt and
out-of-bounds read/write through the EVIOCGKEYCODE/EVIOCSKEYCODE ioctls,
which bound the index only against keycodemax. This is the same defect
as the F3A handler, which was copied from F30.
Size the keymap for the full gpioled_count; the mapping loop still
assigns only the first min(gpioled_count, TRACKSTICK_RANGE_END) entries.
Fixes: 3e64fcbdbd10 ("Input: synaptics-rmi4 - limit the range of what GPIOs are buttons")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
drivers/input/rmi4/rmi_f30.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c
index 35045f161dc2..b2155c8e20e7 100644
--- a/drivers/input/rmi4/rmi_f30.c
+++ b/drivers/input/rmi4/rmi_f30.c
@@ -233,7 +233,7 @@ static int rmi_f30_map_gpios(struct rmi_function *fn,
int button_count = min_t(u8, f30->gpioled_count, TRACKSTICK_RANGE_END);
f30->gpioled_key_map = devm_kcalloc(&fn->dev,
- button_count,
+ f30->gpioled_count,
sizeof(f30->gpioled_key_map[0]),
GFP_KERNEL);
if (!f30->gpioled_key_map) {
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] Input: synaptics-rmi4 - bound the F3A keymap to the GPIO count
From: Bryam Vargas via B4 Relay @ 2026-06-14 5:36 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-kernel, Hans de Goede, Vincent Huang, linux-input,
Andrew Duggan
In-Reply-To: <20260614-b4-disp-818d6bda-v1-0-cf39a3615085@proton.me>
From: Bryam Vargas <hexlabsecurity@proton.me>
rmi_f3a_initialize() takes the GPIO count from the device query register
(f3a->gpio_count = buf & RMI_F3A_GPIO_COUNT, range 0..127).
rmi_f3a_map_gpios() then allocates gpio_key_map with
min(gpio_count, TRACKSTICK_RANGE_END) == at most 6 entries, but
rmi_f3a_attention() iterates the full gpio_count and dereferences
gpio_key_map[i], and input->keycodemax is set to the full gpio_count
while input->keycode points at the 6-entry allocation.
A device that reports gpio_count > 6 therefore causes an out-of-bounds
read of gpio_key_map[] on every attention interrupt, and out-of-bounds
accesses through the input core's default keymap ioctls: EVIOCGKEYCODE
reads past the buffer (leaking adjacent slab memory to user space) and
EVIOCSKEYCODE writes a caller-controlled value past it, for any process
able to open the evdev node, since input_default_getkeycode() and
input_default_setkeycode() only bound the index against keycodemax.
Size the keymap for the full gpio_count. The mapping loop is unchanged:
it still assigns only the first min(gpio_count, TRACKSTICK_RANGE_END)
entries; the remaining slots stay KEY_RESERVED (devm_kcalloc zero-fills)
and are skipped when reporting.
Fixes: 9e4c596bfd00 ("Input: synaptics-rmi4 - add support for F3A")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
---
drivers/input/rmi4/rmi_f3a.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/input/rmi4/rmi_f3a.c b/drivers/input/rmi4/rmi_f3a.c
index 0e8baed84dbb..a0777644eef0 100644
--- a/drivers/input/rmi4/rmi_f3a.c
+++ b/drivers/input/rmi4/rmi_f3a.c
@@ -132,7 +132,7 @@ static int rmi_f3a_map_gpios(struct rmi_function *fn, struct f3a_data *f3a,
int button_count = min_t(u8, f3a->gpio_count, TRACKSTICK_RANGE_END);
f3a->gpio_key_map = devm_kcalloc(&fn->dev,
- button_count,
+ f3a->gpio_count,
sizeof(f3a->gpio_key_map[0]),
GFP_KERNEL);
if (!f3a->gpio_key_map) {
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox