Input: rmi4 - refactor register descriptor parsing

Factor out parsing a register descriptor item from
rmi_read_register_desc() and ensure there are no out-of-bounds accesses.

Use get_unaligned_le16() and get_unaligned_le32() for reading multi-byte
values.

Reported-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Fixes: 2b6a321da9 ("Input: synaptics-rmi4 - add support for Synaptics RMI4 devices")
Cc: stable@vger.kernel.org
Assisted-by: Gemini:gemini-3.1-pro
Link: https://patch.msgid.link/20260505045952.1570713-2-dmitry.torokhov@gmail.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
This commit is contained in:
Dmitry Torokhov
2026-05-04 21:59:32 -07:00
parent a98518e724
commit 0adb483fbf

View File

@@ -22,6 +22,7 @@
#include <uapi/linux/input.h>
#include <linux/rmi.h>
#include <linux/export.h>
#include <linux/unaligned.h>
#include "rmi_bus.h"
#include "rmi_driver.h"
@@ -558,30 +559,74 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void *ctx,
return retval < 0 ? retval : 0;
}
static int rmi_parse_register_desc_item(struct rmi_register_desc_item *item,
const u8 *buf, size_t size)
{
unsigned int offset = 0;
unsigned int map_offset = 0;
int b;
if (offset >= size)
return -EIO;
item->reg_size = buf[offset++];
if (item->reg_size == 0) {
if (size - offset < 2)
return -EIO;
item->reg_size = get_unaligned_le16(&buf[offset]);
offset += 2;
}
if (item->reg_size == 0) {
if (size - offset < 4)
return -EIO;
item->reg_size = get_unaligned_le32(&buf[offset]);
offset += 4;
}
do {
if (offset >= size)
return -EIO;
for (b = 0; b < 7; b++) {
if (buf[offset] & BIT(b)) {
if (map_offset >= RMI_REG_DESC_SUBPACKET_BITS)
return -EIO;
__set_bit(map_offset, item->subpacket_map);
}
++map_offset;
}
} while (buf[offset++] & BIT(7));
item->num_subpackets = bitmap_weight(item->subpacket_map,
RMI_REG_DESC_SUBPACKET_BITS);
return offset;
}
int rmi_read_register_desc(struct rmi_device *d, u16 addr,
struct rmi_register_descriptor *rdesc)
struct rmi_register_descriptor *rdesc)
{
int ret;
u8 size_presence_reg;
u8 buf[35];
int presense_offset = 1;
u8 *struct_buf;
int reg;
int offset = 0;
int map_offset = 0;
unsigned int presence_offset;
unsigned int map_offset;
unsigned int offset;
unsigned int reg;
int i;
int b;
/*
* The first register of the register descriptor is the size of
* the register descriptor's presense register.
* the register descriptor's presence register.
*/
ret = rmi_read(d, addr, &size_presence_reg);
if (ret)
return ret;
++addr;
if (size_presence_reg < 0 || size_presence_reg > 35)
if (size_presence_reg < 1 || size_presence_reg > 35)
return -EIO;
memset(buf, 0, sizeof(buf));
@@ -597,16 +642,23 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr,
addr += size_presence_reg;
if (buf[0] == 0) {
presense_offset = 3;
rdesc->struct_size = buf[1] | (buf[2] << 8);
if (size_presence_reg < 3)
return -EIO;
presence_offset = 3;
rdesc->struct_size = get_unaligned_le16(&buf[1]);
} else {
presence_offset = 1;
rdesc->struct_size = buf[0];
}
for (i = presense_offset; i < size_presence_reg; i++) {
map_offset = 0;
for (i = presence_offset; i < size_presence_reg; i++) {
for (b = 0; b < 8; b++) {
if (buf[i] & (0x1 << b))
if (buf[i] & BIT(b)) {
if (map_offset >= RMI_REG_DESC_PRESENSE_BITS)
return -EIO;
bitmap_set(rdesc->presense_map, map_offset, 1);
}
++map_offset;
}
}
@@ -626,7 +678,7 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr,
* I'm not using devm_kzalloc here since it will not be retained
* after exiting this function
*/
struct_buf = kzalloc(rdesc->struct_size, GFP_KERNEL);
u8 *struct_buf __free(kfree) = kzalloc(rdesc->struct_size, GFP_KERNEL);
if (!struct_buf)
return -ENOMEM;
@@ -638,56 +690,32 @@ int rmi_read_register_desc(struct rmi_device *d, u16 addr,
*/
ret = rmi_read_block(d, addr, struct_buf, rdesc->struct_size);
if (ret)
goto free_struct_buff;
return ret;
reg = find_first_bit(rdesc->presense_map, RMI_REG_DESC_PRESENSE_BITS);
offset = 0;
for (i = 0; i < rdesc->num_registers; i++) {
struct rmi_register_desc_item *item = &rdesc->registers[i];
int reg_size = struct_buf[offset];
int item_size;
++offset;
if (reg_size == 0) {
reg_size = struct_buf[offset] |
(struct_buf[offset + 1] << 8);
offset += 2;
}
if (reg_size == 0) {
reg_size = struct_buf[offset] |
(struct_buf[offset + 1] << 8) |
(struct_buf[offset + 2] << 16) |
(struct_buf[offset + 3] << 24);
offset += 4;
}
item_size = rmi_parse_register_desc_item(item,
&struct_buf[offset],
rdesc->struct_size - offset);
if (item_size < 0)
return item_size;
item->reg = reg;
item->reg_size = reg_size;
map_offset = 0;
do {
for (b = 0; b < 7; b++) {
if (struct_buf[offset] & (0x1 << b))
bitmap_set(item->subpacket_map,
map_offset, 1);
++map_offset;
}
} while (struct_buf[offset++] & 0x80);
item->num_subpackets = bitmap_weight(item->subpacket_map,
RMI_REG_DESC_SUBPACKET_BITS);
offset += item_size;
rmi_dbg(RMI_DEBUG_CORE, &d->dev,
"%s: reg: %d reg size: %ld subpackets: %d\n", __func__,
item->reg, item->reg_size, item->num_subpackets);
reg = find_next_bit(rdesc->presense_map,
RMI_REG_DESC_PRESENSE_BITS, reg + 1);
RMI_REG_DESC_PRESENSE_BITS, reg + 1);
}
free_struct_buff:
kfree(struct_buf);
return ret;
return 0;
}
const struct rmi_register_desc_item *rmi_get_register_desc_item(