syzbot


ID Workflow Result Correct Bug Created Started Finished Revision Error
e71cd1f8-36eb-443c-98e2-0339e5e90f50 patching KASAN: stack-out-of-bounds Read in ft260_smbus_write 2026/01/28 22:43 2026/01/29 00:23 2026/01/29 00:49 8b9a9d26e29b1ad3f31314a827f60be8df01c223


KernelCommit:
v6.19-rc7


KernelRepo:
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git


PatchDescription:
HID: ft260: fix stack-out-of-bounds in ft260_smbus_write

A KASAN stack-out-of-bounds error was observed in ft260_smbus_write().
The issue occurs because the user-supplied length in data->block[0] is
not validated against I2C_SMBUS_BLOCK_MAX (32) in ft260_smbus_xfer
before being used.

Although ft260_smbus_write() checks the length against its own hardware
report buffer size (60 bytes), it does not verify if the source buffer
(from the stack-allocated i2c_smbus_data union) is large enough.

When data->block[0] exceeds I2C_SMBUS_BLOCK_MAX, the memcpy in
ft260_smbus_write() reads past the end of the i2c_smbus_data object.

Fix this by validating the block length against I2C_SMBUS_BLOCK_MAX for
I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA cases.


PatchDiff:
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);


Recipients:
[map[Email:bentiss@kernel.org Name:Benjamin Tissoires To:true] map[Email:jikos@kernel.org Name:Jiri Kosina To:true] map[Email:linux-i2c@vger.kernel.org Name: To:true] map[Email:linux-input@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:michael.zaidman@gmail.com Name:Michael Zaidman To:true]]

Crash report:
==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x19b/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90003427d81 by task syz.2.65/6119

CPU: 0 UID: 0 PID: 6119 Comm: syz.2.65 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xcd/0x630 mm/kasan/report.c:482
 kasan_report+0xe0/0x110 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:194 [inline]
 kasan_check_range+0x100/0x1b0 mm/kasan/generic.c:200
 __asan_memcpy+0x23/0x60 mm/kasan/shadow.c:105
 ft260_smbus_write+0x19b/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x22c/0x640 drivers/hid/hid-ft260.c:736
 __i2c_smbus_xfer drivers/i2c/i2c-core-smbus.c:591 [inline]
 __i2c_smbus_xfer+0x4f0/0xf60 drivers/i2c/i2c-core-smbus.c:554
 i2c_smbus_xfer drivers/i2c/i2c-core-smbus.c:546 [inline]
 i2c_smbus_xfer+0x200/0x3c0 drivers/i2c/i2c-core-smbus.c:536
 i2cdev_ioctl_smbus+0x237/0x990 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x361/0x840 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f88dd98f749
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffeebbe43a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f88ddbe5fa0 RCX: 00007f88dd98f749
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00007f88dda13f91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f88ddbe5fa0 R14: 00007f88ddbe5fa0 R15: 0000000000000003
 </TASK>

The buggy address belongs to stack of task syz.2.65/6119
 and is located at offset 49 in frame:
 i2cdev_ioctl_smbus+0x0/0x990 drivers/i2c/i2c-dev.c:632

This frame has 1 object:
 [48, 82) 'temp'

The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90003420000 allocated at kernel_clone+0xfc/0x910 kernel/fork.c:2651
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x22397
memcg:ffff888074f11282
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000088e5c8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff ffff888074f11282
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5928, tgid 5928 (syz-executor), ts 80598654650, free_ts 80595057505
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x1af/0x220 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0xd0b/0x31a0 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x25f/0x2430 mm/page_alloc.c:5240
 alloc_pages_mpol+0x1fb/0x550 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0x131/0x390 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0xe6c/0x16b0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct kernel/fork.c:923 [inline]
 copy_process+0x619/0x7430 kernel/fork.c:2052
 kernel_clone+0xfc/0x910 kernel/fork.c:2651
 __do_sys_clone+0xce/0x120 kernel/fork.c:2792
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5928 tgid 5928 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0x7df/0x1170 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x130/0x170 mm/slub.c:3886
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x4c/0xf0 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x195/0x1e0 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 __kmalloc_cache_node_noprof+0x299/0x830 mm/slub.c:5784
 kmalloc_node_noprof include/linux/slab.h:983 [inline]
 __get_vm_area_node+0x101/0x330 mm/vmalloc.c:3208
 __vmalloc_node_range_noprof+0x247/0x16b0 mm/vmalloc.c:4011
 __vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4111
 xt_counters_alloc+0x4c/0x70 net/netfilter/x_tables.c:1381
 __do_replace+0x97/0x9e0 net/ipv4/netfilter/ip_tables.c:1046
 do_replace net/ipv4/netfilter/ip_tables.c:1141 [inline]
 do_ipt_set_ctl+0x93b/0xc40 net/ipv4/netfilter/ip_tables.c:1635
 nf_setsockopt+0x8d/0xf0 net/netfilter/nf_sockopt.c:101
 ip_setsockopt+0xcb/0xf0 net/ipv4/ip_sockglue.c:1424
 tcp_setsockopt+0xa7/0x100 net/ipv4/tcp.c:4164
 do_sock_setsockopt+0xf3/0x1d0 net/socket.c:2322

Memory state around the buggy address:
 ffffc90003427c80: f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90003427d00: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1
>ffffc90003427d80: 00 00 00 00 02 f3 f3 f3 f3 f3 00 00 00 00 00 00
                               ^
 ffffc90003427e00: 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1 04 f2 00 00
 ffffc90003427e80: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
==================================================================

Trajectory:
Seq Timestamp Type Name Duration
0/0 2026/01/29 00:23 flow patching
26m Results:
map[KernelCommit:v6.19-rc7 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git PatchDescription:HID: ft260: fix stack-out-of-bounds in ft260_smbus_write

A KASAN stack-out-of-bounds error was observed in ft260_smbus_write().
The issue occurs because the user-supplied length in data->block[0] is
not validated against I2C_SMBUS_BLOCK_MAX (32) in ft260_smbus_xfer
before being used.

Although ft260_smbus_write() checks the length against its own hardware
report buffer size (60 bytes), it does not verify if the source buffer
(from the stack-allocated i2c_smbus_data union) is large enough.

When data->block[0] exceeds I2C_SMBUS_BLOCK_MAX, the memcpy in
ft260_smbus_write() reads past the end of the i2c_smbus_data object.

Fix this by validating the block length against I2C_SMBUS_BLOCK_MAX for
I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA cases. PatchDiff:diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
 Recipients:[map[Email:bentiss@kernel.org Name:Benjamin Tissoires To:true] map[Email:jikos@kernel.org Name:Jiri Kosina To:true] map[Email:linux-i2c@vger.kernel.org Name: To:true] map[Email:linux-input@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:michael.zaidman@gmail.com Name:Michael Zaidman To:true]]]

1/1 2026/01/29 00:23 action base-commit-picker
0m Results:
map[KernelCommit:v6.19-rc7 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git]

2/1 2026/01/29 00:23 action kernel-checkouter
0m Results:
map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/5452af61ab2e1f5601403fe379188fc9d8a831af]

3/1 2026/01/29 00:23 action kernel-builder
6m Results:
map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/93596d01a9653f3962ecb8710025606279e5b9c4]

4/1 2026/01/29 00:29 action crash-reproducer
1m Results:
map[BugTitle:KASAN: stack-out-of-bounds Read in ft260_smbus_write CrashReport:==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002cdfd21 by task syz-executor400/5948

CPU: 0 UID: 0 PID: 5948 Comm: syz-executor400 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f3cef1aa539
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffc58093a58 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f3cef1aa539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc58093bd8
R13: 0000000000000002 R14: 00007ffc58093a80 R15: 00007ffc58093a70
 </TASK>

The buggy address belongs to stack of task syz-executor400/5948
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a vmalloc virtual mapping
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x2948f
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5944, tgid 5944 (syz-executor400), ts 90060265112, free_ts 90040528493
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5942 tgid 5942 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 do_sys_openat2+0xbc/0x230 fs/open.c:1426
 do_sys_open fs/open.c:1436 [inline]
 __do_sys_openat fs/open.c:1452 [inline]
 __se_sys_openat fs/open.c:1447 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1447
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002cdfc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002cdfc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002cdfd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002cdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002cdfe00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================
]

5/1 2026/01/29 00:31 action codesearch-prepare
3m Results:
map[Index:codesearch-index]

6/1 2026/01/29 00:34 agent debugger
2m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with debugging a kernel crash root cause.
You need to provide a detailed explanation of the root cause for another developer to be
able to write a fix for the bug based on your explanation.
Your final reply must contain only the explanation.

Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash is:

==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002cdfd21 by task syz-executor400/5948

CPU: 0 UID: 0 PID: 5948 Comm: syz-executor400 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f3cef1aa539
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffc58093a58 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f3cef1aa539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc58093bd8
R13: 0000000000000002 R14: 00007ffc58093a80 R15: 00007ffc58093a70
 </TASK>

The buggy address belongs to stack of task syz-executor400/5948
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a vmalloc virtual mapping
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x2948f
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5944, tgid 5944 (syz-executor400), ts 90060265112, free_ts 90040528493
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5942 tgid 5942 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 do_sys_openat2+0xbc/0x230 fs/open.c:1426
 do_sys_open fs/open.c:1436 [inline]
 __do_sys_openat fs/open.c:1452 [inline]
 __se_sys_openat fs/open.c:1447 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1447
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002cdfc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002cdfc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002cdfd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002cdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002cdfe00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================


Reply:
The root cause of the crash is a missing validation check in `ft260_smbus_xfer` (in `drivers/hid/hid-ft260.c`) which allows a user-supplied length to exceed the size of the kernel stack buffer `union i2c_smbus_data`.

The crash occurs in `ft260_smbus_write` during a `memcpy` operation. Here is the sequence of events:

1.  The `i2cdev_ioctl_smbus` function (in `drivers/i2c/i2c-dev.c`) copies user-supplied data into a stack variable `temp` of type `union i2c_smbus_data`. This union has a fixed size of 34 bytes (including `I2C_SMBUS_BLOCK_MAX` which is 32). The user can set the length byte `block[0]` to any value, for example 42.
2.  `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which eventually calls the driver callback `ft260_smbus_xfer` with `data` pointing to `temp`.
3.  For the `I2C_SMBUS_I2C_BLOCK_DATA` case (write), `ft260_smbus_xfer` extracts the length from `data->block[0]` and the buffer pointer as `data->block + 1`. It does **not** validate that `data->block[0] <= I2C_SMBUS_BLOCK_MAX`.
4.  It calls `ft260_smbus_write` passing this length (e.g., 42) and the buffer pointer.
5.  `ft260_smbus_write` checks if the length fits into its own hardware report buffer (`sizeof(rep->data)` which is 60 bytes). Since 42 < 60, the check passes.
6.  `ft260_smbus_write` then performs `memcpy(&rep->data[1], data, data_len)`.
    *   `data` points to `temp.block[1]`, which is at offset 1 of the 34-byte `temp` union.
    *   This leaves only 33 valid bytes available to be read from `data` before hitting the end of `temp`.
    *   The `memcpy` tries to read 42 bytes, going 9 bytes past the end of the `temp` object on the stack, triggering the KASAN stack-out-of-bounds report.

The fix is to validate `data->block[0]` in `ft260_smbus_xfer` against `I2C_SMBUS_BLOCK_MAX` (32) for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions.

7/2 2026/01/29 00:34 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 5674
							output: 46
							thoughts: 541
						

Thoughts:
**Debugging a KASAN Stack-Out-of-Bounds Crash**

Okay, so I've got a KASAN stack-out-of-bounds crash in `ft260_smbus_write`, specifically at `drivers/hid/hid-ft260.c:486`. It looks like this function is called from `ft260_smbus_xfer`. The key piece of information is that the buggy address resides on the stack of `syz-executor400` within the `i2cdev_ioctl_smbus` frame, at `drivers/i2c/i2c-dev.c:317`.

My initial thought is to figure out where this out-of-bounds access is happening within `ft260_smbus_write`. The KASAN report indicates that the frame `i2cdev_ioctl_smbus` has one object, `[32, 66) 'temp'`, which is 34 bytes long (66-32 = 34). The crash occurs with a read of size 42 at offset 33 of that stack frame. This instantly tells me that we have a buffer overflow on the stack. My understanding is that the read is happening within `temp`, given that the access starts at offset 33, and `temp` starts at offset 32. This would mean that the read goes from byte 33 to byte 75 (33 + 42) of the stack. But `temp` only occupies bytes 32 to 66. So the read is clearly overflowing the bounds of `temp`.

Now, the KASAN report confirms my interpretation: the buggy address is at offset 33 of the `i2cdev_ioctl_smbus` frame, where `temp` starts at 32 and goes to 66. So it's reading at offset 1 within `temp` and reading 42 bytes.

So, I need to look at both `ft260_smbus_write` and `i2cdev_ioctl_smbus` in detail. I'll need to examine the code to see how `temp` is used in `i2cdev_ioctl_smbus` and how data is being read from it within `ft260_smbus_write`. I'll start by checking the file paths to ensure I have the correct source files. I'll then examine the code around line 486 in `drivers/hid/hid-ft260.c` and around line 317 in `drivers/i2c/i2c-dev.c`.



8/2 2026/01/29 00:35 tool codesearch-dir-index
0m Args:
map[Dir:drivers/hid/]

Results:
map[Files:[hid-a4tech.c hid-accutouch.c hid-alps.c hid-apple.c hid-appleir.c hid-appletb-bl.c hid-appletb-kbd.c hid-asus.c hid-aureal.c hid-axff.c hid-belkin.c hid-betopff.c hid-bigbenff.c hid-cherry.c hid-chicony.c hid-cmedia.c hid-core.c hid-corsair-void.c hid-corsair.c hid-cougar.c hid-cp2112.c hid-creative-sb0540.c hid-cypress.c hid-debug.c hid-dr.c hid-elan.c hid-elecom.c hid-elo.c hid-emsff.c hid-evision.c hid-ezkey.c hid-ft260.c hid-gaff.c hid-gembird.c hid-generic.c hid-gfrm.c hid-glorious.c hid-goodix-spi.c hid-google-hammer.c hid-google-stadiaff.c hid-gt683r.c hid-gyration.c hid-haptic.c hid-haptic.h hid-holtek-kbd.c hid-holtek-mouse.c hid-holtekff.c hid-hyperv.c hid-icade.c hid-ids.h hid-input-test.c hid-input.c hid-ite.c hid-jabra.c hid-kensington.c hid-keytouch.c hid-kye.c hid-kysona.c hid-lcpower.c hid-led.c hid-lenovo.c hid-letsketch.c hid-lg-g15.c hid-lg.c hid-lg.h hid-lg2ff.c hid-lg3ff.c hid-lg4ff.c hid-lg4ff.h hid-lgff.c hid-logitech-dj.c hid-logitech-hidpp.c hid-macally.c hid-magicmouse.c hid-maltron.c hid-mcp2200.c hid-mcp2221.c hid-megaworld.c hid-mf.c hid-microsoft.c hid-monterey.c hid-multitouch.c hid-nintendo.c hid-nti.c hid-ntrig.c hid-nvidia-shield.c hid-ortek.c hid-penmount.c hid-petalynx.c hid-picolcd.h hid-picolcd_backlight.c hid-picolcd_cir.c hid-picolcd_core.c hid-picolcd_debugfs.c hid-picolcd_fb.c hid-picolcd_lcd.c hid-picolcd_leds.c hid-pl.c hid-plantronics.c hid-playstation.c hid-primax.c hid-prodikeys.c hid-pxrc.c hid-quirks.c hid-razer.c hid-redragon.c hid-retrode.c hid-rmi.c hid-roccat-arvo.c hid-roccat-arvo.h hid-roccat-common.c hid-roccat-common.h hid-roccat-isku.c hid-roccat-isku.h hid-roccat-kone.c hid-roccat-kone.h hid-roccat-koneplus.c hid-roccat-koneplus.h hid-roccat-konepure.c hid-roccat-kovaplus.c hid-roccat-kovaplus.h hid-roccat-lua.c hid-roccat-lua.h hid-roccat-pyra.c hid-roccat-pyra.h hid-roccat-ryos.c hid-roccat-savu.c hid-roccat-savu.h hid-roccat.c hid-saitek.c hid-samsung.c hid-semitek.c hid-sensor-custom.c hid-sensor-hub.c hid-sigmamicro.c hid-sjoy.c hid-sony.c hid-speedlink.c hid-steam.c hid-steelseries.c hid-sunplus.c hid-thrustmaster.c hid-tivo.c hid-tmff.c hid-topre.c hid-topseed.c hid-twinhan.c hid-u2fzero.c hid-uclogic-core-test.c hid-uclogic-core.c hid-uclogic-params-test.c hid-uclogic-params.c hid-uclogic-params.h hid-uclogic-rdesc-test.c hid-uclogic-rdesc.c hid-uclogic-rdesc.h hid-udraw-ps3.c hid-universal-pidff.c hid-viewsonic.c hid-vivaldi-common.c hid-vivaldi-common.h hid-vivaldi.c hid-vrc2.c hid-waltop.c hid-wiimote-core.c hid-wiimote-debug.c hid-wiimote-modules.c hid-wiimote.h hid-winwing.c hid-xiaomi.c hid-xinmo.c hid-zpff.c hid-zydacron.c hidraw.c uhid.c wacom.h wacom_sys.c wacom_wac.c wacom_wac.h] Subdirs:[amd-sfh-hid bpf i2c-hid intel-ish-hid intel-thc-hid surface-hid usbhid]]

9/2 2026/01/29 00:35 tool codesearch-dir-index
0m Args:
map[Dir:drivers/i2c/]

Results:
map[Files:[i2c-atr.c i2c-boardinfo.c i2c-core-acpi.c i2c-core-base.c i2c-core-of-prober.c i2c-core-of.c i2c-core-slave.c i2c-core-smbus.c i2c-core.h i2c-dev.c i2c-mux.c i2c-slave-eeprom.c i2c-slave-testunit.c i2c-smbus.c i2c-stub.c] Subdirs:[algos busses muxes]]

10/2 2026/01/29 00:35 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 8935
							output: 101
							thoughts: 75
						

Thoughts:
Okay, here's my summary, tailored as requested:

**Digging into the I2C Communication**

Alright, the files are in place; good. Time to get down to the brass tacks of this I2C interaction. I need to understand how this FT260 chip is actually writing data over SMBus. My focus is going to be on two critical functions. First, I'm going to pore over `ft260_smbus_write` within `drivers/hid/hid-ft260.c`. This is where the core logic for the FT260's SMBus write operation should reside, I need to see how the HID driver handles the SMBus transaction.

Secondly, and perhaps just as importantly, I'm going to cross-reference that with `i2cdev_ioctl_smbus` in `drivers/i2c/i2c-dev.c`. This is the user-space interface, the "hook" that allows applications to interact with the I2C bus through the device driver. I'm keen to see how the `ioctl` call translates into the actual SMBus write command going through the FT260 driver.

I'm going to request the source code for both of these functions to get a clearer picture. I'll analyze how the data is being constructed, how the commands are being issued, and where potential points of failure or optimization may exist. This is the crux of understanding the overall I2C communication flow.



11/2 2026/01/29 00:35 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_smbus_write]

Results:
map[SourceCode: 465:	static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
 466:				     u8 *data, u8 data_len, u8 flag)
 467:	{
 468:		int ret = 0;
 469:		int len = 4;
 470:	
 471:		struct ft260_i2c_write_request_report *rep =
 472:			(struct ft260_i2c_write_request_report *)dev->write_buf;
 473:	
 474:		if (data_len >= sizeof(rep->data))
 475:			return -EINVAL;
 476:	
 477:		rep->address = addr;
 478:		rep->data[0] = cmd;
 479:		rep->length = data_len + 1;
 480:		rep->flag = flag;
 481:		len += rep->length;
 482:	
 483:		rep->report = FT260_I2C_DATA_REPORT_ID(len);
 484:	
 485:		if (data_len > 0)
 486:			memcpy(&rep->data[1], data, data_len);
 487:	
 488:		ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
 489:			  rep->report, addr, cmd, rep->length, len);
 490:	
 491:		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);
 492:	
 493:		return ret;
 494:	}
 SourceFile:drivers/hid/hid-ft260.c]

12/2 2026/01/29 00:35 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/i2c/i2c-dev.c IncludeLines:true Name:i2cdev_ioctl_smbus]

Results:
map[SourceCode: 319:	static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
 320:			u8 read_write, u8 command, u32 size,
 321:			union i2c_smbus_data __user *data)
 322:	{
 323:		union i2c_smbus_data temp = {};
 324:		int datasize, res;
 325:	
 326:		if ((size != I2C_SMBUS_BYTE) &&
 327:		    (size != I2C_SMBUS_QUICK) &&
 328:		    (size != I2C_SMBUS_BYTE_DATA) &&
 329:		    (size != I2C_SMBUS_WORD_DATA) &&
 330:		    (size != I2C_SMBUS_PROC_CALL) &&
 331:		    (size != I2C_SMBUS_BLOCK_DATA) &&
 332:		    (size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
 333:		    (size != I2C_SMBUS_I2C_BLOCK_DATA) &&
 334:		    (size != I2C_SMBUS_BLOCK_PROC_CALL)) {
 335:			dev_dbg(&client->adapter->dev,
 336:				"size out of range (%x) in ioctl I2C_SMBUS.\n",
 337:				size);
 338:			return -EINVAL;
 339:		}
 340:		/* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
 341:		   so the check is valid if size==I2C_SMBUS_QUICK too. */
 342:		if ((read_write != I2C_SMBUS_READ) &&
 343:		    (read_write != I2C_SMBUS_WRITE)) {
 344:			dev_dbg(&client->adapter->dev,
 345:				"read_write out of range (%x) in ioctl I2C_SMBUS.\n",
 346:				read_write);
 347:			return -EINVAL;
 348:		}
 349:	
 350:		/* Note that command values are always valid! */
 351:	
 352:		if ((size == I2C_SMBUS_QUICK) ||
 353:		    ((size == I2C_SMBUS_BYTE) &&
 354:		    (read_write == I2C_SMBUS_WRITE)))
 355:			/* These are special: we do not use data */
 356:			return i2c_smbus_xfer(client->adapter, client->addr,
 357:					      client->flags, read_write,
 358:					      command, size, NULL);
 359:	
 360:		if (data == NULL) {
 361:			dev_dbg(&client->adapter->dev,
 362:				"data is NULL pointer in ioctl I2C_SMBUS.\n");
 363:			return -EINVAL;
 364:		}
 365:	
 366:		if ((size == I2C_SMBUS_BYTE_DATA) ||
 367:		    (size == I2C_SMBUS_BYTE))
 368:			datasize = sizeof(data->byte);
 369:		else if ((size == I2C_SMBUS_WORD_DATA) ||
 370:			 (size == I2C_SMBUS_PROC_CALL))
 371:			datasize = sizeof(data->word);
 372:		else /* size == smbus block, i2c block, or block proc. call */
 373:			datasize = sizeof(data->block);
 374:	
 375:		if ((size == I2C_SMBUS_PROC_CALL) ||
 376:		    (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
 377:		    (size == I2C_SMBUS_I2C_BLOCK_DATA) ||
 378:		    (read_write == I2C_SMBUS_WRITE)) {
 379:			if (copy_from_user(&temp, data, datasize))
 380:				return -EFAULT;
 381:		}
 382:		if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
 383:			/* Convert old I2C block commands to the new
 384:			   convention. This preserves binary compatibility. */
 385:			size = I2C_SMBUS_I2C_BLOCK_DATA;
 386:			if (read_write == I2C_SMBUS_READ)
 387:				temp.block[0] = I2C_SMBUS_BLOCK_MAX;
 388:		}
 389:		res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
 390:		      read_write, command, size, &temp);
 391:		if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
 392:			     (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
 393:			     (read_write == I2C_SMBUS_READ))) {
 394:			if (copy_to_user(data, &temp, datasize))
 395:				return -EFAULT;
 396:		}
 397:		return res;
 398:	}
 SourceFile:drivers/i2c/i2c-dev.c]

13/2 2026/01/29 00:35 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 11472
							output: 51
							thoughts: 948
						

Thoughts:
**Analyzing the FT260 SMBus Write Vulnerability**

Okay, let's break this down. We've got a KASAN report pointing us towards a potential out-of-bounds read in `ft260_smbus_write`. The core issue seems to revolve around how data is being copied within that function. I can see in the `ft260_smbus_write` code snippet that `memcpy` is being used, specifically: `memcpy(&rep->data[1], data, data_len);`.  `data` is a pointer to the source buffer, and `data_len` is the number of bytes we intend to copy.

The report tells us that `ft260_smbus_write` is being called from `ft260_smbus_xfer`, and it's essential that we investigate the context surrounding that function call. We need to understand the source of the `data` pointer and how `data_len` is being determined.

The report also gives us context from `i2cdev_ioctl_smbus`, it's clear the `data` is being passed from `i2cdev_ioctl_smbus`.  In `i2cdev_ioctl_smbus` we see `temp`, which is a `union i2c_smbus_data`, is initialized.  Crucially, we're dealing with a `union` here.  `sizeof(union i2c_smbus_data)` is 34 bytes, which matches what the crash report indicates, so that confirms our understanding.  The `i2c_smbus_data` union is defined as containing:

```c
union i2c_smbus_data {
	__u8 byte;
	__u16 word;
	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
			       /* and one more for user-space compatibility */
};
```
with `I2C_SMBUS_BLOCK_MAX` being 32, we know the size of the block is 34 bytes.

The crash is happening when reading from `data` within `ft260_smbus_write`. The problem is that the size of the read is 42 bytes while the `temp` buffer, which `data` points to (or rather, points *into* since it's an offset within the union's memory) is only 34 bytes. The report says we are reading from offset 33 of the function frame and offset 1 of `temp`. That confirms my suspicion of an out-of-bounds read. This corresponds to copying from `&temp.block[1]`.

So, the key here is that `ft260_smbus_write` is being called with a `data_len` of 42, which is exceeding the allocated buffer size. Now, we must examine `ft260_smbus_xfer` to trace where that `data_len` is coming from, because that is where the vulnerability lies.



14/2 2026/01/29 00:35 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_smbus_xfer]

Results:
map[SourceCode: 652:	static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 653:				    char read_write, u8 cmd, int size,
 654:				    union i2c_smbus_data *data)
 655:	{
 656:		int ret;
 657:		struct ft260_device *dev = i2c_get_adapdata(adapter);
 658:		struct hid_device *hdev = dev->hdev;
 659:	
 660:		ft260_dbg("smbus size %d\n", size);
 661:	
 662:		mutex_lock(&dev->lock);
 663:	
 664:		ret = hid_hw_power(hdev, PM_HINT_FULLON);
 665:		if (ret < 0) {
 666:			hid_err(hdev, "power management error: %d\n", ret);
 667:			mutex_unlock(&dev->lock);
 668:			return ret;
 669:		}
 670:	
 671:		switch (size) {
 672:		case I2C_SMBUS_BYTE:
 673:			if (read_write == I2C_SMBUS_READ)
 674:				ret = ft260_i2c_read(dev, addr, &data->byte, 1,
 675:						     FT260_FLAG_START_STOP);
 676:			else
 677:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 678:							FT260_FLAG_START_STOP);
 679:			break;
 680:		case I2C_SMBUS_BYTE_DATA:
 681:			if (read_write == I2C_SMBUS_READ) {
 682:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 683:							FT260_FLAG_START);
 684:				if (ret)
 685:					goto smbus_exit;
 686:	
 687:				ret = ft260_i2c_read(dev, addr, &data->byte, 1,
 688:						     FT260_FLAG_START_STOP_REPEATED);
 689:			} else {
 690:				ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
 691:							FT260_FLAG_START_STOP);
 692:			}
 693:			break;
 694:		case I2C_SMBUS_WORD_DATA:
 695:			if (read_write == I2C_SMBUS_READ) {
 696:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 697:							FT260_FLAG_START);
 698:				if (ret)
 699:					goto smbus_exit;
 700:	
 701:				ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
 702:						     FT260_FLAG_START_STOP_REPEATED);
 703:			} else {
 704:				ret = ft260_smbus_write(dev, addr, cmd,
 705:							(u8 *)&data->word, 2,
 706:							FT260_FLAG_START_STOP);
 707:			}
 708:			break;
 709:		case I2C_SMBUS_BLOCK_DATA:
 710:			if (read_write == I2C_SMBUS_READ) {
 711:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 712:							FT260_FLAG_START);
 713:				if (ret)
 714:					goto smbus_exit;
 715:	
 716:				ret = ft260_i2c_read(dev, addr, data->block,
 717:						     data->block[0] + 1,
 718:						     FT260_FLAG_START_STOP_REPEATED);
 719:			} else {
 720:				ret = ft260_smbus_write(dev, addr, cmd, data->block,
 721:							data->block[0] + 1,
 722:							FT260_FLAG_START_STOP);
 723:			}
 724:			break;
 725:		case I2C_SMBUS_I2C_BLOCK_DATA:
 726:			if (read_write == I2C_SMBUS_READ) {
 727:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 728:							FT260_FLAG_START);
 729:				if (ret)
 730:					goto smbus_exit;
 731:	
 732:				ret = ft260_i2c_read(dev, addr, data->block + 1,
 733:						     data->block[0],
 734:						     FT260_FLAG_START_STOP_REPEATED);
 735:			} else {
 736:				ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
 737:							data->block[0],
 738:							FT260_FLAG_START_STOP);
 739:			}
 740:			break;
 741:		default:
 742:			hid_err(hdev, "unsupported smbus transaction size %d\n", size);
 743:			ret = -EOPNOTSUPP;
 744:		}
 745:	
 746:	smbus_exit:
 747:		hid_hw_power(hdev, PM_HINT_NORMAL);
 748:		mutex_unlock(&dev->lock);
 749:		return ret;
 750:	}
 SourceFile:drivers/hid/hid-ft260.c]

15/2 2026/01/29 00:35 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 14874
							output: 55
							thoughts: 2626
						

Thoughts:
**Analysis of the I2C SMBus Block Data Vulnerability**

My investigation centers on a potential out-of-bounds (OOB) read vulnerability within the FT260 I2C driver, specifically when handling `I2C_SMBUS_I2C_BLOCK_DATA` transfers. My thought process is as follows:

The vulnerability seems to manifest in the `ft260_smbus_write` function when dealing with `I2C_SMBUS_I2C_BLOCK_DATA`.  Here, the driver takes the `data->block[0]` value directly from user-space, representing the block length, and uses it without validation.  The code accesses `data->block + 1` to copy data to a buffer. However, the `i2c-dev.c` driver, which handles the user-space interface, copies the entire `i2c_smbus_data` union from user space, including the potentially malicious length byte. It does not validate the value of `data->block[0]` before calling `i2c_smbus_xfer`.

The `i2c_smbus_data` union is defined to have a size of 34 bytes (32 bytes for the block and 2 bytes for the length information), so if `data->block[0]` is >33, the buffer will be read out of bounds.

Now, my focus has shifted to the `ft260_smbus_write` function, which is ultimately responsible for the write operation. My initial concern was that `ft260_smbus_write` was not verifying `data_len` prior to the `memcpy` operation.

Fortunately, it looks like `ft260_smbus_write` does a check on line 474: `if (data_len >= sizeof(rep->data)) return -EINVAL;`. This is promising. I need to determine the size of `rep->data`. If `sizeof(rep->data)` is small enough, the user-supplied `data_len` (controlled by `data->block[0]`) might still be able to cause an overflow, but if the check is performed correctly, it may mitigate the issue.



16/2 2026/01/29 00:35 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_i2c_write_request_report]

Results:
map[SourceCode: 208:	struct ft260_i2c_write_request_report {
 209:		u8 report;		/* FT260_I2C_REPORT */
 210:		u8 address;		/* 7-bit I2C address */
 211:		u8 flag;		/* I2C transaction condition */
 212:		u8 length;		/* data payload length */
 213:		u8 data[FT260_WR_DATA_MAX]; /* data payload */
 214:	} __packed;
 SourceFile:drivers/hid/hid-ft260.c]

17/2 2026/01/29 00:35 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 18211
							output: 51
							thoughts: 14
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Navigating the FT260 Data Sheet**

Alright, I'm digging into this FT260 device and, as I'm working through the register descriptions and potential data transfer scenarios, I've hit a critical point: I need to determine the maximum write data size I can push through the FT260. I immediately jump to the need for understanding the `FT260_WR_DATA_MAX` value. It's that simple. Without it, I can't effectively plan the data packet structures, buffering strategies, or interrupt handling on either the host or the FT260 itself. This directly impacts the efficiency of my application. I need to know this maximum allowable buffer size so I can ensure I'm not overrunning any internal buffers or violating any hardware constraints. Time to consult the data sheet and nail this down. It's the lynchpin for any efficient and robust implementation.



18/2 2026/01/29 00:36 tool codesearch-definition-source
0m Error:
requested entity does not exist

Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:FT260_WR_DATA_MAX]

Results:
map[SourceCode: SourceFile:]

19/2 2026/01/29 00:36 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 18516
							output: 27
							thoughts: 43
						

Thoughts:
Okay, here's my interpretation of that thought process, from an expert's perspective:

**Initial Assessment and Diagnostic Approach**

Alright, so we've got something we suspect is a `#define` macro. That's a reasonable first guess. Given the context, I immediately start running through my mental checklist. "Could this be a constant, a flag, or something else defined in this specific driver?" The first thing I'd do is either peek directly at the code or run a quick `grep` search for the suspected macro in the relevant file. It's a quick and dirty way to confirm the syntax and scope. Since we're probably debugging or reverse engineering something, I'll go straight for the code - let's see what's going on in the `drivers/hid/hid-ft260.c` file. I'll read the beginning of the file to see if the define is there.



20/2 2026/01/29 00:36 tool read-file
0m Args:
map[File:drivers/hid/hid-ft260.c]

Results:
map[Contents:// SPDX-License-Identifier: GPL-2.0-only
/*
 * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge
 *
 * Copyright (c) 2021, Michael Zaidman <michaelz@xsightlabs.com>
 *
 * Data Sheet:
 *   https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf
 */

#include "hid-ids.h"
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/usb.h>

#ifdef DEBUG
static int ft260_debug = 1;
#else
static int ft260_debug;
#endif
module_param_named(debug, ft260_debug, int, 0600);
MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");

#define ft260_dbg(format, arg...)					  \
	do {								  \
		if (ft260_debug)					  \
			pr_info("%s: " format, __func__, ##arg);	  \
	} while (0)

#define FT260_REPORT_MAX_LENGTH (64)
#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)

#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */

/*
 * The ft260 input report format defines 62 bytes for the data payload, but
 * when requested 62 bytes, the controller returns 60 and 2 in separate input
 * reports. To achieve better performance with the multi-report read data
 * transfers, we set the maximum read payload length to a multiple of 60.
 * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
 * which is excessive; On the other hand, some higher layer drivers like at24
 * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
 * of I2C for potentially troublesome amounts of time, we select the maximum
 * read payload length to be 180 bytes.
*/
#define FT260_RD_DATA_MAX (180)
#define FT260_WR_DATA_MAX (60)

/*
 * Device interface configuration.
 * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins.
 * First implementes USB HID to I2C bridge function and
 * second - USB HID to UART bridge function.
 */
enum {
	FT260_MODE_ALL			= 0x00,
	FT260_MODE_I2C			= 0x01,
	FT260_MODE_UART			= 0x02,
	FT260_MODE_BOTH			= 0x03,
};

/* Control pipe */
enum {
	FT260_GET_RQST_TYPE		= 0xA1,
	FT260_GET_REPORT		= 0x01,
	FT260_SET_RQST_TYPE		= 0x21,
	FT260_SET_REPORT		= 0x09,
	FT260_FEATURE			= 0x03,
};

/* Report IDs / Feature In */
enum {
	FT260_CHIP_VERSION		= 0xA0,
	FT260_SYSTEM_SETTINGS		= 0xA1,
	FT260_I2C_STATUS		= 0xC0,
	FT260_I2C_READ_REQ		= 0xC2,
	FT260_I2C_REPORT_MIN		= 0xD0,
	FT260_I2C_REPORT_MAX		= 0xDE,
	FT260_GPIO			= 0xB0,
	FT260_UART_INTERRUPT_STATUS	= 0xB1,
	FT260_UART_STATUS		= 0xE0,
	FT260_UART_RI_DCD_STATUS	= 0xE1,
	FT260_UART_REPORT		= 0xF0,
};

/* Feature Out */
enum {
	FT260_SET_CLOCK			= 0x01,
	FT260_SET_I2C_MODE		= 0x02,
	FT260_SET_UART_MODE		= 0x03,
	FT260_ENABLE_INTERRUPT		= 0x05,
	FT260_SELECT_GPIO2_FUNC		= 0x06,
	FT260_ENABLE_UART_DCD_RI	= 0x07,
	FT260_SELECT_GPIOA_FUNC		= 0x08,
	FT260_SELECT_GPIOG_FUNC		= 0x09,
	FT260_SET_INTERRUPT_TRIGGER	= 0x0A,
	FT260_SET_SUSPEND_OUT_POLAR	= 0x0B,
	FT260_ENABLE_UART_RI_WAKEUP	= 0x0C,
	FT260_SET_UART_RI_WAKEUP_CFG	= 0x0D,
	FT260_SET_I2C_RESET		= 0x20,
	FT260_SET_I2C_CLOCK_SPEED	= 0x22,
	FT260_SET_UART_RESET		= 0x40,
	FT260_SET_UART_CONFIG		= 0x41,
	FT260_SET_UART_BAUD_RATE	= 0x42,
	FT260_SET_UART_DATA_BIT		= 0x43,
	FT260_SET_UART_PARITY		= 0x44,
	FT260_SET_UART_STOP_BIT		= 0x45,
	FT260_SET_UART_BREAKING		= 0x46,
	FT260_SET_UART_XON_XOFF		= 0x49,
};

/* Response codes in I2C status report */
enum {
	FT260_I2C_STATUS_SUCCESS	= 0x00,
	FT260_I2C_STATUS_CTRL_BUSY	= 0x01,
	FT260_I2C_STATUS_ERROR		= 0x02,
	FT260_I2C_STATUS_ADDR_NO_ACK	= 0x04,
	FT260_I2C_STATUS_DATA_NO_ACK	= 0x08,
	FT260_I2C_STATUS_ARBITR_LOST	= 0x10,
	FT260_I2C_STATUS_CTRL_IDLE	= 0x20,
	FT260_I2C_STATUS_BUS_BUSY	= 0x40,
};

/* I2C Conditions flags */
enum {
	FT260_FLAG_NONE			= 0x00,
	FT260_FLAG_START		= 0x02,
	FT260_FLAG_START_REPEATED	= 0x03,
	FT260_FLAG_STOP			= 0x04,
	FT260_FLAG_START_STOP		= 0x06,
	FT260_FLAG_START_STOP_REPEATED	= 0x07,
};

#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)

/* Feature In reports */

struct ft260_get_chip_version_report {
	u8 report;		/* FT260_CHIP_VERSION */
	u8 chip_code[4];	/* FTDI chip identification code */
	u8 reserved[8];
} __packed;

struct ft260_get_system_status_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 chip_mode;		/* DCNF0 and DCNF1 status, bits 0-1 */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
	u8 suspend_status;	/* 0 - not suspended, 1 - suspended */
	u8 pwren_status;	/* 0 - FT260 is not ready, 1 - ready */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
	u8 hid_over_i2c_en;	/* 0 - disabled, 1 - enabled */
	u8 gpio2_function;	/* 0 - GPIO,  1 - SUSPOUT, */
				/* 2 - PWREN, 4 - TX_LED */
	u8 gpioA_function;	/* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */
	u8 gpioG_function;	/* 0 - GPIO, 2 - PWREN, */
				/* 5 - RX_LED, 6 - BCD_DET */
	u8 suspend_out_pol;	/* 0 - active-high, 1 - active-low */
	u8 enable_wakeup_int;	/* 0 - disabled, 1 - enabled */
	u8 intr_cond;		/* Interrupt trigger conditions */
	u8 power_saving_en;	/* 0 - disabled, 1 - enabled */
	u8 reserved[10];
} __packed;

struct ft260_get_i2c_status_report {
	u8 report;		/* FT260_I2C_STATUS */
	u8 bus_status;		/* I2C bus status */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
	u8 reserved;
} __packed;

/* Feature Out reports */

struct ft260_set_system_clock_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_CLOCK */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
} __packed;

struct ft260_set_i2c_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_MODE */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
} __packed;

struct ft260_set_uart_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_UART_MODE */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
} __packed;

struct ft260_set_i2c_reset_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_RESET */
} __packed;

struct ft260_set_i2c_speed_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_CLOCK_SPEED */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
} __packed;

/* Data transfer reports */

struct ft260_i2c_write_request_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	u8 length;		/* data payload length */
	u8 data[FT260_WR_DATA_MAX]; /* data payload */
} __packed;

struct ft260_i2c_read_request_report {
	u8 report;		/* FT260_I2C_READ_REQ */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	__le16 length;		/* data payload length */
} __packed;

struct ft260_i2c_input_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 length;		/* data payload length */
	u8 data[2];		/* data payload */
} __packed;

static const struct hid_device_id ft260_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY,
			 USB_DEVICE_ID_FT260) },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(hid, ft260_devices);

struct ft260_device {
	struct i2c_adapter adap;
	struct hid_device *hdev;
	struct completion wait;
	struct mutex lock;
	u8 write_buf[FT260_REPORT_MAX_LENGTH];
	unsigned long need_wakeup_at;
	u8 *read_buf;
	u16 read_idx;
	u16 read_len;
	u16 clock;
};

static int ft260_hid_feature_report_get(struct hid_device *hdev,
					unsigned char report_id, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmalloc(len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT,
				 HID_REQ_GET_REPORT);
	if (likely(ret == len))
		memcpy(data, buf, len);
	else if (ret >= 0)
		ret = -EIO;
	kfree(buf);
	return ret;
}

static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	buf[0] = FT260_SYSTEM_SETTINGS;

	ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
				 HID_REQ_SET_REPORT);

	kfree(buf);
	return ret;
}

static int ft260_i2c_reset(struct hid_device *hdev)
{
	struct ft260_set_i2c_reset_report report;
	int ret;

	report.request = FT260_SET_I2C_RESET;

	ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
	if (ret < 0) {
		hid_err(hdev, "failed to reset I2C controller: %d\n", ret);
		return ret;
	}

	ft260_dbg("done\n");
	return ret;
}

static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
	struct hid_device *hdev = dev->hdev;
	struct ft260_get_i2c_status_report report;
	int ret;

	if (time_is_before_jiffies(dev->need_wakeup_at)) {
		ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
						(u8 *)&report, sizeof(report));
		if (unlikely(ret < 0)) {
			hid_err(hdev, "failed to retrieve status: %d, no wakeup\n",
				ret);
		} else {
			dev->need_wakeup_at = jiffies +
				msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
			ft260_dbg("bus_status %#02x, wakeup\n",
				  report.bus_status);
		}
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
					   (u8 *)&report, sizeof(report));
	if (unlikely(ret < 0)) {
		hid_err(hdev, "failed to retrieve status: %d\n", ret);
		return ret;
	}

	dev->clock = le16_to_cpu(report.clock);
	ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
		  dev->clock);

	if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
		return -EAGAIN;

	/*
	 * The error condition (bit 1) is a status bit reflecting any
	 * error conditions. When any of the bits 2, 3, or 4 are raised
	 * to 1, bit 1 is also set to 1.
	 */
	if (report.bus_status & FT260_I2C_STATUS_ERROR) {
		hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
		return -EIO;
	}

	return 0;
}

static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
				   size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_output_report(hdev, buf, len);

	kfree(buf);
	return ret;
}

static int ft260_hid_output_report_check_status(struct ft260_device *dev,
						u8 *data, int len)
{
	u8 bus_busy;
	int ret, usec, try = 100;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)data;

	ret = ft260_hid_output_report(hdev, data, len);
	if (ret < 0) {
		hid_err(hdev, "%s: failed to start transfer, ret %d\n",
			__func__, ret);
		ft260_i2c_reset(hdev);
		return ret;
	}

	/* transfer time = 1 / clock(KHz) * 9 bits * bytes */
	usec = len * 9000 / dev->clock;
	if (usec > 2000) {
		usec -= 1500;
		usleep_range(usec, usec + 100);
		ft260_dbg("wait %d usec, len %d\n", usec, len);
	}

	/*
	 * Do not check the busy bit for combined transactions
	 * since the controller keeps the bus busy between writing
	 * and reading IOs to ensure an atomic operation.
	 */
	if (rep->flag == FT260_FLAG_START)
		bus_busy = 0;
	else
		bus_busy = FT260_I2C_STATUS_BUS_BUSY;

	do {
		ret = ft260_xfer_status(dev, bus_busy);
		if (ret != -EAGAIN)
			break;
	} while (--try);

	if (ret == 0)
		return 0;

	ft260_i2c_reset(hdev);
	return -EIO;
}

static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
			   int len, u8 flag)
{
	int ret, wr_len, idx = 0;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (len < 1)
		return -EINVAL;

	rep->flag = FT260_FLAG_START;

	do {
		if (len <= FT260_WR_DATA_MAX) {
			wr_len = len;
			if (flag == FT260_FLAG_START_STOP)
				rep->flag |= FT260_FLAG_STOP;
		} else {
			wr_len = FT260_WR_DATA_MAX;
		}

		rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
		rep->address = addr;
		rep->length = wr_len;

		memcpy(rep->data, &data[idx], wr_len);

		ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
			  rep->report, addr, idx, len, wr_len,
			  rep->flag, data[0]);

		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
							   wr_len + 4);
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			return ret;
		}

		len -= wr_len;
		idx += wr_len;
		rep->flag = 0;

	} while (len > 0);

	return 0;
}

static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
			     u8 *data, u8 data_len, u8 flag)
{
	int ret = 0;
	int len = 4;

	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (data_len >= sizeof(rep->data))
		return -EINVAL;

	rep->address = addr;
	rep->data[0] = cmd;
	rep->length = data_len + 1;
	rep->flag = flag;
	len += rep->length;

	rep->report = FT260_I2C_DATA_REPORT_ID(len);

	if (data_len > 0)
		memcpy(&rep->data[1], data, data_len);

	ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
		  rep->report, addr, cmd, rep->length, len);

	ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);

	return ret;
}

static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
			  u16 len, u8 flag)
{
	u16 rd_len;
	u16 rd_data_max = 60;
	int timeout, ret = 0;
	struct ft260_i2c_read_request_report rep;
	struct hid_device *hdev = dev->hdev;
	u8 bus_busy = 0;

	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
		flag = FT260_FLAG_START_REPEATED;
	else
		flag = FT260_FLAG_START;
	do {
		if (len <= rd_data_max) {
			rd_len = len;
			flag |= FT260_FLAG_STOP;
		} else {
			rd_len = rd_data_max;
		}
		rd_data_max = FT260_RD_DATA_MAX;

		rep.report = FT260_I2C_READ_REQ;
		rep.length = cpu_to_le16(rd_len);
		rep.address = addr;
		rep.flag = flag;

		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
			  rep.report, rep.address, len, rd_len, flag);

		reinit_completion(&dev->wait);

		dev->read_idx = 0;
		dev->read_buf = data;
		dev->read_len = rd_len;

		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			goto ft260_i2c_read_exit;
		}

		timeout = msecs_to_jiffies(5000);
		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
			ret = -ETIMEDOUT;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		dev->read_buf = NULL;

		if (flag & FT260_FLAG_STOP)
			bus_busy = FT260_I2C_STATUS_BUS_BUSY;

		ret = ft260_xfer_status(dev, bus_busy);
		if (ret < 0) {
			ret = -EIO;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		len -= rd_len;
		data += rd_len;
		flag = 0;

	} while (len > 0);

ft260_i2c_read_exit:
	dev->read_buf = NULL;
	return ret;
}

/*
 * A random read operation is implemented as a dummy write operation, followed
 * by a current address read operation. The dummy write operation is used to
 * load the target byte address into the current byte address counter, from
 * which the subsequent current address read operation then reads.
 */
static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
{
	int ret;
	int wr_len = msgs[0].len;
	int rd_len = msgs[1].len;
	struct hid_device *hdev = dev->hdev;
	u8 addr = msgs[0].addr;
	u16 read_off = 0;

	if (wr_len > 2) {
		hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
		return -EOPNOTSUPP;
	}

	if (ft260_debug) {
		if (wr_len == 2)
			read_off = be16_to_cpu(*(__be16 *)msgs[0].buf);
		else
			read_off = *msgs[0].buf;

		pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
			read_off, rd_len, wr_len);
	}

	ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
			      FT260_FLAG_START);
	if (ret < 0)
		return ret;

	ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
			     FT260_FLAG_START_STOP_REPEATED);
	if (ret < 0)
		return ret;

	return 0;
}

static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
			  int num)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	if (num == 1) {
		if (msgs->flags & I2C_M_RD)
			ret = ft260_i2c_read(dev, msgs->addr, msgs->buf,
					     msgs->len, FT260_FLAG_START_STOP);
		else
			ret = ft260_i2c_write(dev, msgs->addr, msgs->buf,
					      msgs->len, FT260_FLAG_START_STOP);
		if (ret < 0)
			goto i2c_exit;

	} else {
		/* Combined write then read message */
		ret = ft260_i2c_write_read(dev, msgs);
		if (ret < 0)
			goto i2c_exit;
	}

	ret = num;
i2c_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	ft260_dbg("smbus size %d\n", size);

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "power management error: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	switch (size) {
	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_READ)
			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP);
		else
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START_STOP);
		break;
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_WORD_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd,
						(u8 *)&data->word, 2,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
	default:
		hid_err(hdev, "unsupported smbus transaction size %d\n", size);
		ret = -EOPNOTSUPP;
	}

smbus_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static u32 ft260_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	       I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}

static const struct i2c_adapter_quirks ft260_i2c_quirks = {
	.flags = I2C_AQ_COMB_WRITE_THEN_READ,
	.max_comb_1st_msg_len = 2,
};

static const struct i2c_algorithm ft260_i2c_algo = {
	.master_xfer = ft260_i2c_xfer,
	.smbus_xfer = ft260_smbus_xfer,
	.functionality = ft260_functionality,
};

static int ft260_get_system_config(struct hid_device *hdev,
				   struct ft260_get_system_status_report *cfg)
{
	int ret;
	int len = sizeof(struct ft260_get_system_status_report);

	ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS,
					   (u8 *)cfg, len);
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve system status\n");
		return ret;
	}
	return 0;
}

static int ft260_is_interface_enabled(struct hid_device *hdev)
{
	struct ft260_get_system_status_report cfg;
	struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);
	int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
	int ret;

	ret = ft260_get_system_config(hdev, &cfg);
	if (ret < 0)
		return ret;

	ft260_dbg("interface:  0x%02x\n", interface);
	ft260_dbg("chip mode:  0x%02x\n", cfg.chip_mode);
	ft260_dbg("clock_ctl:  0x%02x\n", cfg.clock_ctl);
	ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable);
	ft260_dbg("uart_mode:  0x%02x\n", cfg.uart_mode);

	switch (cfg.chip_mode) {
	case FT260_MODE_ALL:
	case FT260_MODE_BOTH:
		if (interface == 1)
			hid_info(hdev, "uart interface is not supported\n");
		else
			ret = 1;
		break;
	case FT260_MODE_UART:
		hid_info(hdev, "uart interface is not supported\n");
		break;
	case FT260_MODE_I2C:
		ret = 1;
		break;
	}
	return ret;
}

static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   u8 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", *field);
}

static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   __le16 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field));
}

#define FT260_ATTR_SHOW(name, reptype, id, type, func)			       \
	static ssize_t name##_show(struct device *kdev,			       \
				   struct device_attribute *attr, char *buf)   \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type *field = &rep.name;				       \
		int len = sizeof(rep);					       \
									       \
		return func(hdev, id, (u8 *)&rep, len, field, buf);	       \
	}

#define FT260_SSTAT_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_system_status_report,	       \
				FT260_SYSTEM_SETTINGS, u8, ft260_byte_show)

#define FT260_I2CST_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_i2c_status_report,	       \
				FT260_I2C_STATUS, __le16, ft260_word_show)

#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func)	       \
	static ssize_t name##_store(struct device *kdev,		       \
				    struct device_attribute *attr,	       \
				    const char *buf, size_t count)	       \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type name;						       \
		int ret;						       \
									       \
		if (!func(buf, 10, (ctype *)&name)) {			       \
			rep.name = name;				       \
			rep.report = id;				       \
			rep.request = req;				       \
			ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep,   \
							   sizeof(rep));       \
			if (!ret)					       \
				ret = count;				       \
		} else {						       \
			ret = -EINVAL;					       \
		}							       \
		return ret;						       \
	}

#define FT260_BYTE_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 u8, u8, kstrtou8)

#define FT260_WORD_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 __le16, u16, kstrtou16)

FT260_SSTAT_ATTR_SHOW(chip_mode);
static DEVICE_ATTR_RO(chip_mode);

FT260_SSTAT_ATTR_SHOW(pwren_status);
static DEVICE_ATTR_RO(pwren_status);

FT260_SSTAT_ATTR_SHOW(suspend_status);
static DEVICE_ATTR_RO(suspend_status);

FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en);
static DEVICE_ATTR_RO(hid_over_i2c_en);

FT260_SSTAT_ATTR_SHOW(power_saving_en);
static DEVICE_ATTR_RO(power_saving_en);

FT260_SSTAT_ATTR_SHOW(i2c_enable);
FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report,
		      FT260_SET_I2C_MODE);
static DEVICE_ATTR_RW(i2c_enable);

FT260_SSTAT_ATTR_SHOW(uart_mode);
FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report,
		      FT260_SET_UART_MODE);
static DEVICE_ATTR_RW(uart_mode);

FT260_SSTAT_ATTR_SHOW(clock_ctl);
FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report,
		      FT260_SET_CLOCK);
static DEVICE_ATTR_RW(clock_ctl);

FT260_I2CST_ATTR_SHOW(clock);
FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report,
		      FT260_SET_I2C_CLOCK_SPEED);
static DEVICE_ATTR_RW(clock);

static ssize_t i2c_reset_store(struct device *kdev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	struct hid_device *hdev = to_hid_device(kdev);
	int ret = ft260_i2c_reset(hdev);

	if (ret)
		return ret;
	return count;
}
static DEVICE_ATTR_WO(i2c_reset);

static const struct attribute_group ft260_attr_group = {
	.attrs = (struct attribute *[]) {
		  &dev_attr_chip_mode.attr,
		  &dev_attr_pwren_status.attr,
		  &dev_attr_suspend_status.attr,
		  &dev_attr_hid_over_i2c_en.attr,
		  &dev_attr_power_saving_en.attr,
		  &dev_attr_i2c_enable.attr,
		  &dev_attr_uart_mode.attr,
		  &dev_attr_clock_ctl.attr,
		  &dev_attr_i2c_reset.attr,
		  &dev_attr_clock.attr,
		  NULL
	}
};

static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	struct ft260_device *dev;
	struct ft260_get_chip_version_report version;
	int ret;

	if (!hid_is_usb(hdev))
		return -EINVAL;

	dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	ret = hid_parse(hdev);
	if (ret) {
		hid_err(hdev, "failed to parse HID\n");
		return ret;
	}

	ret = hid_hw_start(hdev, 0);
	if (ret) {
		hid_err(hdev, "failed to start HID HW\n");
		return ret;
	}

	ret = hid_hw_open(hdev);
	if (ret) {
		hid_err(hdev, "failed to open HID HW\n");
		goto err_hid_stop;
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
					   (u8 *)&version, sizeof(version));
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve chip version\n");
		goto err_hid_close;
	}

	hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
		 version.chip_code[0], version.chip_code[1],
		 version.chip_code[2], version.chip_code[3]);

	ret = ft260_is_interface_enabled(hdev);
	if (ret <= 0)
		goto err_hid_close;

	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
		hdev->version >> 8, hdev->version & 0xff, hdev->name,
		hdev->phys);

	hid_set_drvdata(hdev, dev);
	dev->hdev = hdev;
	dev->adap.owner = THIS_MODULE;
	dev->adap.class = I2C_CLASS_HWMON;
	dev->adap.algo = &ft260_i2c_algo;
	dev->adap.quirks = &ft260_i2c_quirks;
	dev->adap.dev.parent = &hdev->dev;
	snprintf(dev->adap.name, sizeof(dev->adap.name),
		 "FT260 usb-i2c bridge");

	mutex_init(&dev->lock);
	init_completion(&dev->wait);

	ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
	if (ret)
		ft260_i2c_reset(hdev);

	i2c_set_adapdata(&dev->adap, dev);
	ret = i2c_add_adapter(&dev->adap);
	if (ret) {
		hid_err(hdev, "failed to add i2c adapter\n");
		goto err_hid_close;
	}

	ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
	if (ret < 0) {
		hid_err(hdev, "failed to create sysfs attrs\n");
		goto err_i2c_free;
	}

	return 0;

err_i2c_free:
	i2c_del_adapter(&dev->adap);
err_hid_close:
	hid_hw_close(hdev);
err_hid_stop:
	hid_hw_stop(hdev);
	return ret;
}

static void ft260_remove(struct hid_device *hdev)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);

	if (!dev)
		return;

	sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
	i2c_del_adapter(&dev->adap);

	hid_hw_close(hdev);
	hid_hw_stop(hdev);
}

static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
			   u8 *data, int size)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);
	struct ft260_i2c_input_report *xfer = (void *)data;

	if (xfer->report >= FT260_I2C_REPORT_MIN &&
	    xfer->report <= FT260_I2C_REPORT_MAX) {
		ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
			  xfer->length);

		if ((dev->read_buf == NULL) ||
		    (xfer->length > dev->read_len - dev->read_idx)) {
			hid_err(hdev, "unexpected report %#02x, length %d\n",
				xfer->report, xfer->length);
			return -1;
		}

		memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
		       xfer->length);
		dev->read_idx += xfer->length;

		if (dev->read_idx == dev->read_len)
			complete(&dev->wait);

	} else {
		hid_err(hdev, "unhandled report %#02x\n", xfer->report);
	}
	return 0;
}

static struct hid_driver ft260_driver = {
	.name		= "ft260",
	.id_table	= ft260_devices,
	.probe		= ft260_probe,
	.remove		= ft260_remove,
	.raw_event	= ft260_raw_event,
};

module_hid_driver(ft260_driver);
MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge");
MODULE_AUTHOR("Michael Zaidman <michael.zaidman@gmail.com>");
MODULE_LICENSE("GPL v2");
]

21/2 2026/01/29 00:36 llm debugger
1m Model:
gemini-3-pro-preview

Tokens:
							input: 31630
							output: 656
							thoughts: 4990
						

Thoughts:
**Analysis of the FT260 SMBus Out-of-Bounds Read Vulnerability**

Okay, here's the breakdown of what happened and why, from my perspective. This KASAN report reveals an out-of-bounds read vulnerability within the FT260 USB-to-I2C driver, specifically within the `ft260_smbus_write` function, triggered when handling I2C block write operations (`I2C_SMBUS_I2C_BLOCK_DATA`).

The heart of the problem lies in insufficient validation within the `ft260_smbus_xfer` function, which, I've confirmed, is the entry point for SMBus transactions in this driver. It doesn't properly check the length byte, `data->block[0]`, provided by the user via the `i2c-dev` interface, before using it as the length argument in `ft260_smbus_write`.  This `data->block[0]` value determines the number of bytes to be transferred from a user-supplied buffer.

The `i2cdev_ioctl_smbus` function, which is the user-space interface, copies data into a local `i2c_smbus_data` union, `temp`, on the stack. The key is that `temp` is of fixed size (34 bytes), yet `i2cdev_ioctl_smbus` trusts the user provided `data->block[0]` in this fixed-size union. The user can craft an I2C SMBus request and specifies the length of the block data.

The report shows that the file defines `FT260_WR_DATA_MAX` to be 60.

Now, let's step through the crash scenario.

1.  A user-space program sends an I2C SMBus write command using `I2C_SMBUS_I2C_BLOCK_DATA` and specifies a length of, say, 42 bytes in `temp.block[0]`. The user-provided data starts at `temp.block+1`, but `sizeof(temp)` is 34 bytes.
2.  The `i2c_smbus_xfer` calls `ft260_smbus_xfer`, which in turn calls `ft260_smbus_write`.
3.  `ft260_smbus_write` is called with a pointer to the *source* data ( `data->block + 1`), and the length specified by `data->block[0]`.
4.  Critically, `ft260_smbus_xfer` in `hid-ft260.c` *does not* validate that the supplied `data->block[0]` is within the bounds of the actual `i2c_smbus_data` union. `ft260_smbus_write` only checks that `data_len` is less than or equal to `FT260_WR_DATA_MAX` (60), but this check is insufficient, because it does not validate the source data.
5.  In the crash scenario, data\_len is 42, but the actual available bytes from the source buffer, `data->block + 1`, is 33 bytes in the stack variable `temp`.
6.  `memcpy` in `ft260_smbus_write` attempts to copy 42 bytes. This results in an out-of-bounds read of 9 bytes (42 - 33).
7.  The KASAN report confirms this by pointing to an address within the stack frame, offset 33 (which corresponds to `temp.block + 1`), with the read of size 42.

The root cause is the missing validation of `data->block[0]` in `ft260_smbus_xfer` before passing the value to `ft260_smbus_write`. Specifically, it needs to be checked against `I2C_SMBUS_BLOCK_MAX` (which is typically 32), and if it exceeds that, it needs to return an error, rather than blindly trusting the user's input. The `ft260_smbus_write` check is important to prevent very large values, but does not prevent OOB reads.

The fix is straightforward: add a check in `ft260_smbus_xfer` for `data->block[0] <= I2C_SMBUS_BLOCK_MAX` before passing `data->block[0]` to `ft260_smbus_write`.

This vulnerability is not just limited to write operations, I should note, similar vulnerabilities exist in the


22/1 2026/01/29 00:37 action kernel-scratch-checkouter
0m Results:
map[KernelScratchSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/tmp/tmp2900743489]

23/1 2026/01/29 00:37 loop
11m
24/2 2026/01/29 00:37 iteration 0
11m
25/3 2026/01/29 00:37 agent patch-generator
2m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with creating a fix for a kernel bug.
Use the codeedit tool to do code edits.
Note: you will not see your changes when looking at the code using codesearch tools.
Your final reply should contain explanation of what you did in the patch and why.



Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash that corresponds to the bug is:

==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002cdfd21 by task syz-executor400/5948

CPU: 0 UID: 0 PID: 5948 Comm: syz-executor400 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f3cef1aa539
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffc58093a58 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f3cef1aa539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc58093bd8
R13: 0000000000000002 R14: 00007ffc58093a80 R15: 00007ffc58093a70
 </TASK>

The buggy address belongs to stack of task syz-executor400/5948
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a vmalloc virtual mapping
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x2948f
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5944, tgid 5944 (syz-executor400), ts 90060265112, free_ts 90040528493
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5942 tgid 5942 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 do_sys_openat2+0xbc/0x230 fs/open.c:1426
 do_sys_open fs/open.c:1436 [inline]
 __do_sys_openat fs/open.c:1452 [inline]
 __se_sys_openat fs/open.c:1447 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1447
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002cdfc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002cdfc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002cdfd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002cdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002cdfe00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================


The explanation of the root cause of the bug is:

The root cause of the crash is a missing validation check in `ft260_smbus_xfer` (in `drivers/hid/hid-ft260.c`) which allows a user-supplied length to exceed the size of the kernel stack buffer `union i2c_smbus_data`.

The crash occurs in `ft260_smbus_write` during a `memcpy` operation. Here is the sequence of events:

1.  The `i2cdev_ioctl_smbus` function (in `drivers/i2c/i2c-dev.c`) copies user-supplied data into a stack variable `temp` of type `union i2c_smbus_data`. This union has a fixed size of 34 bytes (including `I2C_SMBUS_BLOCK_MAX` which is 32). The user can set the length byte `block[0]` to any value, for example 42.
2.  `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which eventually calls the driver callback `ft260_smbus_xfer` with `data` pointing to `temp`.
3.  For the `I2C_SMBUS_I2C_BLOCK_DATA` case (write), `ft260_smbus_xfer` extracts the length from `data->block[0]` and the buffer pointer as `data->block + 1`. It does **not** validate that `data->block[0] <= I2C_SMBUS_BLOCK_MAX`.
4.  It calls `ft260_smbus_write` passing this length (e.g., 42) and the buffer pointer.
5.  `ft260_smbus_write` checks if the length fits into its own hardware report buffer (`sizeof(rep->data)` which is 60 bytes). Since 42 < 60, the check passes.
6.  `ft260_smbus_write` then performs `memcpy(&rep->data[1], data, data_len)`.
    *   `data` points to `temp.block[1]`, which is at offset 1 of the 34-byte `temp` union.
    *   This leaves only 33 valid bytes available to be read from `data` before hitting the end of `temp`.
    *   The `memcpy` tries to read 42 bytes, going 9 bytes past the end of the `temp` object on the stack, triggering the KASAN stack-out-of-bounds report.

The fix is to validate `data->block[0]` in `ft260_smbus_xfer` against `I2C_SMBUS_BLOCK_MAX` (32) for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions.



Reply:
The bug was caused by a missing validation check in `ft260_smbus_xfer` in `drivers/hid/hid-ft260.c`. The function used the length byte `data->block[0]` provided by the user without verifying it against `I2C_SMBUS_BLOCK_MAX` (32). This allowed `ft260_smbus_write` (and potentially `ft260_i2c_read`) to access memory beyond the bounds of the `union i2c_smbus_data` stack variable, leading to a KASAN stack-out-of-bounds error.

I have added checks to `ft260_smbus_xfer` to validate `data->block[0]` before using it in `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions. If the length exceeds `I2C_SMBUS_BLOCK_MAX`, the function now returns `-EINVAL`.

```c
--- drivers/hid/hid-ft260.c
+++ drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
```

26/4 2026/01/29 00:37 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 6574
							output: 22
							thoughts: 228
						

Thoughts:
**Fixing the FT260 SMBus Stack-Out-Of-Bounds Bug**

Okay, so I've got a stack-out-of-bounds bug in `drivers/hid/hid-ft260.c` to tackle. Seems like `ft260_smbus_xfer` is the culprit. The issue stems from the fact that `data->block[0]` - which I'm pretty sure represents the length of the SMBus block data - isn't being validated against `I2C_SMBUS_BLOCK_MAX`. That's a classic. An unchecked length means `ft260_smbus_write` is going to happily copy more data than the buffer can handle, leading to an out-of-bounds read, and potentially a crash or, even worse, a security vulnerability.

Right, so here's the game plan: First, confirm I've got the correct file path. It's usually wise to verify before I go diving in. Then, I need to understand the control flow, specifically how `data->block[0]` gets used within `ft260_smbus_xfer` and then called from `ft260_smbus_write`. I need to figure out the right place to add this length check. It's a simple fix conceptually, but nailing the placement and context is key to avoiding regressions.

Once I have a solid understanding, the final step is adding a validation check to make sure `data->block[0]` is never larger than `I2C_SMBUS_BLOCK_MAX`. I'll probably add a `WARN_ON` to alert development if this limit is exceeded, even in a test case, so we can ensure the code is robust. This is a pretty straightforward fix, but I'll make sure to double-check any related areas of the code, just to be thorough.



27/4 2026/01/29 00:37 tool codesearch-dir-index
0m Args:
map[Dir:drivers/hid/]

Results:
map[Files:[hid-a4tech.c hid-accutouch.c hid-alps.c hid-apple.c hid-appleir.c hid-appletb-bl.c hid-appletb-kbd.c hid-asus.c hid-aureal.c hid-axff.c hid-belkin.c hid-betopff.c hid-bigbenff.c hid-cherry.c hid-chicony.c hid-cmedia.c hid-core.c hid-corsair-void.c hid-corsair.c hid-cougar.c hid-cp2112.c hid-creative-sb0540.c hid-cypress.c hid-debug.c hid-dr.c hid-elan.c hid-elecom.c hid-elo.c hid-emsff.c hid-evision.c hid-ezkey.c hid-ft260.c hid-gaff.c hid-gembird.c hid-generic.c hid-gfrm.c hid-glorious.c hid-goodix-spi.c hid-google-hammer.c hid-google-stadiaff.c hid-gt683r.c hid-gyration.c hid-haptic.c hid-haptic.h hid-holtek-kbd.c hid-holtek-mouse.c hid-holtekff.c hid-hyperv.c hid-icade.c hid-ids.h hid-input-test.c hid-input.c hid-ite.c hid-jabra.c hid-kensington.c hid-keytouch.c hid-kye.c hid-kysona.c hid-lcpower.c hid-led.c hid-lenovo.c hid-letsketch.c hid-lg-g15.c hid-lg.c hid-lg.h hid-lg2ff.c hid-lg3ff.c hid-lg4ff.c hid-lg4ff.h hid-lgff.c hid-logitech-dj.c hid-logitech-hidpp.c hid-macally.c hid-magicmouse.c hid-maltron.c hid-mcp2200.c hid-mcp2221.c hid-megaworld.c hid-mf.c hid-microsoft.c hid-monterey.c hid-multitouch.c hid-nintendo.c hid-nti.c hid-ntrig.c hid-nvidia-shield.c hid-ortek.c hid-penmount.c hid-petalynx.c hid-picolcd.h hid-picolcd_backlight.c hid-picolcd_cir.c hid-picolcd_core.c hid-picolcd_debugfs.c hid-picolcd_fb.c hid-picolcd_lcd.c hid-picolcd_leds.c hid-pl.c hid-plantronics.c hid-playstation.c hid-primax.c hid-prodikeys.c hid-pxrc.c hid-quirks.c hid-razer.c hid-redragon.c hid-retrode.c hid-rmi.c hid-roccat-arvo.c hid-roccat-arvo.h hid-roccat-common.c hid-roccat-common.h hid-roccat-isku.c hid-roccat-isku.h hid-roccat-kone.c hid-roccat-kone.h hid-roccat-koneplus.c hid-roccat-koneplus.h hid-roccat-konepure.c hid-roccat-kovaplus.c hid-roccat-kovaplus.h hid-roccat-lua.c hid-roccat-lua.h hid-roccat-pyra.c hid-roccat-pyra.h hid-roccat-ryos.c hid-roccat-savu.c hid-roccat-savu.h hid-roccat.c hid-saitek.c hid-samsung.c hid-semitek.c hid-sensor-custom.c hid-sensor-hub.c hid-sigmamicro.c hid-sjoy.c hid-sony.c hid-speedlink.c hid-steam.c hid-steelseries.c hid-sunplus.c hid-thrustmaster.c hid-tivo.c hid-tmff.c hid-topre.c hid-topseed.c hid-twinhan.c hid-u2fzero.c hid-uclogic-core-test.c hid-uclogic-core.c hid-uclogic-params-test.c hid-uclogic-params.c hid-uclogic-params.h hid-uclogic-rdesc-test.c hid-uclogic-rdesc.c hid-uclogic-rdesc.h hid-udraw-ps3.c hid-universal-pidff.c hid-viewsonic.c hid-vivaldi-common.c hid-vivaldi-common.h hid-vivaldi.c hid-vrc2.c hid-waltop.c hid-wiimote-core.c hid-wiimote-debug.c hid-wiimote-modules.c hid-wiimote.h hid-winwing.c hid-xiaomi.c hid-xinmo.c hid-zpff.c hid-zydacron.c hidraw.c uhid.c wacom.h wacom_sys.c wacom_wac.c wacom_wac.h] Subdirs:[amd-sfh-hid bpf i2c-hid intel-ish-hid intel-thc-hid surface-hid usbhid]]

28/4 2026/01/29 00:37 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 9118
							output: 51
							thoughts: 37
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing the USB-to-SMBus Driver**

Alright, let's dive in. First, I see `hid-ft260.c` in the listing. That immediately signals a HID-based implementation for the FT260 chip, likely interacting over USB. Now, given the context, I'm expecting to find the SMBus transfer functions within that driver. So, I need to pinpoint `ft260_smbus_xfer`. That function call is going to be the central point to understand how SMBus transactions are being constructed and executed over this particular USB interface. I'll be looking for any device-specific quirks and error handling there, as this is where the rubber meets the road.



29/4 2026/01/29 00:37 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:true Name:ft260_smbus_xfer]

Results:
map[SourceCode: 652:	static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 653:				    char read_write, u8 cmd, int size,
 654:				    union i2c_smbus_data *data)
 655:	{
 656:		int ret;
 657:		struct ft260_device *dev = i2c_get_adapdata(adapter);
 658:		struct hid_device *hdev = dev->hdev;
 659:	
 660:		ft260_dbg("smbus size %d\n", size);
 661:	
 662:		mutex_lock(&dev->lock);
 663:	
 664:		ret = hid_hw_power(hdev, PM_HINT_FULLON);
 665:		if (ret < 0) {
 666:			hid_err(hdev, "power management error: %d\n", ret);
 667:			mutex_unlock(&dev->lock);
 668:			return ret;
 669:		}
 670:	
 671:		switch (size) {
 672:		case I2C_SMBUS_BYTE:
 673:			if (read_write == I2C_SMBUS_READ)
 674:				ret = ft260_i2c_read(dev, addr, &data->byte, 1,
 675:						     FT260_FLAG_START_STOP);
 676:			else
 677:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 678:							FT260_FLAG_START_STOP);
 679:			break;
 680:		case I2C_SMBUS_BYTE_DATA:
 681:			if (read_write == I2C_SMBUS_READ) {
 682:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 683:							FT260_FLAG_START);
 684:				if (ret)
 685:					goto smbus_exit;
 686:	
 687:				ret = ft260_i2c_read(dev, addr, &data->byte, 1,
 688:						     FT260_FLAG_START_STOP_REPEATED);
 689:			} else {
 690:				ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
 691:							FT260_FLAG_START_STOP);
 692:			}
 693:			break;
 694:		case I2C_SMBUS_WORD_DATA:
 695:			if (read_write == I2C_SMBUS_READ) {
 696:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 697:							FT260_FLAG_START);
 698:				if (ret)
 699:					goto smbus_exit;
 700:	
 701:				ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
 702:						     FT260_FLAG_START_STOP_REPEATED);
 703:			} else {
 704:				ret = ft260_smbus_write(dev, addr, cmd,
 705:							(u8 *)&data->word, 2,
 706:							FT260_FLAG_START_STOP);
 707:			}
 708:			break;
 709:		case I2C_SMBUS_BLOCK_DATA:
 710:			if (read_write == I2C_SMBUS_READ) {
 711:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 712:							FT260_FLAG_START);
 713:				if (ret)
 714:					goto smbus_exit;
 715:	
 716:				ret = ft260_i2c_read(dev, addr, data->block,
 717:						     data->block[0] + 1,
 718:						     FT260_FLAG_START_STOP_REPEATED);
 719:			} else {
 720:				ret = ft260_smbus_write(dev, addr, cmd, data->block,
 721:							data->block[0] + 1,
 722:							FT260_FLAG_START_STOP);
 723:			}
 724:			break;
 725:		case I2C_SMBUS_I2C_BLOCK_DATA:
 726:			if (read_write == I2C_SMBUS_READ) {
 727:				ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 728:							FT260_FLAG_START);
 729:				if (ret)
 730:					goto smbus_exit;
 731:	
 732:				ret = ft260_i2c_read(dev, addr, data->block + 1,
 733:						     data->block[0],
 734:						     FT260_FLAG_START_STOP_REPEATED);
 735:			} else {
 736:				ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
 737:							data->block[0],
 738:							FT260_FLAG_START_STOP);
 739:			}
 740:			break;
 741:		default:
 742:			hid_err(hdev, "unsupported smbus transaction size %d\n", size);
 743:			ret = -EOPNOTSUPP;
 744:		}
 745:	
 746:	smbus_exit:
 747:		hid_hw_power(hdev, PM_HINT_NORMAL);
 748:		mutex_unlock(&dev->lock);
 749:		return ret;
 750:	}
 SourceFile:drivers/hid/hid-ft260.c]

30/4 2026/01/29 00:37 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 11103
							output: 26
							thoughts: 605
						

Thoughts:
**Vulnerability Analysis of I2C SMBus Block Data Handling**

Okay, so I'm looking at a potential vulnerability in the FT260 driver, specifically related to how it handles I2C SMBus block data transfers. The key areas of concern are the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases, defined around lines 709 and 725, respectively.

The core issue appears to be a lack of input validation on the length byte (`data->block[0]`) provided by the user. In both cases, this byte is directly used as the length of the data to be transferred, without any verification to ensure it's within acceptable bounds.

For the `I2C_SMBUS_BLOCK_DATA` case, the code snippet is:
```c
720:				ret = ft260_smbus_write(dev, addr, cmd, data->block,
721:							data->block[0] + 1,
722:							FT260_FLAG_START_STOP);
```

Here, the length passed to `ft260_smbus_write` is `data->block[0] + 1`, where `data->block` is a `u8` array and `data->block[0]` supposedly contains the length of the subsequent data.

The more concerning case, as highlighted in the bug report, is `I2C_SMBUS_I2C_BLOCK_DATA`:

```c
736:				ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
737:							data->block[0],
738:							FT260_FLAG_START_STOP);
```

Here, `ft260_smbus_write` is called with a length equal to `data->block[0]`. If a malicious user supplies a large value for `data->block[0]` (let's say 42), `ft260_smbus_write` is called with that length, and the data pointer is offset by one byte (`data->block + 1`). This is a potential overflow scenario because the code then performs a `memcpy` in `ft260_smbus_write` using the unchecked length provided in `data->block[0]`. The size of the `union i2c_smbus_data` data buffer is crucial, so I need to check its definition to understand the buffer size limitations. I'll need to locate its definition, probably in `include/linux/i2c.h` or somewhere similar, to confirm the potential for buffer overflow. I'll use a `codesearch` command or similar, to inspect the layout.



31/4 2026/01/29 00:37 tool codesearch-struct-layout
0m Args:
map[Name:i2c_smbus_data]

Results:
map[Fields:[map[Name:byte OffsetBits:0 SizeBits:8] map[Name:word OffsetBits:0 SizeBits:16] map[Name:block OffsetBits:0 SizeBits:272]]]

32/4 2026/01/29 00:37 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 12420
							output: 51
							thoughts: 1434
						

Thoughts:
**Analyzing the Potential I2C SMBus Driver Vulnerability**

Okay, so `SizeBits` 272 translates to 34 bytes, which is interesting.  The `block` is defined as `u8 block[I2C_SMBUS_BLOCK_MAX + 2]`, and if `I2C_SMBUS_BLOCK_MAX` is 32, then `block` indeed has a size of 34 bytes, which makes sense. `block[0]` is the length, and `block[1]` to `block[32]` are data, with `block[33]` being the extra byte. So, as the crash report indicated, a fixed size of 34 bytes. A user can specify `block[0]` to any value, for example 42.

The core of the problem lies in `ft260_smbus_write` and `memcpy`.  `memcpy(&rep->data[1], data, data_len)` is the key. `data` points to `temp.block[1]` and we have 33 valid bytes to work with. If the length byte `data->block[0]` is set to, say, 42, then `memcpy` tries to read 42 bytes from a buffer of size 33, leading to an out-of-bounds read and a crash. Thus, a check on `data->block[0]` is needed. It must be <= `I2C_SMBUS_BLOCK_MAX` (32).

Now, let's drill down into the vulnerable code sections:

In `I2C_SMBUS_BLOCK_DATA`, `ft260_smbus_write` is called with `data->block` and the length `data->block[0] + 1`. This means the length is being passed, but the base pointer `data->block` points to the *beginning* of the buffer, not offset 1. So if `data->block[0]` is 32, the length is 33 which is safe, and if `data->block[0]` is 42, the length passed is 43, which is an overflow and a vulnerability.

In `I2C_SMBUS_I2C_BLOCK_DATA`, the `memcpy` uses `data->block + 1` and the length is `data->block[0]`. This is even more straightforward; the pointer is offset by one. So, 32 bytes copies fine but 42 bytes overflows the buffer.

Therefore, for both cases, we need to validate that `data->block[0]` is not greater than `I2C_SMBUS_BLOCK_MAX`.

Now let's consider the `I2C_SMBUS_BLOCK_DATA` READ operation.  Here, `ft260_i2c_read` is called with `data->block` and length `data->block[0] + 1`.

Wait. This is where I have to pause and think. Is `data->block[0]` *always* the length the caller wants to read *in a read*? I believe, in general, `data->block[0]` is the length that the *slave* will respond with for a block read; the caller provides the *buffer* to receive the response. I really need to verify how `ft260_i2c_read` treats its arguments in the context of a block read. I should probably consult the I2C SMBus documentation or the i2c-core-smbus.c file.

But the code here shows `data->block[0] + 1` being used as the length to read, implying the *caller* specifies the buffer size. If `data->block[0]` contains garbage data (uninitialized memory), then the driver could be told to read arbitrary bytes, causing a buffer overflow. This seems like a potential bug. For the read, the length is not known in advance - the slave will tell. So how does `ft260` handle this situation? I need to know how the length is passed to `ft260_i2c_read`. It acts as the adapter, it should handle the communication with the device.

I need to dig into `ft260_i2c_read` to be sure.



33/4 2026/01/29 00:38 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:false Name:ft260_i2c_read]

Results:
map[SourceCode:static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
			  u16 len, u8 flag)
{
	u16 rd_len;
	u16 rd_data_max = 60;
	int timeout, ret = 0;
	struct ft260_i2c_read_request_report rep;
	struct hid_device *hdev = dev->hdev;
	u8 bus_busy = 0;

	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
		flag = FT260_FLAG_START_REPEATED;
	else
		flag = FT260_FLAG_START;
	do {
		if (len <= rd_data_max) {
			rd_len = len;
			flag |= FT260_FLAG_STOP;
		} else {
			rd_len = rd_data_max;
		}
		rd_data_max = FT260_RD_DATA_MAX;

		rep.report = FT260_I2C_READ_REQ;
		rep.length = cpu_to_le16(rd_len);
		rep.address = addr;
		rep.flag = flag;

		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
			  rep.report, rep.address, len, rd_len, flag);

		reinit_completion(&dev->wait);

		dev->read_idx = 0;
		dev->read_buf = data;
		dev->read_len = rd_len;

		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			goto ft260_i2c_read_exit;
		}

		timeout = msecs_to_jiffies(5000);
		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
			ret = -ETIMEDOUT;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		dev->read_buf = NULL;

		if (flag & FT260_FLAG_STOP)
			bus_busy = FT260_I2C_STATUS_BUS_BUSY;

		ret = ft260_xfer_status(dev, bus_busy);
		if (ret < 0) {
			ret = -EIO;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		len -= rd_len;
		data += rd_len;
		flag = 0;

	} while (len > 0);

ft260_i2c_read_exit:
	dev->read_buf = NULL;
	return ret;
}
 SourceFile:drivers/hid/hid-ft260.c]

34/4 2026/01/29 00:38 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 15643
							output: 50
							thoughts: 989
						

Thoughts:
**Analyzing the I2C Block Data Implementation in the FT260 Driver**

Okay, so I'm digging into this FT260 I2C driver, specifically the `ft260_i2c_read` function. It looks like it performs a straightforward read of a specified number of bytes, which isn't the "block read" I'm used to where the initial byte dictates the subsequent length.

The problem, as I see it, centers around how `ft260_smbus_xfer` handles `I2C_SMBUS_BLOCK_DATA`. Specifically, the code blindly trusts `data->block[0]` to provide the number of bytes to read.  For `I2C_SMBUS_BLOCK_DATA`, it uses `ft260_i2c_read` with a length calculated as `data->block[0] + 1` (line 717). If that `data->block[0]` is larger than `I2C_SMBUS_BLOCK_MAX` which is 34, then it's going to read beyond the allocated buffer, leading to a buffer overflow.  `data->block` is 34 bytes, so a value of 40 would mean an overflow of 7 bytes.

The same concern arises for `I2C_SMBUS_I2C_BLOCK_DATA` (line 733). Here, the code reads from `data->block + 1` with a length of `data->block[0]`.  Again, an overly large `data->block[0]` leads to a buffer overflow.

Therefore, the fix is pretty obvious: I need to add a check within `ft260_smbus_xfer` before using `data->block[0]`.  A simple `if (data->block[0] > I2C_SMBUS_BLOCK_MAX)` check, along with a proper error handling (like `-EINVAL`) and early exit, should be sufficient.

But wait, now I'm thinking about the semantics of an `I2C_SMBUS_BLOCK_DATA` READ operation.  Does the *user* provide the expected length, or does the driver/hardware determine it?  In the standard I2C SMBus emulation (in `i2c-core-smbus.c`), when emulating the block read, the first byte *is* read to determine the length.  However, this FT260 is a *hardware* driver.  The `i2c_adapter` implementation should handle `I2C_SMBUS_BLOCK_DATA`. The user (through something like `i2c-dev`) passes the `data`.

Crucially, for a block read, is `data->block[0]` an *input* from the user?  If it's *not* an input for a READ, then the driver's current approach is incorrect *unless* the FT260 protocol somehow *requires* knowing the read length in advance – which would be unusual for a standard SMBus Block Read.  It seems the driver *assumes* that `data->block[0]` *is* the length. If the FT260 hardware doesn't support "read until stop" or "read block length first", maybe it's actually an "I2C Block Read" where the length is *specified*? Or is `data->block[0]` simply the *expected* length of the read, and thus comes from the user?

The bug report clarifies it: `i2cdev_ioctl_smbus` in `i2c-dev.c` copies user data, including the `block[0]` length byte, into a stack variable. This confirms that `data->block[0]` *does* come from the user. Thus, if `ft260_smbus_xfer` relies on it, it absolutely must be validated.

I should probably also review `ft260_smbus_write` as well, since that's mentioned in the bug report.



35/4 2026/01/29 00:38 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c IncludeLines:false Name:ft260_smbus_write]

Results:
map[SourceCode:static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
			     u8 *data, u8 data_len, u8 flag)
{
	int ret = 0;
	int len = 4;

	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (data_len >= sizeof(rep->data))
		return -EINVAL;

	rep->address = addr;
	rep->data[0] = cmd;
	rep->length = data_len + 1;
	rep->flag = flag;
	len += rep->length;

	rep->report = FT260_I2C_DATA_REPORT_ID(len);

	if (data_len > 0)
		memcpy(&rep->data[1], data, data_len);

	ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
		  rep->report, addr, cmd, rep->length, len);

	ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);

	return ret;
}
 SourceFile:drivers/hid/hid-ft260.c]

36/4 2026/01/29 00:38 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 17912
							output: 27
							thoughts: 1233
						

Thoughts:
**Analyzing and Fixing the FT260 SMBus Out-of-Bounds Read**

Okay, let's break this down. My initial concern lies with `ft260_smbus_write`. I'm seeing that it checks if `data_len` is greater than or equal to `sizeof(rep->data)`. Based on my understanding and a quick verification of the `ft260_i2c_write_request_report` structure, `sizeof(rep->data)` is 60 bytes. The vulnerability becomes apparent when `data_len` is less than 60, specifically where `memcpy(&rep->data[1], data, data_len)` is called.  If the source buffer `data` is smaller than `data_len`, that's an out-of-bounds read, as described in the bug report.

The root cause then seems to be the lack of proper validation on `data_len` relative to the size of the *source* buffer, which is controlled by `i2c_smbus_data`. The `i2c_smbus_data` structure inherently limits block size to `I2C_SMBUS_BLOCK_MAX`.

My fix is going to be implemented in `ft260_smbus_xfer`. I will insert a check to validate that `data->block[0]` (which holds the block size) does not exceed `I2C_SMBUS_BLOCK_MAX` for both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases.

Here's how I envision implementing the check:

```c
		case I2C_SMBUS_BLOCK_DATA:
            if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
                ret = -EINVAL;
                goto smbus_exit;
            }
			if (read_write == I2C_SMBUS_READ) {
...
```

I'll replicate this check for `I2C_SMBUS_I2C_BLOCK_DATA` as well.

Now, a question arises regarding error handling. Should I include a specific error message, even though the bug report doesn't explicitly mention it? The existing code employs `hid_err`. However, when violating `I2C_SMBUS_BLOCK_MAX` from userspace via an ioctl call, it’s conventional to just return `-EINVAL`. The existing code already uses `-EOPNOTSUPP` for unsupported SMBus transaction sizes. Given this, and considering that `I2C_SMBUS_BLOCK_MAX` is being violated, returning `-EINVAL` is appropriate.

I'll prepare the patch with these additions.

One final verification: I need to confirm that `hid-ft260.c` actually includes the necessary header file to define `I2C_SMBUS_BLOCK_MAX`. That constant is defined in `include/uapi/linux/i2c.h` (or just `linux/i2c.h`). Since the driver is using other constants like `I2C_SMBUS_BYTE`, it’s likely that `linux/i2c.h` is already included. I'll double-check the include statements in `hid-ft260.c` to be certain.



37/4 2026/01/29 00:38 tool read-file
0m Args:
map[File:drivers/hid/hid-ft260.c]

Results:
map[Contents:// SPDX-License-Identifier: GPL-2.0-only
/*
 * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge
 *
 * Copyright (c) 2021, Michael Zaidman <michaelz@xsightlabs.com>
 *
 * Data Sheet:
 *   https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf
 */

#include "hid-ids.h"
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/usb.h>

#ifdef DEBUG
static int ft260_debug = 1;
#else
static int ft260_debug;
#endif
module_param_named(debug, ft260_debug, int, 0600);
MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");

#define ft260_dbg(format, arg...)					  \
	do {								  \
		if (ft260_debug)					  \
			pr_info("%s: " format, __func__, ##arg);	  \
	} while (0)

#define FT260_REPORT_MAX_LENGTH (64)
#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)

#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */

/*
 * The ft260 input report format defines 62 bytes for the data payload, but
 * when requested 62 bytes, the controller returns 60 and 2 in separate input
 * reports. To achieve better performance with the multi-report read data
 * transfers, we set the maximum read payload length to a multiple of 60.
 * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
 * which is excessive; On the other hand, some higher layer drivers like at24
 * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
 * of I2C for potentially troublesome amounts of time, we select the maximum
 * read payload length to be 180 bytes.
*/
#define FT260_RD_DATA_MAX (180)
#define FT260_WR_DATA_MAX (60)

/*
 * Device interface configuration.
 * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins.
 * First implementes USB HID to I2C bridge function and
 * second - USB HID to UART bridge function.
 */
enum {
	FT260_MODE_ALL			= 0x00,
	FT260_MODE_I2C			= 0x01,
	FT260_MODE_UART			= 0x02,
	FT260_MODE_BOTH			= 0x03,
};

/* Control pipe */
enum {
	FT260_GET_RQST_TYPE		= 0xA1,
	FT260_GET_REPORT		= 0x01,
	FT260_SET_RQST_TYPE		= 0x21,
	FT260_SET_REPORT		= 0x09,
	FT260_FEATURE			= 0x03,
};

/* Report IDs / Feature In */
enum {
	FT260_CHIP_VERSION		= 0xA0,
	FT260_SYSTEM_SETTINGS		= 0xA1,
	FT260_I2C_STATUS		= 0xC0,
	FT260_I2C_READ_REQ		= 0xC2,
	FT260_I2C_REPORT_MIN		= 0xD0,
	FT260_I2C_REPORT_MAX		= 0xDE,
	FT260_GPIO			= 0xB0,
	FT260_UART_INTERRUPT_STATUS	= 0xB1,
	FT260_UART_STATUS		= 0xE0,
	FT260_UART_RI_DCD_STATUS	= 0xE1,
	FT260_UART_REPORT		= 0xF0,
};

/* Feature Out */
enum {
	FT260_SET_CLOCK			= 0x01,
	FT260_SET_I2C_MODE		= 0x02,
	FT260_SET_UART_MODE		= 0x03,
	FT260_ENABLE_INTERRUPT		= 0x05,
	FT260_SELECT_GPIO2_FUNC		= 0x06,
	FT260_ENABLE_UART_DCD_RI	= 0x07,
	FT260_SELECT_GPIOA_FUNC		= 0x08,
	FT260_SELECT_GPIOG_FUNC		= 0x09,
	FT260_SET_INTERRUPT_TRIGGER	= 0x0A,
	FT260_SET_SUSPEND_OUT_POLAR	= 0x0B,
	FT260_ENABLE_UART_RI_WAKEUP	= 0x0C,
	FT260_SET_UART_RI_WAKEUP_CFG	= 0x0D,
	FT260_SET_I2C_RESET		= 0x20,
	FT260_SET_I2C_CLOCK_SPEED	= 0x22,
	FT260_SET_UART_RESET		= 0x40,
	FT260_SET_UART_CONFIG		= 0x41,
	FT260_SET_UART_BAUD_RATE	= 0x42,
	FT260_SET_UART_DATA_BIT		= 0x43,
	FT260_SET_UART_PARITY		= 0x44,
	FT260_SET_UART_STOP_BIT		= 0x45,
	FT260_SET_UART_BREAKING		= 0x46,
	FT260_SET_UART_XON_XOFF		= 0x49,
};

/* Response codes in I2C status report */
enum {
	FT260_I2C_STATUS_SUCCESS	= 0x00,
	FT260_I2C_STATUS_CTRL_BUSY	= 0x01,
	FT260_I2C_STATUS_ERROR		= 0x02,
	FT260_I2C_STATUS_ADDR_NO_ACK	= 0x04,
	FT260_I2C_STATUS_DATA_NO_ACK	= 0x08,
	FT260_I2C_STATUS_ARBITR_LOST	= 0x10,
	FT260_I2C_STATUS_CTRL_IDLE	= 0x20,
	FT260_I2C_STATUS_BUS_BUSY	= 0x40,
};

/* I2C Conditions flags */
enum {
	FT260_FLAG_NONE			= 0x00,
	FT260_FLAG_START		= 0x02,
	FT260_FLAG_START_REPEATED	= 0x03,
	FT260_FLAG_STOP			= 0x04,
	FT260_FLAG_START_STOP		= 0x06,
	FT260_FLAG_START_STOP_REPEATED	= 0x07,
};

#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)

/* Feature In reports */

struct ft260_get_chip_version_report {
	u8 report;		/* FT260_CHIP_VERSION */
	u8 chip_code[4];	/* FTDI chip identification code */
	u8 reserved[8];
} __packed;

struct ft260_get_system_status_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 chip_mode;		/* DCNF0 and DCNF1 status, bits 0-1 */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
	u8 suspend_status;	/* 0 - not suspended, 1 - suspended */
	u8 pwren_status;	/* 0 - FT260 is not ready, 1 - ready */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
	u8 hid_over_i2c_en;	/* 0 - disabled, 1 - enabled */
	u8 gpio2_function;	/* 0 - GPIO,  1 - SUSPOUT, */
				/* 2 - PWREN, 4 - TX_LED */
	u8 gpioA_function;	/* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */
	u8 gpioG_function;	/* 0 - GPIO, 2 - PWREN, */
				/* 5 - RX_LED, 6 - BCD_DET */
	u8 suspend_out_pol;	/* 0 - active-high, 1 - active-low */
	u8 enable_wakeup_int;	/* 0 - disabled, 1 - enabled */
	u8 intr_cond;		/* Interrupt trigger conditions */
	u8 power_saving_en;	/* 0 - disabled, 1 - enabled */
	u8 reserved[10];
} __packed;

struct ft260_get_i2c_status_report {
	u8 report;		/* FT260_I2C_STATUS */
	u8 bus_status;		/* I2C bus status */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
	u8 reserved;
} __packed;

/* Feature Out reports */

struct ft260_set_system_clock_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_CLOCK */
	u8 clock_ctl;		/* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
} __packed;

struct ft260_set_i2c_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_MODE */
	u8 i2c_enable;		/* 0 - disabled, 1 - enabled */
} __packed;

struct ft260_set_uart_mode_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_UART_MODE */
	u8 uart_mode;		/* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
				/* 3 - XON_XOFF, 4 - No flow control */
} __packed;

struct ft260_set_i2c_reset_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_RESET */
} __packed;

struct ft260_set_i2c_speed_report {
	u8 report;		/* FT260_SYSTEM_SETTINGS */
	u8 request;		/* FT260_SET_I2C_CLOCK_SPEED */
	__le16 clock;		/* I2C bus clock in range 60-3400 KHz */
} __packed;

/* Data transfer reports */

struct ft260_i2c_write_request_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	u8 length;		/* data payload length */
	u8 data[FT260_WR_DATA_MAX]; /* data payload */
} __packed;

struct ft260_i2c_read_request_report {
	u8 report;		/* FT260_I2C_READ_REQ */
	u8 address;		/* 7-bit I2C address */
	u8 flag;		/* I2C transaction condition */
	__le16 length;		/* data payload length */
} __packed;

struct ft260_i2c_input_report {
	u8 report;		/* FT260_I2C_REPORT */
	u8 length;		/* data payload length */
	u8 data[2];		/* data payload */
} __packed;

static const struct hid_device_id ft260_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY,
			 USB_DEVICE_ID_FT260) },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(hid, ft260_devices);

struct ft260_device {
	struct i2c_adapter adap;
	struct hid_device *hdev;
	struct completion wait;
	struct mutex lock;
	u8 write_buf[FT260_REPORT_MAX_LENGTH];
	unsigned long need_wakeup_at;
	u8 *read_buf;
	u16 read_idx;
	u16 read_len;
	u16 clock;
};

static int ft260_hid_feature_report_get(struct hid_device *hdev,
					unsigned char report_id, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmalloc(len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT,
				 HID_REQ_GET_REPORT);
	if (likely(ret == len))
		memcpy(data, buf, len);
	else if (ret >= 0)
		ret = -EIO;
	kfree(buf);
	return ret;
}

static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
					size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	buf[0] = FT260_SYSTEM_SETTINGS;

	ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
				 HID_REQ_SET_REPORT);

	kfree(buf);
	return ret;
}

static int ft260_i2c_reset(struct hid_device *hdev)
{
	struct ft260_set_i2c_reset_report report;
	int ret;

	report.request = FT260_SET_I2C_RESET;

	ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
	if (ret < 0) {
		hid_err(hdev, "failed to reset I2C controller: %d\n", ret);
		return ret;
	}

	ft260_dbg("done\n");
	return ret;
}

static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
	struct hid_device *hdev = dev->hdev;
	struct ft260_get_i2c_status_report report;
	int ret;

	if (time_is_before_jiffies(dev->need_wakeup_at)) {
		ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
						(u8 *)&report, sizeof(report));
		if (unlikely(ret < 0)) {
			hid_err(hdev, "failed to retrieve status: %d, no wakeup\n",
				ret);
		} else {
			dev->need_wakeup_at = jiffies +
				msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
			ft260_dbg("bus_status %#02x, wakeup\n",
				  report.bus_status);
		}
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
					   (u8 *)&report, sizeof(report));
	if (unlikely(ret < 0)) {
		hid_err(hdev, "failed to retrieve status: %d\n", ret);
		return ret;
	}

	dev->clock = le16_to_cpu(report.clock);
	ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
		  dev->clock);

	if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
		return -EAGAIN;

	/*
	 * The error condition (bit 1) is a status bit reflecting any
	 * error conditions. When any of the bits 2, 3, or 4 are raised
	 * to 1, bit 1 is also set to 1.
	 */
	if (report.bus_status & FT260_I2C_STATUS_ERROR) {
		hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
		return -EIO;
	}

	return 0;
}

static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
				   size_t len)
{
	u8 *buf;
	int ret;

	buf = kmemdup(data, len, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	ret = hid_hw_output_report(hdev, buf, len);

	kfree(buf);
	return ret;
}

static int ft260_hid_output_report_check_status(struct ft260_device *dev,
						u8 *data, int len)
{
	u8 bus_busy;
	int ret, usec, try = 100;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)data;

	ret = ft260_hid_output_report(hdev, data, len);
	if (ret < 0) {
		hid_err(hdev, "%s: failed to start transfer, ret %d\n",
			__func__, ret);
		ft260_i2c_reset(hdev);
		return ret;
	}

	/* transfer time = 1 / clock(KHz) * 9 bits * bytes */
	usec = len * 9000 / dev->clock;
	if (usec > 2000) {
		usec -= 1500;
		usleep_range(usec, usec + 100);
		ft260_dbg("wait %d usec, len %d\n", usec, len);
	}

	/*
	 * Do not check the busy bit for combined transactions
	 * since the controller keeps the bus busy between writing
	 * and reading IOs to ensure an atomic operation.
	 */
	if (rep->flag == FT260_FLAG_START)
		bus_busy = 0;
	else
		bus_busy = FT260_I2C_STATUS_BUS_BUSY;

	do {
		ret = ft260_xfer_status(dev, bus_busy);
		if (ret != -EAGAIN)
			break;
	} while (--try);

	if (ret == 0)
		return 0;

	ft260_i2c_reset(hdev);
	return -EIO;
}

static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
			   int len, u8 flag)
{
	int ret, wr_len, idx = 0;
	struct hid_device *hdev = dev->hdev;
	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (len < 1)
		return -EINVAL;

	rep->flag = FT260_FLAG_START;

	do {
		if (len <= FT260_WR_DATA_MAX) {
			wr_len = len;
			if (flag == FT260_FLAG_START_STOP)
				rep->flag |= FT260_FLAG_STOP;
		} else {
			wr_len = FT260_WR_DATA_MAX;
		}

		rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
		rep->address = addr;
		rep->length = wr_len;

		memcpy(rep->data, &data[idx], wr_len);

		ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
			  rep->report, addr, idx, len, wr_len,
			  rep->flag, data[0]);

		ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
							   wr_len + 4);
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			return ret;
		}

		len -= wr_len;
		idx += wr_len;
		rep->flag = 0;

	} while (len > 0);

	return 0;
}

static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
			     u8 *data, u8 data_len, u8 flag)
{
	int ret = 0;
	int len = 4;

	struct ft260_i2c_write_request_report *rep =
		(struct ft260_i2c_write_request_report *)dev->write_buf;

	if (data_len >= sizeof(rep->data))
		return -EINVAL;

	rep->address = addr;
	rep->data[0] = cmd;
	rep->length = data_len + 1;
	rep->flag = flag;
	len += rep->length;

	rep->report = FT260_I2C_DATA_REPORT_ID(len);

	if (data_len > 0)
		memcpy(&rep->data[1], data, data_len);

	ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
		  rep->report, addr, cmd, rep->length, len);

	ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);

	return ret;
}

static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
			  u16 len, u8 flag)
{
	u16 rd_len;
	u16 rd_data_max = 60;
	int timeout, ret = 0;
	struct ft260_i2c_read_request_report rep;
	struct hid_device *hdev = dev->hdev;
	u8 bus_busy = 0;

	if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
		flag = FT260_FLAG_START_REPEATED;
	else
		flag = FT260_FLAG_START;
	do {
		if (len <= rd_data_max) {
			rd_len = len;
			flag |= FT260_FLAG_STOP;
		} else {
			rd_len = rd_data_max;
		}
		rd_data_max = FT260_RD_DATA_MAX;

		rep.report = FT260_I2C_READ_REQ;
		rep.length = cpu_to_le16(rd_len);
		rep.address = addr;
		rep.flag = flag;

		ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
			  rep.report, rep.address, len, rd_len, flag);

		reinit_completion(&dev->wait);

		dev->read_idx = 0;
		dev->read_buf = data;
		dev->read_len = rd_len;

		ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
		if (ret < 0) {
			hid_err(hdev, "%s: failed with %d\n", __func__, ret);
			goto ft260_i2c_read_exit;
		}

		timeout = msecs_to_jiffies(5000);
		if (!wait_for_completion_timeout(&dev->wait, timeout)) {
			ret = -ETIMEDOUT;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		dev->read_buf = NULL;

		if (flag & FT260_FLAG_STOP)
			bus_busy = FT260_I2C_STATUS_BUS_BUSY;

		ret = ft260_xfer_status(dev, bus_busy);
		if (ret < 0) {
			ret = -EIO;
			ft260_i2c_reset(hdev);
			goto ft260_i2c_read_exit;
		}

		len -= rd_len;
		data += rd_len;
		flag = 0;

	} while (len > 0);

ft260_i2c_read_exit:
	dev->read_buf = NULL;
	return ret;
}

/*
 * A random read operation is implemented as a dummy write operation, followed
 * by a current address read operation. The dummy write operation is used to
 * load the target byte address into the current byte address counter, from
 * which the subsequent current address read operation then reads.
 */
static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
{
	int ret;
	int wr_len = msgs[0].len;
	int rd_len = msgs[1].len;
	struct hid_device *hdev = dev->hdev;
	u8 addr = msgs[0].addr;
	u16 read_off = 0;

	if (wr_len > 2) {
		hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
		return -EOPNOTSUPP;
	}

	if (ft260_debug) {
		if (wr_len == 2)
			read_off = be16_to_cpu(*(__be16 *)msgs[0].buf);
		else
			read_off = *msgs[0].buf;

		pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
			read_off, rd_len, wr_len);
	}

	ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
			      FT260_FLAG_START);
	if (ret < 0)
		return ret;

	ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
			     FT260_FLAG_START_STOP_REPEATED);
	if (ret < 0)
		return ret;

	return 0;
}

static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
			  int num)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	if (num == 1) {
		if (msgs->flags & I2C_M_RD)
			ret = ft260_i2c_read(dev, msgs->addr, msgs->buf,
					     msgs->len, FT260_FLAG_START_STOP);
		else
			ret = ft260_i2c_write(dev, msgs->addr, msgs->buf,
					      msgs->len, FT260_FLAG_START_STOP);
		if (ret < 0)
			goto i2c_exit;

	} else {
		/* Combined write then read message */
		ret = ft260_i2c_write_read(dev, msgs);
		if (ret < 0)
			goto i2c_exit;
	}

	ret = num;
i2c_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
			    char read_write, u8 cmd, int size,
			    union i2c_smbus_data *data)
{
	int ret;
	struct ft260_device *dev = i2c_get_adapdata(adapter);
	struct hid_device *hdev = dev->hdev;

	ft260_dbg("smbus size %d\n", size);

	mutex_lock(&dev->lock);

	ret = hid_hw_power(hdev, PM_HINT_FULLON);
	if (ret < 0) {
		hid_err(hdev, "power management error: %d\n", ret);
		mutex_unlock(&dev->lock);
		return ret;
	}

	switch (size) {
	case I2C_SMBUS_BYTE:
		if (read_write == I2C_SMBUS_READ)
			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP);
		else
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START_STOP);
		break;
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, &data->byte, 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_WORD_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd,
						(u8 *)&data->word, 2,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
	default:
		hid_err(hdev, "unsupported smbus transaction size %d\n", size);
		ret = -EOPNOTSUPP;
	}

smbus_exit:
	hid_hw_power(hdev, PM_HINT_NORMAL);
	mutex_unlock(&dev->lock);
	return ret;
}

static u32 ft260_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	       I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}

static const struct i2c_adapter_quirks ft260_i2c_quirks = {
	.flags = I2C_AQ_COMB_WRITE_THEN_READ,
	.max_comb_1st_msg_len = 2,
};

static const struct i2c_algorithm ft260_i2c_algo = {
	.master_xfer = ft260_i2c_xfer,
	.smbus_xfer = ft260_smbus_xfer,
	.functionality = ft260_functionality,
};

static int ft260_get_system_config(struct hid_device *hdev,
				   struct ft260_get_system_status_report *cfg)
{
	int ret;
	int len = sizeof(struct ft260_get_system_status_report);

	ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS,
					   (u8 *)cfg, len);
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve system status\n");
		return ret;
	}
	return 0;
}

static int ft260_is_interface_enabled(struct hid_device *hdev)
{
	struct ft260_get_system_status_report cfg;
	struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);
	int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
	int ret;

	ret = ft260_get_system_config(hdev, &cfg);
	if (ret < 0)
		return ret;

	ft260_dbg("interface:  0x%02x\n", interface);
	ft260_dbg("chip mode:  0x%02x\n", cfg.chip_mode);
	ft260_dbg("clock_ctl:  0x%02x\n", cfg.clock_ctl);
	ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable);
	ft260_dbg("uart_mode:  0x%02x\n", cfg.uart_mode);

	switch (cfg.chip_mode) {
	case FT260_MODE_ALL:
	case FT260_MODE_BOTH:
		if (interface == 1)
			hid_info(hdev, "uart interface is not supported\n");
		else
			ret = 1;
		break;
	case FT260_MODE_UART:
		hid_info(hdev, "uart interface is not supported\n");
		break;
	case FT260_MODE_I2C:
		ret = 1;
		break;
	}
	return ret;
}

static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   u8 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", *field);
}

static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
			   __le16 *field, u8 *buf)
{
	int ret;

	ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
	if (ret < 0)
		return ret;

	return scnprintf(buf, PAGE_SIZE, "%d\n", le16_to_cpu(*field));
}

#define FT260_ATTR_SHOW(name, reptype, id, type, func)			       \
	static ssize_t name##_show(struct device *kdev,			       \
				   struct device_attribute *attr, char *buf)   \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type *field = &rep.name;				       \
		int len = sizeof(rep);					       \
									       \
		return func(hdev, id, (u8 *)&rep, len, field, buf);	       \
	}

#define FT260_SSTAT_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_system_status_report,	       \
				FT260_SYSTEM_SETTINGS, u8, ft260_byte_show)

#define FT260_I2CST_ATTR_SHOW(name)					       \
		FT260_ATTR_SHOW(name, ft260_get_i2c_status_report,	       \
				FT260_I2C_STATUS, __le16, ft260_word_show)

#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func)	       \
	static ssize_t name##_store(struct device *kdev,		       \
				    struct device_attribute *attr,	       \
				    const char *buf, size_t count)	       \
	{								       \
		struct reptype rep;					       \
		struct hid_device *hdev = to_hid_device(kdev);		       \
		type name;						       \
		int ret;						       \
									       \
		if (!func(buf, 10, (ctype *)&name)) {			       \
			rep.name = name;				       \
			rep.report = id;				       \
			rep.request = req;				       \
			ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep,   \
							   sizeof(rep));       \
			if (!ret)					       \
				ret = count;				       \
		} else {						       \
			ret = -EINVAL;					       \
		}							       \
		return ret;						       \
	}

#define FT260_BYTE_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 u8, u8, kstrtou8)

#define FT260_WORD_ATTR_STORE(name, reptype, req)			       \
		FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
				 __le16, u16, kstrtou16)

FT260_SSTAT_ATTR_SHOW(chip_mode);
static DEVICE_ATTR_RO(chip_mode);

FT260_SSTAT_ATTR_SHOW(pwren_status);
static DEVICE_ATTR_RO(pwren_status);

FT260_SSTAT_ATTR_SHOW(suspend_status);
static DEVICE_ATTR_RO(suspend_status);

FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en);
static DEVICE_ATTR_RO(hid_over_i2c_en);

FT260_SSTAT_ATTR_SHOW(power_saving_en);
static DEVICE_ATTR_RO(power_saving_en);

FT260_SSTAT_ATTR_SHOW(i2c_enable);
FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report,
		      FT260_SET_I2C_MODE);
static DEVICE_ATTR_RW(i2c_enable);

FT260_SSTAT_ATTR_SHOW(uart_mode);
FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report,
		      FT260_SET_UART_MODE);
static DEVICE_ATTR_RW(uart_mode);

FT260_SSTAT_ATTR_SHOW(clock_ctl);
FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report,
		      FT260_SET_CLOCK);
static DEVICE_ATTR_RW(clock_ctl);

FT260_I2CST_ATTR_SHOW(clock);
FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report,
		      FT260_SET_I2C_CLOCK_SPEED);
static DEVICE_ATTR_RW(clock);

static ssize_t i2c_reset_store(struct device *kdev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	struct hid_device *hdev = to_hid_device(kdev);
	int ret = ft260_i2c_reset(hdev);

	if (ret)
		return ret;
	return count;
}
static DEVICE_ATTR_WO(i2c_reset);

static const struct attribute_group ft260_attr_group = {
	.attrs = (struct attribute *[]) {
		  &dev_attr_chip_mode.attr,
		  &dev_attr_pwren_status.attr,
		  &dev_attr_suspend_status.attr,
		  &dev_attr_hid_over_i2c_en.attr,
		  &dev_attr_power_saving_en.attr,
		  &dev_attr_i2c_enable.attr,
		  &dev_attr_uart_mode.attr,
		  &dev_attr_clock_ctl.attr,
		  &dev_attr_i2c_reset.attr,
		  &dev_attr_clock.attr,
		  NULL
	}
};

static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	struct ft260_device *dev;
	struct ft260_get_chip_version_report version;
	int ret;

	if (!hid_is_usb(hdev))
		return -EINVAL;

	dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	ret = hid_parse(hdev);
	if (ret) {
		hid_err(hdev, "failed to parse HID\n");
		return ret;
	}

	ret = hid_hw_start(hdev, 0);
	if (ret) {
		hid_err(hdev, "failed to start HID HW\n");
		return ret;
	}

	ret = hid_hw_open(hdev);
	if (ret) {
		hid_err(hdev, "failed to open HID HW\n");
		goto err_hid_stop;
	}

	ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
					   (u8 *)&version, sizeof(version));
	if (ret < 0) {
		hid_err(hdev, "failed to retrieve chip version\n");
		goto err_hid_close;
	}

	hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
		 version.chip_code[0], version.chip_code[1],
		 version.chip_code[2], version.chip_code[3]);

	ret = ft260_is_interface_enabled(hdev);
	if (ret <= 0)
		goto err_hid_close;

	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
		hdev->version >> 8, hdev->version & 0xff, hdev->name,
		hdev->phys);

	hid_set_drvdata(hdev, dev);
	dev->hdev = hdev;
	dev->adap.owner = THIS_MODULE;
	dev->adap.class = I2C_CLASS_HWMON;
	dev->adap.algo = &ft260_i2c_algo;
	dev->adap.quirks = &ft260_i2c_quirks;
	dev->adap.dev.parent = &hdev->dev;
	snprintf(dev->adap.name, sizeof(dev->adap.name),
		 "FT260 usb-i2c bridge");

	mutex_init(&dev->lock);
	init_completion(&dev->wait);

	ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
	if (ret)
		ft260_i2c_reset(hdev);

	i2c_set_adapdata(&dev->adap, dev);
	ret = i2c_add_adapter(&dev->adap);
	if (ret) {
		hid_err(hdev, "failed to add i2c adapter\n");
		goto err_hid_close;
	}

	ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
	if (ret < 0) {
		hid_err(hdev, "failed to create sysfs attrs\n");
		goto err_i2c_free;
	}

	return 0;

err_i2c_free:
	i2c_del_adapter(&dev->adap);
err_hid_close:
	hid_hw_close(hdev);
err_hid_stop:
	hid_hw_stop(hdev);
	return ret;
}

static void ft260_remove(struct hid_device *hdev)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);

	if (!dev)
		return;

	sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
	i2c_del_adapter(&dev->adap);

	hid_hw_close(hdev);
	hid_hw_stop(hdev);
}

static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
			   u8 *data, int size)
{
	struct ft260_device *dev = hid_get_drvdata(hdev);
	struct ft260_i2c_input_report *xfer = (void *)data;

	if (xfer->report >= FT260_I2C_REPORT_MIN &&
	    xfer->report <= FT260_I2C_REPORT_MAX) {
		ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
			  xfer->length);

		if ((dev->read_buf == NULL) ||
		    (xfer->length > dev->read_len - dev->read_idx)) {
			hid_err(hdev, "unexpected report %#02x, length %d\n",
				xfer->report, xfer->length);
			return -1;
		}

		memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
		       xfer->length);
		dev->read_idx += xfer->length;

		if (dev->read_idx == dev->read_len)
			complete(&dev->wait);

	} else {
		hid_err(hdev, "unhandled report %#02x\n", xfer->report);
	}
	return 0;
}

static struct hid_driver ft260_driver = {
	.name		= "ft260",
	.id_table	= ft260_devices,
	.probe		= ft260_probe,
	.remove		= ft260_remove,
	.raw_event	= ft260_raw_event,
};

module_hid_driver(ft260_driver);
MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge");
MODULE_AUTHOR("Michael Zaidman <michael.zaidman@gmail.com>");
MODULE_LICENSE("GPL v2");
]

38/4 2026/01/29 00:38 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 32753
							output: 915
							thoughts: 2127
						

Thoughts:
**Analyzing and Implementing a Fix for FT260 SMBus Block Data Transfer**

Okay, so I'm looking at this FT260 driver code, specifically the `ft260_smbus_xfer` function. The goal is to address a potential vulnerability related to SMBus block data transfers, which is causing a KASAN report.  First, the inclusion of `<linux/i2c.h>` indicates that `I2C_SMBUS_BLOCK_MAX` should be available, which is good.

My initial thought is to modify `ft260_smbus_xfer` as shown in the diff.  The issue appears to be related to both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transfers, and whether or not the length byte, `data->block[0]`, is properly validated.

In the original code, there's no check on the size of the block data.  For the `I2C_SMBUS_BLOCK_DATA` READ case, `data->block[0]` is, in theory, an output from the device. However, this is also passed as an input length parameter later. If `data->block[0]` contains garbage (uninitialized data), it's a huge potential vulnerability because it could lead to arbitrary reads from memory.  The call `ft260_i2c_read(..., data->block[0] + 1, ...)` implies that the number of bytes to read is derived from `data->block[0]`.

The KASAN report, however, points to `ft260_smbus_write`, and that path would be relevant for both `I2C_SMBUS_I2C_BLOCK_DATA` WRITE and `I2C_SMBUS_BLOCK_DATA` WRITE operations. However, if I make the validation for both cases, WRITE and READ paths will be protected.

Now, let's trace this. `ft260_i2c_read` writes data to `dev->read_buf`, which is, in this context, either `data->block` or `data->block+1`. The length, which I call `rd_len`, is passed to `ft260_i2c_read`, based on `data->block[0]`.  If `rd_len` is too large, it could lead to a buffer overflow during data transfer within `ft260_raw_event`. It looks like `ft260_raw_event` copies data to `dev->read_buf`. And it seems that it does a check on `dev->read_len`, which is derived from `rd_len`.
If `dev->read_len` is large (larger than the real buffer size), `ft260_raw_event` happily copies data up to the incorrect `len`. Since `dev->read_buf` points to the `data->block` stack variable, we have a stack buffer overflow.

Therefore, validating that `data->block[0]` is within the bounds of `I2C_SMBUS_BLOCK_MAX` is correct for both the READ and WRITE paths within the FT260 driver.  It covers the reported KASAN issue and mitigates potential stack buffer overflows on reads as well.

I had to clarify the use of `data->block[0]` in the `i2c-dev.c` ioctl function. Turns out, it *does* copy user space data into `data`, even for `I2C_SMBUS_BLOCK_DATA` READ operations.  Thus, the validation of `data->block[0]` is *absolutely* necessary for both the read and write paths, making this fix a critical security measure.

So, I am going to apply the fix now, which is to check `data->block[0] > I2C_SMBUS_BLOCK_MAX`.



39/4 2026/01/29 00:39 tool codeeditor
0m Args:
map[CurrentCode:	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
 NewCode:	case I2C_SMBUS_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block,
						data->block[0] + 1,
						FT260_FLAG_START_STOP);
		}
		break;
	case I2C_SMBUS_I2C_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
						FT260_FLAG_START);
			if (ret)
				goto smbus_exit;

			ret = ft260_i2c_read(dev, addr, data->block + 1,
					     data->block[0],
					     FT260_FLAG_START_STOP_REPEATED);
		} else {
			ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
						data->block[0],
						FT260_FLAG_START_STOP);
		}
		break;
 SourceFile:drivers/hid/hid-ft260.c]

Results:
map[]

40/4 2026/01/29 00:39 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 36660
							output: 552
							thoughts: 384
						

Thoughts:
**My Assessment of the Fix**

Okay, so I've implemented the suggested fix for that KASAN stack-out-of-bounds error. The core of it was pretty straightforward, and I'm confident in the solution. I went in and added a crucial bounds check within the `ft260_smbus_xfer` function. Specifically, I'm now validating the value of `data->block[0]` – the byte that specifies the block data length – against the defined `I2C_SMBUS_BLOCK_MAX` constant (which, as I recall, is 32 bytes). This check is only implemented for the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transaction types.

The aim, as per the bug report, was to prevent any potential out-of-bounds memory access within the subsequent `ft260_smbus_write` and `ft260_i2c_read` functions. By ensuring that `data->block[0]` doesn't exceed the allocated buffer size (34 bytes), we're effectively safeguarding against the underlying KASAN issue. The fix involves, in effect, performing a simple length check before the actual transaction. If the length is invalid, the operation is immediately aborted with an `EINVAL` error.

I took the original code, something like this:

```c
	case I2C_SMBUS_BLOCK_DATA:
		if (read_write == I2C_SMBUS_READ) {
            ...
```

And modified it to:

```c
	case I2C_SMBUS_BLOCK_DATA:
		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
			ret = -EINVAL;
			goto smbus_exit;
		}

		if (read_write == I2C_SMBUS_READ) {
            ...
```

I've done the same for the `I2C_SMBUS_I2C_BLOCK_DATA` case. So, it's a direct implementation of the suggested fix. It seems clear and efficient, and I'm satisfied that it addresses the reported vulnerability. I'd consider this a solid implementation.



41/3 2026/01/29 00:39 action test-patch
9m Results:
map[PatchDiff:diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
 TestError:]

42/1 2026/01/29 00:49 action get-maintainers
0m Results:
map[Recipients:[map[Email:bentiss@kernel.org Name:Benjamin Tissoires To:true] map[Email:jikos@kernel.org Name:Jiri Kosina To:true] map[Email:linux-i2c@vger.kernel.org Name: To:true] map[Email:linux-input@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:michael.zaidman@gmail.com Name:Michael Zaidman To:true]]]

43/1 2026/01/29 00:49 agent description-generator
0m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with writing a commit description for
a kernel bug fixing commit. The description should start with a one-line summary,
and then include description of the bug being fixed, and how it's fixed by the provided patch.
Your final reply should contain only the text of the commit description.
Phrase the one-line summary so that it is not longer than 72 characters.
The rest of the description must be word-wrapped at 72 characters.

Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash that corresponds to the bug is:

==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90002cdfd21 by task syz-executor400/5948

CPU: 0 UID: 0 PID: 5948 Comm: syz-executor400 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
 ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
 __i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
 i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
 i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
 i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f3cef1aa539
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffc58093a58 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f3cef1aa539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffc58093bd8
R13: 0000000000000002 R14: 00007ffc58093a80 R15: 00007ffc58093a70
 </TASK>

The buggy address belongs to stack of task syz-executor400/5948
 and is located at offset 33 in frame:
 i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317

This frame has 1 object:
 [32, 66) 'temp'

The buggy address belongs to a vmalloc virtual mapping
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x2948f
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5944, tgid 5944 (syz-executor400), ts 90060265112, free_ts 90040528493
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x234/0x290 mm/page_alloc.c:1884
 prep_new_page mm/page_alloc.c:1892 [inline]
 get_page_from_freelist+0x24e4/0x2580 mm/page_alloc.c:3945
 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5240
 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
 alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
 alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
 vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
 __vmalloc_area_node mm/vmalloc.c:3863 [inline]
 __vmalloc_node_range_noprof+0x919/0x16a0 mm/vmalloc.c:4051
 __vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4111
 alloc_thread_stack_node kernel/fork.c:354 [inline]
 dup_task_struct+0x250/0x9b0 kernel/fork.c:923
 copy_process+0x4e7/0x3950 kernel/fork.c:2052
 kernel_clone+0x21e/0x820 kernel/fork.c:2651
 __do_sys_clone kernel/fork.c:2792 [inline]
 __se_sys_clone kernel/fork.c:2776 [inline]
 __x64_sys_clone+0x190/0x1f0 kernel/fork.c:2776
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5942 tgid 5942 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xbc4/0xd40 mm/page_alloc.c:2973
 discard_slab mm/slub.c:3346 [inline]
 __put_partials+0x146/0x170 mm/slub.c:3886
 __slab_free+0x294/0x320 mm/slub.c:5956
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4953 [inline]
 slab_alloc_node mm/slub.c:5263 [inline]
 kmem_cache_alloc_noprof+0x37d/0x710 mm/slub.c:5270
 getname_flags+0xb8/0x540 fs/namei.c:146
 getname include/linux/fs.h:2498 [inline]
 do_sys_openat2+0xbc/0x230 fs/open.c:1426
 do_sys_open fs/open.c:1436 [inline]
 __do_sys_openat fs/open.c:1452 [inline]
 __se_sys_openat fs/open.c:1447 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1447
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffffc90002cdfc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc90002cdfc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc90002cdfd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc90002cdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc90002cdfe00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================


The explanation of the root cause of the bug is:

The root cause of the crash is a missing validation check in `ft260_smbus_xfer` (in `drivers/hid/hid-ft260.c`) which allows a user-supplied length to exceed the size of the kernel stack buffer `union i2c_smbus_data`.

The crash occurs in `ft260_smbus_write` during a `memcpy` operation. Here is the sequence of events:

1.  The `i2cdev_ioctl_smbus` function (in `drivers/i2c/i2c-dev.c`) copies user-supplied data into a stack variable `temp` of type `union i2c_smbus_data`. This union has a fixed size of 34 bytes (including `I2C_SMBUS_BLOCK_MAX` which is 32). The user can set the length byte `block[0]` to any value, for example 42.
2.  `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which eventually calls the driver callback `ft260_smbus_xfer` with `data` pointing to `temp`.
3.  For the `I2C_SMBUS_I2C_BLOCK_DATA` case (write), `ft260_smbus_xfer` extracts the length from `data->block[0]` and the buffer pointer as `data->block + 1`. It does **not** validate that `data->block[0] <= I2C_SMBUS_BLOCK_MAX`.
4.  It calls `ft260_smbus_write` passing this length (e.g., 42) and the buffer pointer.
5.  `ft260_smbus_write` checks if the length fits into its own hardware report buffer (`sizeof(rep->data)` which is 60 bytes). Since 42 < 60, the check passes.
6.  `ft260_smbus_write` then performs `memcpy(&rep->data[1], data, data_len)`.
    *   `data` points to `temp.block[1]`, which is at offset 1 of the 34-byte `temp` union.
    *   This leaves only 33 valid bytes available to be read from `data` before hitting the end of `temp`.
    *   The `memcpy` tries to read 42 bytes, going 9 bytes past the end of the `temp` object on the stack, triggering the KASAN stack-out-of-bounds report.

The fix is to validate `data->block[0]` in `ft260_smbus_xfer` against `I2C_SMBUS_BLOCK_MAX` (32) for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions.

The diff of the bug fix is:

diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80..6b4fbce5c 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);


Additional description of the patch:

The bug was caused by a missing validation check in `ft260_smbus_xfer` in `drivers/hid/hid-ft260.c`. The function used the length byte `data->block[0]` provided by the user without verifying it against `I2C_SMBUS_BLOCK_MAX` (32). This allowed `ft260_smbus_write` (and potentially `ft260_i2c_read`) to access memory beyond the bounds of the `union i2c_smbus_data` stack variable, leading to a KASAN stack-out-of-bounds error.

I have added checks to `ft260_smbus_xfer` to validate `data->block[0]` before using it in `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions. If the length exceeds `I2C_SMBUS_BLOCK_MAX`, the function now returns `-EINVAL`.

```c
--- drivers/hid/hid-ft260.c
+++ drivers/hid/hid-ft260.c
@@ -707,6 +707,11 @@
 		}
 		break;
 	case I2C_SMBUS_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
@@ -723,6 +728,11 @@
 		}
 		break;
 	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EINVAL;
+			goto smbus_exit;
+		}
+
 		if (read_write == I2C_SMBUS_READ) {
 			ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
 						FT260_FLAG_START);
```



Reply:
HID: ft260: fix stack-out-of-bounds in ft260_smbus_write

A KASAN stack-out-of-bounds error was observed in ft260_smbus_write().
The issue occurs because the user-supplied length in data->block[0] is
not validated against I2C_SMBUS_BLOCK_MAX (32) in ft260_smbus_xfer
before being used.

Although ft260_smbus_write() checks the length against its own hardware
report buffer size (60 bytes), it does not verify if the source buffer
(from the stack-allocated i2c_smbus_data union) is large enough.

When data->block[0] exceeds I2C_SMBUS_BLOCK_MAX, the memcpy in
ft260_smbus_write() reads past the end of the i2c_smbus_data object.

Fix this by validating the block length against I2C_SMBUS_BLOCK_MAX for
I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA cases.

44/2 2026/01/29 00:49 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 7413
							output: 258
							thoughts: 2582
						

Thoughts:
**Commit Summary and Analysis**

Okay, here's how I'd approach this. I need to produce a commit message for a kernel bug fix related to an out-of-bounds read in the `ft260` driver. First, the crash is KASAN-detected, a stack-out-of-bounds read within `ft260_smbus_write`. The root cause? `ft260_smbus_xfer` doesn't validate the length field (`data->block[0]`) against the `I2C_SMBUS_BLOCK_MAX` limit (32 bytes). This means `ft260_smbus_write` attempts an `memcpy` beyond the boundaries of the stack-allocated `i2c_smbus_data` object, which causes the crash when an invalid length (e.g., 42) is provided. The fix is straightforward: add a check within `ft260_smbus_xfer` to ensure that `data->block[0]` is not greater than `I2C_SMBUS_BLOCK_MAX` in both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases.

**Here's the commit I'm drafting:**

```
Subject: HID: ft260: fix stack-out-of-bounds in ft260_smbus_write

Body:
A KASAN stack-out-of-bounds error was observed in ft260_smbus_write().
The issue occurs because the user-supplied length in data->block[0] is
not validated against I2C_SMBUS_BLOCK_MAX (32) in ft260_smbus_xfer
before being used.

Although ft260_smbus_write() checks the length against its own hardware
report buffer size (60 bytes), it does not verify if the source buffer
(from the stack-allocated i2c_smbus_data union) is large enough.

When data->block[0] exceeds I2C_SMBUS_BLOCK_MAX, the memcpy in
ft260_smbus_write() reads past the end of the i2c_smbus_data object.

Fix this by validating the block length against I2C_SMBUS_BLOCK_MAX for
I2C_SMBUS_BLOCK_DATA and I2C_SMBUS_I2C_BLOCK_DATA cases.
```

I'm confident this summary is accurate, complete, and clearly explains the issue and the fix. The added explanation regarding the internal buffer size check adds crucial context as to why `ft260_smbus_write` itself isn't sufficient to prevent the bug. The subject line is concise and descriptive. The text clearly shows the bug, it's consequences and the solution.



LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)