Skip to content

Commit 90e9298

Browse files
Raman Varabetsgregkh
authored andcommitted
HID: ft260: fix stack-use-after-return write in I2C read race
[ Upstream commit bf3e39d ] ft260_i2c_read() points dev->read_buf at a caller-supplied buffer (often an on-stack variable), arms a completion and waits up to five seconds for the device to return the data. The HID input callback ft260_raw_event() runs in the input/IRQ path, independent of the dev->lock mutex held by the read path, and copies the device-supplied payload into dev->read_buf after a plain NULL check. These two paths share read_buf, read_idx and read_len with no serialization. If the device delays its response until the read times out, ft260_i2c_read() resets the controller, clears read_buf and returns, unwinding the stack frame the buffer lived in. A response that arrives at that moment lets ft260_raw_event() pass the NULL check and then memcpy() the device-controlled payload into the now-freed stack location, a bounded but attacker-influenced stack-use-after-return write triggerable by malicious or malfunctioning hardware. Add a dedicated spinlock that serializes every access to read_buf, read_idx and read_len. ft260_raw_event() now holds it across the NULL check, the memcpy and the index update, while the read path takes it when arming and when clearing the buffer, so the teardown can no longer slip between the check and the copy. Fixes: 6a82582 ("HID: ft260: add usb hid to i2c host bridge driver") Cc: stable@vger.kernel.org Signed-off-by: Raman Varabets <kernel-linux-20260610-80b7ab08@raman.v1.sg> Reviewed-by: Michael Zaidman <michael.zaidman@gmail.com> Signed-off-by: Jiri Kosina <jkosina@suse.com> [ adapted per-iteration locking to the flat single-shot `ft260_i2c_read()` body (no chunking loop), dropping one indentation level. ] Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent f9fa2e2 commit 90e9298

1 file changed

Lines changed: 24 additions & 1 deletion

File tree

drivers/hid/hid-ft260.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ struct ft260_device {
240240
struct mutex lock;
241241
u8 write_buf[FT260_REPORT_MAX_LENGTH];
242242
unsigned long need_wakeup_at;
243+
/* Protects read_buf, read_idx and read_len against ft260_raw_event() */
244+
spinlock_t read_lock;
243245
u8 *read_buf;
244246
u16 read_idx;
245247
u16 read_len;
@@ -500,6 +502,7 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
500502
{
501503
struct ft260_i2c_read_request_report rep;
502504
struct hid_device *hdev = dev->hdev;
505+
unsigned long irqflags;
503506
u8 bus_busy = 0;
504507
int timeout;
505508
int ret = 0;
@@ -519,9 +522,11 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
519522

520523
reinit_completion(&dev->wait);
521524

525+
spin_lock_irqsave(&dev->read_lock, irqflags);
522526
dev->read_idx = 0;
523527
dev->read_buf = data;
524528
dev->read_len = len;
529+
spin_unlock_irqrestore(&dev->read_lock, irqflags);
525530

526531
ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
527532
if (ret < 0) {
@@ -537,7 +542,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
537542
goto ft260_i2c_read_exit;
538543
}
539544

545+
spin_lock_irqsave(&dev->read_lock, irqflags);
540546
dev->read_buf = NULL;
547+
spin_unlock_irqrestore(&dev->read_lock, irqflags);
541548

542549
if (flag & FT260_FLAG_STOP)
543550
bus_busy = FT260_I2C_STATUS_BUS_BUSY;
@@ -550,7 +557,9 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
550557
}
551558

552559
ft260_i2c_read_exit:
560+
spin_lock_irqsave(&dev->read_lock, irqflags);
553561
dev->read_buf = NULL;
562+
spin_unlock_irqrestore(&dev->read_lock, irqflags);
554563
return ret;
555564
}
556565

@@ -1019,6 +1028,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
10191028
((struct hidraw *)hdev->hidraw)->minor);
10201029

10211030
mutex_init(&dev->lock);
1031+
spin_lock_init(&dev->read_lock);
10221032
init_completion(&dev->wait);
10231033

10241034
ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
@@ -1068,6 +1078,7 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
10681078
{
10691079
struct ft260_device *dev = hid_get_drvdata(hdev);
10701080
struct ft260_i2c_input_report *xfer = (void *)data;
1081+
unsigned long irqflags;
10711082

10721083
if (size < offsetof(struct ft260_i2c_input_report, data)) {
10731084
hid_err(hdev, "short report %d\n", size);
@@ -1076,6 +1087,8 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
10761087

10771088
if (xfer->report >= FT260_I2C_REPORT_MIN &&
10781089
xfer->report <= FT260_I2C_REPORT_MAX) {
1090+
bool complete_read;
1091+
10791092
ft260_dbg("i2c resp: rep %#02x len %d size %d\n",
10801093
xfer->report, xfer->length, size);
10811094

@@ -1086,8 +1099,15 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
10861099
return -1;
10871100
}
10881101

1102+
/*
1103+
* Hold read_lock so a timed-out ft260_i2c_read() cannot
1104+
* clear read_buf between the NULL check and the memcpy.
1105+
*/
1106+
spin_lock_irqsave(&dev->read_lock, irqflags);
1107+
10891108
if ((dev->read_buf == NULL) ||
10901109
(xfer->length > dev->read_len - dev->read_idx)) {
1110+
spin_unlock_irqrestore(&dev->read_lock, irqflags);
10911111
hid_err(hdev, "unexpected report %#02x, length %d\n",
10921112
xfer->report, xfer->length);
10931113
return -1;
@@ -1096,8 +1116,11 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
10961116
memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
10971117
xfer->length);
10981118
dev->read_idx += xfer->length;
1119+
complete_read = dev->read_idx == dev->read_len;
1120+
1121+
spin_unlock_irqrestore(&dev->read_lock, irqflags);
10991122

1100-
if (dev->read_idx == dev->read_len)
1123+
if (complete_read)
11011124
complete(&dev->wait);
11021125

11031126
} else {

0 commit comments

Comments
 (0)