* Re: [PATCH] HID: steam: reject short serial number reports
From: sashiko-bot @ 2026-06-28 0:54 UTC (permalink / raw)
To: Yousef Alhouseen; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260628004106.26920-1-alhouseenyousef@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The serial number payload is systematically truncated and its buffer is improperly NUL-terminated. `strscpy(steam->serial_no, reply + 3, reply[1])` incorrectly uses the dynamic payload length (`reply[1]`) as the destination buffer size limit. Because `strscpy` guarantees NUL-termination by copying at most `size - 1` bytes, it unconditionally truncates the last byte of the serial number. Furthermore, `reply[3 + STEAM_SERIAL_LEN] = 0` hardcodes the NUL terminator at index 24 (the maximum possible payload boundary). If the device returns the maximum 22-byte payload, this overwrites the 22nd payload byte before copying. If the payload is shorter, it leaves uninitialized stack memory between the end of the payload and the NUL terminator, which would be exposed if `strscpy` used the correct buffer size.
- [High] Works and delayed works are cancelled before the underlying hardware is stopped, creating a race condition that leads to a Use-After-Free (UAF) on the `steam_device` structure.
--
commit 9b12928f5d4ac893e4176a52f291e99aaa7d63e9
Author: Yousef Alhouseen <alhouseenyousef@gmail.com>
HID: steam: reject short serial number reports
This patch modifies the steam driver to correctly validate the length of
the serial number response. It accounts for the stripped report ID in the
return value and ensures that both the header and its declared payload are
fully received before parsing.
Closes: https://syzkaller.appspot.com/bug?extid=75f3f9bff8c510602d36
> diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
> index 197126d6e0810..8c8bfb10e8b82 100644
> --- a/drivers/hid/hid-steam.c
> +++ b/drivers/hid/hid-steam.c
> @@ -454,11 +454,20 @@ static int steam_get_serial(struct steam_device *steam)
> ret = steam_recv_report(steam, reply, sizeof(reply));
> if (ret < 0)
> goto out;
> + /* hid_hw_raw_request() counts the stripped report ID byte. */
> + if (ret < 4) {
> + ret = -EIO;
> + goto out;
> + }
> if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 ||
> reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) {
> ret = -EIO;
> goto out;
> }
> + if (ret - 1 < 3 + reply[1]) {
> + ret = -EIO;
> + goto out;
> + }
> reply[3 + STEAM_SERIAL_LEN] = 0;
> strscpy(steam->serial_no, reply + 3, reply[1]);
[Severity: High]
This is a pre-existing issue, but does this logic truncate the serial number?
Because strscpy() guarantees NUL-termination by copying at most size - 1
bytes, passing reply[1] as the destination size limit will unconditionally
truncate the last byte of the serial number payload.
Additionally, setting reply[3 + STEAM_SERIAL_LEN] = 0 hardcodes the NUL
terminator at the maximum boundary.
If the device returns the maximum 22-byte payload, this overwrites the 22nd
payload byte. If the payload is shorter, could this leave uninitialized
stack memory between the end of the payload and the NUL terminator?
I also noticed another potential problem in steam_remove() which is not
touched by this patch:
> static void steam_remove(struct hid_device *hdev)
> {
[ ... ]
> cancel_delayed_work_sync(&steam->mode_switch);
> cancel_work_sync(&steam->work_connect);
> cancel_work_sync(&steam->rumble_work);
> cancel_work_sync(&steam->unregister_work);
> steam->client_hdev = NULL;
> steam->client_opened = 0;
> if (steam->quirks & STEAM_QUIRK_WIRELESS) {
> hid_info(hdev, "Steam wireless receiver disconnected");
> }
> hid_hw_close(hdev);
> hid_hw_stop(hdev);
[Severity: High]
This isn't a bug introduced by this patch, but could this cancellation
sequence cause a use-after-free on the steam struct?
Since the driver explicitly cancels works before stopping the underlying
hardware with hid_hw_close() and hid_hw_stop(), an incoming interrupt could
trigger steam_raw_event() and reschedule the work.
If the work is rescheduled right before steam_remove() returns, could the
workqueue execute on the freed steam struct after the driver core cleans
it up?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628004106.26920-1-alhouseenyousef@gmail.com?part=1
^ permalink raw reply
* [PATCH] HID: synchronize input before cleaning up a failed probe
From: Yousef Alhouseen @ 2026-06-28 0:58 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, syzbot+9eebf5f6544c5e873858,
Yousef Alhouseen
hid_device_io_start() allows reports to run concurrently with probe. If
the probe subsequently fails, __hid_device_probe() releases driver
resources and clears hdev->driver without first excluding those report
callbacks.
For example, a report may enter hidraw_report_event() while the failure
path frees the associated hidraw object, leading to a use-after-free when
the report takes the object's list lock.
Stop input before performing failed-probe cleanup. This reacquires
driver_input_lock and waits for any report callback already in progress.
Fixes: c849a6143bec ("HID: Separate struct hid_device's driver_lock into two locks.")
Reported-by: syzbot+9eebf5f6544c5e873858@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=9eebf5f6544c5e873858
Signed-off-by: Yousef Alhouseen <alhouseenyousef@gmail.com>
---
drivers/hid/hid-core.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 41a79e43c82b..469b849b7a2a 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2835,6 +2835,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
*/
if (ret) {
+ if (hdev->io_started)
+ hid_device_io_stop(hdev);
devres_release_group(&hdev->dev, hdev->devres_group_id);
hid_close_report(hdev);
hdev->driver = NULL;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] HID: synchronize input before cleaning up a failed probe
From: sashiko-bot @ 2026-06-28 1:14 UTC (permalink / raw)
To: Yousef Alhouseen; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260628005846.31248-1-alhouseenyousef@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Use-After-Free in hidraw_report_event() due to drivers calling hid_hw_stop() before hid_device_io_stop() on probe error paths.
--
commit 6c338032433c7e95cf76d26b6790e7b5f487a9b4
Author: Yousef Alhouseen <alhouseenyousef@gmail.com>
HID: synchronize input before cleaning up a failed probe
hid_device_io_start() allows reports to run concurrently with probe. If
the probe subsequently fails, __hid_device_probe() releases driver
resources and clears hdev->driver without first excluding those report
callbacks.
Closes: https://syzkaller.appspot.com/bug?extid=9eebf5f6544c5e873858
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index 41a79e43c82b3..469b849b7a2a4 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -2835,6 +2835,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
> */
>
> if (ret) {
> + if (hdev->io_started)
> + hid_device_io_stop(hdev);
[Severity: High]
This is a pre-existing issue, but does this fix cover all the cases where the
use-after-free can occur?
If drivers call hid_hw_stop(hdev) in their own probe error paths before
returning an error, they might bypass the core's new hid_device_io_stop()
cleanup.
hid_hw_stop() unconditionally calls hid_disconnect(), which frees the hidraw
object. If HID_QUIRK_ALWAYS_POLL is set (or for Bluetooth devices), polling
continues, and a concurrent report could enter hidraw_report_event() with a
dangling pointer before driver_input_lock is taken.
For example, this pattern appears in nintendo_hid_probe():
drivers/hid/hid-nintendo.c:nintendo_hid_probe() {
...
err_close:
hid_hw_close(hdev);
err_stop:
hid_hw_stop(hdev);
...
}
And similarly in corsairpsu_probe():
drivers/hwmon/corsair-psu.c:corsairpsu_probe() {
...
fail_and_close:
hid_hw_close(hdev);
fail_and_stop:
hid_hw_stop(hdev);
...
}
Could these drivers also trigger the use-after-free by tearing down the
hardware and freeing hidraw while IO is still started and driver_input_lock
is unheld?
> devres_release_group(&hdev->dev, hdev->devres_group_id);
> hid_close_report(hdev);
> hdev->driver = NULL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628005846.31248-1-alhouseenyousef@gmail.com?part=1
^ permalink raw reply
* [PATCH 0/2] Add keyboard LED support for Lenovo IdeaPad 83RR/83SR
From: Rodnei Cilto @ 2026-06-29 1:57 UTC (permalink / raw)
To: Dmitry Torokhov, Ike Panhc, Mark Pearson, Derek J. Clark,
Hans de Goede, Ilpo Järvinen
Cc: linux-input, linux-kernel, platform-driver-x86, Rodnei Cilto
This series adds support for the internal keyboard of Lenovo IdeaPad
laptops based on Wildcat Lake SoC (models 83RR worldwide and 83SR
Brazil regional variant).
The EC PS/2 emulation on these models does not fully support the AT
protocol. Sending the SETLEDS command (0xED) after initialization
causes the EC to return corrupted scancodes ('**' in i8042.debug),
rendering the keyboard non-functional.
Patch 1 adds a softleds DMI quirk to atkbd that suppresses 0xED
while keeping EV_LED capabilities visible to userspace.
Patch 2 adds physical CapsLock/NumLock LED control in ideapad-laptop
via direct EC register access (offset 0xA1) and ACPI _QDF method,
discovered via DSDT analysis.
Rodnei Cilto (2):
input: atkbd: add softleds quirk for broken EC PS/2 emulation
platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
Signed-off-by: Rodnei Cilto <rodnei.cilto@gmail.com>
---
Rodnei Cilto (2):
input: atkbd: add softleds quirk for broken EC PS/2 emulation
platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
drivers/input/keyboard/atkbd.c | 46 ++++++++-
drivers/input/serio/i8042-acpipnpio.h | 3 +
drivers/platform/x86/lenovo/ideapad-laptop.c | 143 +++++++++++++++++++++++++++
3 files changed, 191 insertions(+), 1 deletion(-)
---
base-commit: 03e2778d1f11de9260543f969e9e888a1c2bf830
change-id: 20260628-ideapad-kbd-leds-16f587e9ab97
Best regards,
--
Rodnei Cilto <rodnei.cilto@gmail.com>
^ permalink raw reply
* [PATCH 1/2] input: atkbd: add softleds quirk for broken EC PS/2 emulation
From: Rodnei Cilto @ 2026-06-29 1:57 UTC (permalink / raw)
To: Dmitry Torokhov, Ike Panhc, Mark Pearson, Derek J. Clark,
Hans de Goede, Ilpo Järvinen
Cc: linux-input, linux-kernel, platform-driver-x86, Rodnei Cilto
In-Reply-To: <20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com>
Some Lenovo IdeaPad laptops (e.g. 83RR/83SR, Wildcat Lake) implement
PS/2 keyboard emulation via the Embedded Controller (EC) but do not
fully support the AT protocol. Specifically, sending the SETLEDS
command (0xED) after initialization causes the EC to return corrupted
scancodes (reported as '**' in i8042.debug), rendering the keyboard
non-functional.
The existing SERIO_QUIRK_DUMBKBD resolves scancode corruption by
zeroing serio->write, preventing AT commands. However, LED registration
in atkbd_set_device_attrs() depends on atkbd->write being set, so
dumbkbd mode loses EV_LED capabilities entirely.
Note: serio->id.extra is __u8 (8 bits only) and cannot be used to
pass new quirk flags from i8042 to atkbd. The quirk is detected
directly in atkbd via its DMI quirk table.
Introduce atkbd_softleds: a DMI-detected mode that combines dumbkbd
behaviour (serio->write = NULL, no 0xED sent) with EV_LED registration
so that CapsLock/NumLock/ScrollLock state remains visible to userspace
via the input subsystem.
Add DMI entries for Lenovo IdeaPad 83RR (Wildcat Lake) and its Brazil
regional variant 83SR.
Signed-off-by: Rodnei Cilto <rodnei.cilto@gmail.com>
---
drivers/input/keyboard/atkbd.c | 46 ++++++++++++++++++++++++++++++++++-
drivers/input/serio/i8042-acpipnpio.h | 3 +++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index 8cb4dc6fb165..826a21dc016a 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -212,6 +212,7 @@ struct atkbd {
bool softrepeat;
bool softraw;
bool scroll;
+ bool softleds; /* suppress 0xED, register EV_LED in software */
bool enabled;
/* Accessed only from interrupt */
@@ -245,6 +246,7 @@ static unsigned int (*atkbd_platform_scancode_fixup)(struct atkbd *, unsigned in
* to many commands until full reset (ATKBD_CMD_RESET_BAT) is performed.
*/
static bool atkbd_skip_deactivate;
+static bool atkbd_softleds;
static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf,
ssize_t (*handler)(struct atkbd *, char *));
@@ -600,6 +602,14 @@ static int atkbd_set_leds(struct atkbd *atkbd)
struct input_dev *dev = atkbd->dev;
u8 param[2];
+ /*
+ * softleds: EC PS/2 emulation does not support AT commands
+ * after initialization. Accept LED state from userspace but
+ * never send SETLEDS (0xED) to avoid scancode corruption.
+ */
+ if (atkbd->softleds)
+ return 0;
+
param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0)
| (test_bit(LED_NUML, dev->led) ? 2 : 0)
| (test_bit(LED_CAPSL, dev->led) ? 4 : 0);
@@ -1193,7 +1203,7 @@ static void atkbd_set_device_attrs(struct atkbd *atkbd)
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
BIT_MASK(EV_MSC);
- if (atkbd->write) {
+ if (atkbd->write || atkbd->softleds) {
input_dev->evbit[0] |= BIT_MASK(EV_LED);
input_dev->ledbit[0] = BIT_MASK(LED_NUML) |
BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL);
@@ -1291,6 +1301,12 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
if (atkbd->softrepeat)
atkbd->softraw = true;
+ if (atkbd_softleds) {
+ serio->write = NULL;
+ atkbd->write = false;
+ atkbd->softleds = true;
+ }
+
serio_set_drvdata(serio, atkbd);
err = serio_open(serio, drv);
@@ -1767,6 +1783,12 @@ static int __init atkbd_deactivate_fixup(const struct dmi_system_id *id)
return 1;
}
+static int __init atkbd_setup_softleds(const struct dmi_system_id *id)
+{
+ atkbd_softleds = true;
+ return 1;
+}
+
/*
* NOTE: do not add any more "force release" quirks to this table. The
* task of adjusting list of keys that should be "released" automatically
@@ -1938,6 +1960,28 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = {
},
.callback = atkbd_deactivate_fixup,
},
+ {
+ /*
+ * Lenovo IdeaPad 83RR (Wildcat Lake) - EC PS/2 emulation
+ * returns corrupted scancodes ('**' in i8042.debug) when
+ * receiving AT SETLEDS (0xED) after keyboard initialization.
+ * Enable softleds mode: suppress 0xED to hardware while
+ * keeping CapsLock/NumLock/ScrollLock visible to userspace.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"),
+ },
+ .callback = atkbd_setup_softleds,
+ },
+ {
+ /* Lenovo IdeaPad 83SR (83RR Brazil regional variant) */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83SR"),
+ },
+ .callback = atkbd_setup_softleds,
+ },
{ }
};
diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h
index 8ebdf4fb9030..d233544ebac9 100644
--- a/drivers/input/serio/i8042-acpipnpio.h
+++ b/drivers/input/serio/i8042-acpipnpio.h
@@ -79,6 +79,9 @@ static inline void i8042_write_command(int val)
#define SERIO_QUIRK_DIRECT BIT(8)
#define SERIO_QUIRK_DUMBKBD BIT(9)
#define SERIO_QUIRK_NOLOOP BIT(10)
+/* SERIO_QUIRK_DUMBKBD_LEDS handled via atkbd DMI quirk table.
+ * serio->id.extra is __u8 (8 bits only), cannot carry this flag.
+ */
#define SERIO_QUIRK_NOTIMEOUT BIT(11)
#define SERIO_QUIRK_KBDRESET BIT(12)
#define SERIO_QUIRK_DRITEK BIT(13)
--
2.51.0
^ permalink raw reply related
* [PATCH 2/2] platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
From: Rodnei Cilto @ 2026-06-29 1:57 UTC (permalink / raw)
To: Dmitry Torokhov, Ike Panhc, Mark Pearson, Derek J. Clark,
Hans de Goede, Ilpo Järvinen
Cc: linux-input, linux-kernel, platform-driver-x86, Rodnei Cilto
In-Reply-To: <20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com>
Some Lenovo IdeaPad laptops (e.g. 83RR/83SR, Wildcat Lake) have
physical CapsLock and NumLock LEDs controlled via the EC.
The EC exposes CAPL (bit 5) and NUML (bit 4) at offset 0xA1.
Writing these bits via ec_write() and evaluating _QDF via
acpi_evaluate_object() causes the firmware to sync EC state to the
GPIO lines that drive the physical LEDs.
Discovery via DSDT analysis on Lenovo IdeaPad 83RR (Wildcat Lake):
- CAPL/NUML at EC offset 0xA1 (bits 5 and 4)
- _QDF (_SB.PC00.LPCB.EC0._QDF) reads CAPL/NUML -> SGOV()
- GPIO 0x001A1087 -> CapsLock LED physical pin
- GPIO 0x001A0485 -> NumLock LED physical pin
- ec_read/ec_write exported via EXPORT_SYMBOL in drivers/acpi/ec.c
and declared in <linux/acpi.h>
Add two led_classdev entries (input::capslock, input::numlock)
guarded by DMI match (features.kbd_leds) for 83RR and its Brazil
regional variant 83SR.
Signed-off-by: Rodnei Cilto <rodnei.cilto@gmail.com>
---
drivers/platform/x86/lenovo/ideapad-laptop.c | 143 +++++++++++++++++++++++++++
1 file changed, 143 insertions(+)
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 4fbc904f1fc3..03e3f234067c 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -42,6 +42,17 @@
#include <dt-bindings/leds/common.h>
+/* EC keyboard LED control (IdeaPad EC PS/2 emulation).
+ * Validated on Lenovo IdeaPad 83RR (Wildcat Lake):
+ * EC offset 0xA1 bit4=NUML, bit5=CAPL
+ * _QDF syncs EC state to GPIO -> physical LED
+ * ec_read/ec_write declared in <linux/acpi.h>
+ */
+#define IDEAPAD_EC_KBD_LED_OFFSET 0xA1
+#define IDEAPAD_EC_KBD_LED_NUML_BIT BIT(4)
+#define IDEAPAD_EC_KBD_LED_CAPL_BIT BIT(5)
+#define IDEAPAD_ACPI_EC0_QDF_PATH "\\_SB.PC00.LPCB.EC0._QDF"
+
#define IDEAPAD_RFKILL_DEV_NUM 3
enum {
@@ -198,6 +209,7 @@ struct ideapad_private {
bool ctrl_ps2_aux_port : 1;
bool usb_charging : 1;
bool ymc_ec_trigger : 1;
+ bool kbd_leds : 1;
} features;
struct {
bool initialized;
@@ -210,6 +222,11 @@ struct ideapad_private {
struct led_classdev led;
unsigned int last_brightness;
} fn_lock;
+ struct {
+ bool initialized;
+ struct led_classdev capslock;
+ struct led_classdev numlock;
+ } kbd_leds;
};
static bool no_bt_rfkill;
@@ -1587,6 +1604,99 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
/*
* keyboard backlight
*/
+static int ideapad_kbd_led_ec_set(u8 bit, bool on)
+{
+ u8 val;
+ int err;
+
+ err = ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val);
+ if (err)
+ return err;
+ if (on)
+ val |= bit;
+ else
+ val &= ~bit;
+ err = ec_write(IDEAPAD_EC_KBD_LED_OFFSET, val);
+ if (err)
+ return err;
+ acpi_evaluate_object(NULL, IDEAPAD_ACPI_EC0_QDF_PATH, NULL, NULL);
+ return 0;
+}
+
+static void ideapad_capslock_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ ideapad_kbd_led_ec_set(IDEAPAD_EC_KBD_LED_CAPL_BIT, brightness != LED_OFF);
+}
+
+static enum led_brightness ideapad_capslock_led_get(struct led_classdev *led_cdev)
+{
+ u8 val;
+
+ if (ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val))
+ return LED_OFF;
+ return (val & IDEAPAD_EC_KBD_LED_CAPL_BIT) ? LED_ON : LED_OFF;
+}
+
+static void ideapad_numlock_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ ideapad_kbd_led_ec_set(IDEAPAD_EC_KBD_LED_NUML_BIT, brightness != LED_OFF);
+}
+
+static enum led_brightness ideapad_numlock_led_get(struct led_classdev *led_cdev)
+{
+ u8 val;
+
+ if (ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val))
+ return LED_OFF;
+ return (val & IDEAPAD_EC_KBD_LED_NUML_BIT) ? LED_ON : LED_OFF;
+}
+
+static int ideapad_kbd_leds_init(struct ideapad_private *priv)
+{
+ int err;
+
+ if (WARN_ON(priv->kbd_leds.initialized))
+ return -EEXIST;
+
+ priv->kbd_leds.capslock.name = "input::capslock";
+ priv->kbd_leds.capslock.max_brightness = 1;
+ priv->kbd_leds.capslock.brightness_set = ideapad_capslock_led_set;
+ priv->kbd_leds.capslock.brightness_get = ideapad_capslock_led_get;
+ priv->kbd_leds.capslock.flags = LED_RETAIN_AT_SHUTDOWN;
+
+ err = led_classdev_register(&priv->platform_device->dev,
+ &priv->kbd_leds.capslock);
+ if (err)
+ return err;
+
+ priv->kbd_leds.numlock.name = "input::numlock";
+ priv->kbd_leds.numlock.max_brightness = 1;
+ priv->kbd_leds.numlock.brightness_set = ideapad_numlock_led_set;
+ priv->kbd_leds.numlock.brightness_get = ideapad_numlock_led_get;
+ priv->kbd_leds.numlock.flags = LED_RETAIN_AT_SHUTDOWN;
+
+ err = led_classdev_register(&priv->platform_device->dev,
+ &priv->kbd_leds.numlock);
+ if (err) {
+ led_classdev_unregister(&priv->kbd_leds.capslock);
+ return err;
+ }
+
+ priv->kbd_leds.initialized = true;
+ return 0;
+}
+
+static void ideapad_kbd_leds_exit(struct ideapad_private *priv)
+{
+ if (!priv->kbd_leds.initialized)
+ return;
+ priv->kbd_leds.initialized = false;
+ led_classdev_unregister(&priv->kbd_leds.numlock);
+ led_classdev_unregister(&priv->kbd_leds.capslock);
+}
+
static int ideapad_kbd_bl_check_tristate(int type)
{
return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO);
@@ -1832,6 +1942,29 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv, bool send_
priv->r_touchpad_val = value;
}
+static const struct dmi_system_id ideapad_kbd_leds_dmi_table[] = {
+ {
+ /*
+ * Lenovo IdeaPad 83RR (Wildcat Lake) - EC PS/2 emulation
+ * controls CapsLock/NumLock LEDs via EC offset 0xA1 + _QDF.
+ * CAPL=bit5 (0x20), NUML=bit4 (0x10).
+ * _QDF drives GPIO via SGOV() to physical LED pins.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"),
+ },
+ },
+ {
+ /* Lenovo IdeaPad 83SR (83RR Brazil regional variant) */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83SR"),
+ },
+ },
+ { }
+};
+
static const struct dmi_system_id ymc_ec_trigger_quirk_dmi_table[] = {
{
/* Lenovo Yoga 7 14ARB7 */
@@ -2178,6 +2311,8 @@ static int ideapad_check_features(struct ideapad_private *priv)
priv->features.touchpad_ctrl_via_ec = touchpad_ctrl_via_ec;
priv->features.ymc_ec_trigger =
ymc_ec_trigger || dmi_check_system(ymc_ec_trigger_quirk_dmi_table);
+ priv->features.kbd_leds =
+ dmi_check_system(ideapad_kbd_leds_dmi_table);
if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
priv->features.fan_mode = true;
@@ -2418,6 +2553,12 @@ static int ideapad_acpi_add(struct platform_device *pdev)
dev_info(&pdev->dev, "FnLock control not available\n");
}
+ if (priv->features.kbd_leds) {
+ err = ideapad_kbd_leds_init(priv);
+ if (err)
+ dev_warn(&pdev->dev, "Could not set up kbd LEDs: %d\n", err);
+ }
+
/*
* On some models without a hw-switch (the yoga 2 13 at least)
* VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
@@ -2477,6 +2618,7 @@ static int ideapad_acpi_add(struct platform_device *pdev)
ideapad_unregister_rfkill(priv, i);
ideapad_fn_lock_led_exit(priv);
+ ideapad_kbd_leds_exit(priv);
ideapad_kbd_bl_exit(priv);
ideapad_input_exit(priv);
@@ -2506,6 +2648,7 @@ static void ideapad_acpi_remove(struct platform_device *pdev)
ideapad_unregister_rfkill(priv, i);
ideapad_fn_lock_led_exit(priv);
+ ideapad_kbd_leds_exit(priv);
ideapad_kbd_bl_exit(priv);
ideapad_input_exit(priv);
ideapad_debugfs_exit(priv);
--
2.51.0
^ permalink raw reply related
* Re: [PATCH 2/2] platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
From: sashiko-bot @ 2026-06-28 2:06 UTC (permalink / raw)
To: Rodnei Cilto; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260628-ideapad-kbd-leds-v1-2-50b421d94927@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Sleeping in atomic context due to improper LED brightness callback assignment.
- [Medium] Unsynchronized read-modify-write on the shared EC keyboard LED register.
- [Medium] Missing default triggers for CapsLock and NumLock LEDs.
--
commit 3bd8490b486c6791548a82ecc890f8f432c92886
Author: Rodnei Cilto <rodnei.cilto@gmail.com>
platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
This commit adds support for physical CapsLock and NumLock LEDs on specific
Lenovo IdeaPad laptops (83RR/83SR). It maps the EC's offset 0xA1 bits 5 and
4 to CAPL and NUML and exposes them as led_classdev entries.
> diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
> --- a/drivers/platform/x86/lenovo/ideapad-laptop.c
> +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
[ ... ]
> +static int ideapad_kbd_led_ec_set(u8 bit, bool on)
> +{
> + u8 val;
> + int err;
> +
> + err = ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val);
> + if (err)
> + return err;
> + if (on)
> + val |= bit;
> + else
> + val &= ~bit;
> + err = ec_write(IDEAPAD_EC_KBD_LED_OFFSET, val);
[Severity: Medium]
Is this read-modify-write sequence safe against concurrent updates?
Since CapsLock and NumLock are exposed as separate led_classdev devices, the
LED subsystem protects their state with independent locks. If both LEDs are
toggled simultaneously, could two threads concurrently enter
ideapad_kbd_led_ec_set(), read the same initial value, and write it back,
causing one modification to overwrite and lose the other?
> + if (err)
> + return err;
> + acpi_evaluate_object(NULL, IDEAPAD_ACPI_EC0_QDF_PATH, NULL, NULL);
> + return 0;
> +}
[ ... ]
> +static int ideapad_kbd_leds_init(struct ideapad_private *priv)
> +{
> + int err;
> +
> + if (WARN_ON(priv->kbd_leds.initialized))
> + return -EEXIST;
> +
> + priv->kbd_leds.capslock.name = "input::capslock";
> + priv->kbd_leds.capslock.max_brightness = 1;
> + priv->kbd_leds.capslock.brightness_set = ideapad_capslock_led_set;
[Severity: Medium]
Will this LED automatically synchronize with the system's actual keyboard lock
state? Without setting .default_trigger = "kbd-capslock", the LED subsystem
might not automatically link the keyboard input state to this LED, requiring
manual sysfs configuration.
[Severity: High]
Does this risk a scheduling while atomic panic? The LED subsystem often calls
the non-blocking brightness_set callback from atomic contexts, like softirq
for keyboard input triggers.
Since ideapad_capslock_led_set() calls ideapad_kbd_led_ec_set(), which uses
ec_read() and ec_write() that acquire mutexes and sleep, should this use
brightness_set_blocking instead to safely defer execution to a workqueue?
> + priv->kbd_leds.capslock.brightness_get = ideapad_capslock_led_get;
> + priv->kbd_leds.capslock.flags = LED_RETAIN_AT_SHUTDOWN;
> +
> + err = led_classdev_register(&priv->platform_device->dev,
> + &priv->kbd_leds.capslock);
> + if (err)
> + return err;
> +
> + priv->kbd_leds.numlock.name = "input::numlock";
> + priv->kbd_leds.numlock.max_brightness = 1;
> + priv->kbd_leds.numlock.brightness_set = ideapad_numlock_led_set;
[Severity: Medium]
Similarly, should this have .default_trigger = "kbd-numlock" set to ensure
the NumLock LED automatically tracks the physical keyboard state?
[Severity: High]
As with the CapsLock LED, does this need to use brightness_set_blocking to
avoid sleeping in atomic context when the trigger updates the LED?
> + priv->kbd_leds.numlock.brightness_get = ideapad_numlock_led_get;
> + priv->kbd_leds.numlock.flags = LED_RETAIN_AT_SHUTDOWN;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH 1/2] input: atkbd: add softleds quirk for broken EC PS/2 emulation
From: sashiko-bot @ 2026-06-28 2:09 UTC (permalink / raw)
To: Rodnei Cilto; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260628-ideapad-kbd-leds-v1-1-50b421d94927@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The `atkbd` driver mutates the parent `serio` port's `write` function pointer to `NULL`, which is a driver model violation and permanently corrupts the bus state.
- [Medium] The newly added early return `if (atkbd->softleds) return 0;` inside `atkbd_set_leds()` is completely unreachable dead code.
Pre-existing issues:
- [High] A pre-existing race condition in `atkbd_disconnect()` can lead to a Use-After-Free of the `input_dev` structure in `atkbd_event_work()`.
--
commit 449f71d6f3b7e8daf08e55bc0da16d0f59475df4
Author: Rodnei Cilto <rodnei.cilto@gmail.com>
input: atkbd: add softleds quirk for broken EC PS/2 emulation
This commit introduces the atkbd_softleds mode to handle broken EC PS/2
emulation on some Lenovo IdeaPad laptops. It combines dumbkbd behaviour with
EV_LED registration to keep LED states visible to userspace without sending
the SETLEDS command. DMI entries are added for the Lenovo IdeaPad 83RR and
83SR.
> diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
> index 8cb4dc6fb1658..826a21dc016ab 100644
> --- a/drivers/input/keyboard/atkbd.c
> +++ b/drivers/input/keyboard/atkbd.c
> @@ -600,6 +602,14 @@ static int atkbd_set_leds(struct atkbd *atkbd)
> struct input_dev *dev = atkbd->dev;
> u8 param[2];
>
> + /*
> + * softleds: EC PS/2 emulation does not support AT commands
> + * after initialization. Accept LED state from userspace but
> + * never send SETLEDS (0xED) to avoid scancode corruption.
> + */
> + if (atkbd->softleds)
> + return 0;
> +
[Severity: Medium]
Is this early return reachable? When atkbd_softleds is active, atkbd_connect()
sets atkbd->write = false.
When an EV_LED event occurs, atkbd_event() checks atkbd->write and immediately
returns -1, dropping the event before atkbd_schedule_event_work() is called.
Does this mean atkbd_set_leds() is never executed by the workqueue in this
mode, and this check will never be triggered?
[ ... ]
> @@ -1291,6 +1301,12 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
> if (atkbd->softrepeat)
> atkbd->softraw = true;
>
> + if (atkbd_softleds) {
> + serio->write = NULL;
> + atkbd->write = false;
> + atkbd->softleds = true;
> + }
> +
[Severity: High]
Does this permanently modify the parent bus provider's function pointer
(serio->write)?
If atkbd is unbound and another driver (e.g., serio_raw) binds to the same
port, or if atkbd is rebound, would the port remain crippled and unable to
transmit due to serio->write still being NULL?
[Severity: High]
This is a pre-existing issue, but can this sequence lead to a use-after-free
if a privileged user unbinds the driver while LED or repeat rate events are
being processed?
In atkbd_disconnect(), input_unregister_device() is called before
cancel_delayed_work_sync():
drivers/input/keyboard/atkbd.c:atkbd_disconnect() {
...
input_unregister_device(atkbd->dev);
/*
* Make sure we don't have a command in flight.
...
*/
cancel_delayed_work_sync(&atkbd->event_work);
...
}
If atkbd_event_work() starts executing on another CPU, it acquires
atkbd->mutex and checks atkbd->enabled. If preempted immediately after this
check, atkbd_disconnect() can proceed to set atkbd->enabled = false,
unregister and free the input device, and then block on
cancel_delayed_work_sync().
When the worker thread resumes, it calls atkbd_set_leds():
drivers/input/keyboard/atkbd.c:atkbd_event_work() {
...
if (!atkbd->enabled) {
...
} else {
if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask))
atkbd_set_leds(atkbd);
...
}
Would atkbd_set_leds() then dereference the now-freed atkbd->dev?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com?part=1
^ permalink raw reply
* [git pull] Input updates for v7.2-rc0 (part 2)
From: Dmitry Torokhov @ 2026-06-28 5:11 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, linux-input
Hi Linus,
Please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.2-rc0-2
to receive updates for the input subsystem. You will get:
- Updates to Synaptics RMI4 driver to fix potential OOB accesses in
F30 and F3A keymap handling
- A workaround in Synaptics RMI4 to tolerate buggy firmware on some
touchpads (e.g. ThinkPad T14 Gen 1) that report incomplete register
descriptor structures, preventing probe failures
- A revert of an incorrect register descriptor address calculation in
Synaptics RMI4 driver
- A fix for a regression in HP GSC PS/2 (gscps2) driver where the
receive buffer write index was not advanced, leaving keyboard and
mouse unusable.
Changelog:
---------
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
Dmitry Torokhov (2):
Revert "Input: rmi4 - fix register descriptor address calculation"
Input: rmi4 - tolerate short register descriptor structure
Xu Rao (1):
Input: gscps2 - advance receive buffer write index
Diffstat:
--------
drivers/input/rmi4/rmi_driver.c | 39 ++++++++++++++++++++++++++-------------
drivers/input/rmi4/rmi_f30.c | 2 +-
drivers/input/rmi4/rmi_f3a.c | 2 +-
drivers/input/serio/gscps2.c | 1 +
4 files changed, 29 insertions(+), 15 deletions(-)
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/2] input: atkbd: add softleds quirk for broken EC PS/2 emulation
From: Dmitry Torokhov @ 2026-06-28 5:20 UTC (permalink / raw)
To: Rodnei Cilto
Cc: Ike Panhc, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, linux-input, linux-kernel,
platform-driver-x86
In-Reply-To: <20260628-ideapad-kbd-leds-v1-1-50b421d94927@gmail.com>
Hi Rodnei,
On Sun, Jun 28, 2026 at 10:57:03PM -0300, Rodnei Cilto wrote:
> Some Lenovo IdeaPad laptops (e.g. 83RR/83SR, Wildcat Lake) implement
> PS/2 keyboard emulation via the Embedded Controller (EC) but do not
> fully support the AT protocol. Specifically, sending the SETLEDS
> command (0xED) after initialization causes the EC to return corrupted
> scancodes (reported as '**' in i8042.debug), rendering the keyboard
> non-functional.
"**" do not represent corrupted scancodes, they are safety measure to
not disclose your password on accident.
Use i8042.debug=1 i8042.unmask_kbd_data=1 to unveil real data.
Thanks.
--
Dmitry
^ permalink raw reply
page: | 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