syzbot


ID Workflow Result Correct Bug Created Started Finished Revision Error
0be06909-40f3-46f2-bb52-643b2c2793d4 patching-patching-nogikh KASAN: stack-out-of-bounds Read in ft260_smbus_write 2026/01/29 14:28 2026/01/29 14:28 2026/01/29 15:32 63a5f77ffe6c631f8781efb082e741722cdf8c6a+


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 report was observed in ft260_smbus_write.
The crash is triggered by a user-supplied length in I2C_SMBUS_BLOCK_DATA
or I2C_SMBUS_I2C_BLOCK_DATA transactions that exceeds the size of the
data->block buffer.

The ft260_smbus_xfer function in drivers/hid/hid-ft260.c retrieves the
length from data->block[0]. This value is passed to ft260_smbus_write
which then performs a memcpy from the stack-allocated data buffer. The
i2c_smbus_data union in i2cdev_ioctl_smbus has a block size of
I2C_SMBUS_BLOCK_MAX + 2 (34 bytes). If data->block[0] is large (e.g.,
42), the memcpy reads past the end of the stack variable.

Add a check in ft260_smbus_xfer to ensure that data->block[0] does
not exceed I2C_SMBUS_BLOCK_MAX before processing the data.


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 14:28 flow patching-patching-nogikh
1h03m 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 report was observed in ft260_smbus_write.
The crash is triggered by a user-supplied length in I2C_SMBUS_BLOCK_DATA
or I2C_SMBUS_I2C_BLOCK_DATA transactions that exceeds the size of the
data->block buffer.

The ft260_smbus_xfer function in drivers/hid/hid-ft260.c retrieves the
length from data->block[0]. This value is passed to ft260_smbus_write
which then performs a memcpy from the stack-allocated data buffer. The
i2c_smbus_data union in i2cdev_ioctl_smbus has a block size of
I2C_SMBUS_BLOCK_MAX + 2 (34 bytes). If data->block[0] is large (e.g.,
42), the memcpy reads past the end of the stack variable.

Add a check in ft260_smbus_xfer to ensure that data->block[0] does
not exceed I2C_SMBUS_BLOCK_MAX before processing the data. 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 14:28 action base-commit-picker
7m Results:
map[KernelCommit:v6.19-rc7 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git]

2/1 2026/01/29 14:35 action kernel-checkouter
1m Results:
map[KernelSrc:/usr/local/google/home/nogikh/dev/syz-agent/bin/workdir/cache/src/5452af61ab2e1f5601403fe379188fc9d8a831af]

3/1 2026/01/29 14:36 action kernel-builder
14m Results:
map[KernelObj:/usr/local/google/home/nogikh/dev/syz-agent/bin/workdir/cache/build/93596d01a9653f3962ecb8710025606279e5b9c4]

4/1 2026/01/29 14:51 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 ffffc9000298fd21 by task syz-executor363/5985

CPU: 1 UID: 0 PID: 5985 Comm: syz-executor363 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:0x7f184d9a2539
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:00007ffeb5abc738 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f184d9a2539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffeb5abc8b8
R13: 0000000000000002 R14: 00007ffeb5abc760 R15: 00007ffeb5abc750
 </TASK>

The buggy address belongs to stack of task syz-executor363/5985
 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:0x4d797
flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
raw: 04fff00000000000 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 5983, tgid 5983 (syz-executor363), ts 88795144180, free_ts 78542443113
 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 5949 tgid 5949 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
 __slab_free+0x2ce/0x320 mm/slub.c:6008
 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:
 ffffc9000298fc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc9000298fc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc9000298fd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc9000298fd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc9000298fe00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================
]

5/1 2026/01/29 14:53 action codesearch-prepare
15m Results:
map[Index:codesearch-index]

6/1 2026/01/29 15:08 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 ffffc9000298fd21 by task syz-executor363/5985

CPU: 1 UID: 0 PID: 5985 Comm: syz-executor363 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:0x7f184d9a2539
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:00007ffeb5abc738 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f184d9a2539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffeb5abc8b8
R13: 0000000000000002 R14: 00007ffeb5abc760 R15: 00007ffeb5abc750
 </TASK>

The buggy address belongs to stack of task syz-executor363/5985
 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:0x4d797
flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
raw: 04fff00000000000 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 5983, tgid 5983 (syz-executor363), ts 88795144180, free_ts 78542443113
 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 5949 tgid 5949 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
 __slab_free+0x2ce/0x320 mm/slub.c:6008
 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:
 ffffc9000298fc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc9000298fc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc9000298fd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc9000298fd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc9000298fe00: 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 the `ft260_smbus_xfer` function in `drivers/hid/hid-ft260.c`, which allows a malicious user to trigger a stack out-of-bounds read by supplying an invalid length in `I2C_SMBUS_I2C_BLOCK_DATA` or `I2C_SMBUS_BLOCK_DATA` transactions.

**Detailed Explanation:**

1.  **User Input:** The `i2cdev_ioctl_smbus` function in `drivers/i2c/i2c-dev.c` copies user-provided data into a local stack variable `union i2c_smbus_data temp`. This union contains a `block` array of size `I2C_SMBUS_BLOCK_MAX + 2` (32 + 2 = 34 bytes). The first byte, `block[0]`, represents the length of the data block.
2.  **No Validation in Core:** The core I2C layer does not validate the content of the data copied from userspace, so `block[0]` can hold any value (e.g., 42), even if it exceeds `I2C_SMBUS_BLOCK_MAX` (32).
3.  **Vulnerable Callback:** The `ft260` driver implements the `smbus_xfer` callback. In `ft260_smbus_xfer`, when handling `I2C_SMBUS_I2C_BLOCK_DATA` (write), it uses `data->block[0]` as the length of the data to be written.
    ```c
    case I2C_SMBUS_I2C_BLOCK_DATA:
        ...
        ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
                    data->block[0], FT260_FLAG_START_STOP);
    ```
4.  **OOB Read:** It passes `data->block + 1` (offset 1 in `temp`) as the source buffer and `data->block[0]` (e.g., 42) as the length to `ft260_smbus_write`.
5.  **Insufficient Check:** Inside `ft260_smbus_write`, there is a check `if (data_len >= sizeof(rep->data))`, where `sizeof(rep->data)` is 60 (`FT260_WR_DATA_MAX`). Since 42 < 60, the check passes.
6.  **Crash:** The function then performs `memcpy(&rep->data[1], data, data_len)`. It attempts to read 42 bytes from `data` (which is `&temp.block[1]`). However, `temp` is only 34 bytes long, and starting from offset 1, only 33 bytes remain valid. Reading 42 bytes accesses 9 bytes past the end of the stack variable `temp`, causing the KASAN stack-out-of-bounds error.

**Fix:**
The `ft260_smbus_xfer` function must validate that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` (32) before processing `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions.

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

Tokens:
							input: 5629
							output: 46
							thoughts: 1077
						

Thoughts:
**Root Cause Analysis of KASAN: stack-out-of-bounds in ft260_smbus_write**

Okay, so we have a kernel crash, specifically a KASAN stack-out-of-bounds error in `ft260_smbus_write`. The call trace points us to `ft260_smbus_write` -> `ft260_smbus_xfer` -> `__i2c_smbus_xfer` -> `i2c_smbus_xfer` -> `i2cdev_ioctl_smbus`.  The error report zeroes in on an access within `i2cdev_ioctl_smbus`.  The report helpfully tells us the buggy address lies on the stack, within the frame of `i2cdev_ioctl_smbus`, at offset 33. It says this frame contains an object `temp` of size [32, 66), which translates to 34 bytes (66-32 = 34).

The error specifically mentions a "Read of size 42 at addr ffffc9000298fd21". This is where things become clear.  The provided address `...fd21` is at offset 1 from the start of the object `temp` on the stack, located at `...fd20`.  And the read is for 42 bytes. Since `temp` is 34 bytes long, reading 42 bytes starting at offset 1 (from the beginning of `temp` is the same as offset 33 in the stack frame) is a clear out-of-bounds access. We are trying to read bytes 1 to 42, but we only have 34 - 1 = 33 bytes available. This confirms the stack-out-of-bounds violation. The shadow memory also supports this, indicating a 34-byte `temp` object.

Therefore, the problem is that `ft260_smbus_write` is reading from `temp` – which is a buffer provided via the `i2cdev_ioctl_smbus` call – and it's reading too many bytes.

Now, let's locate the code involved.  `ft260_smbus_write` is in `drivers/hid/hid-ft260.c`, and `i2cdev_ioctl_smbus` resides in `drivers/i2c/i2c-dev.c`. I need to understand the data flow, specifically how `temp` is used in `i2cdev_ioctl_smbus` and passed down (directly or indirectly) to `ft260_smbus_write`.

The call chain reveals that `i2cdev_ioctl_smbus` calls `i2c_smbus_xfer`, which in turn calls `__i2c_smbus_xfer`.  `__i2c_smbus_xfer` then calls `adapter->algo->smbus_xfer`, and it seems that the `ft260` driver provides an implementation of `smbus_xfer`.

My next step is to examine the source code, beginning with `drivers/hid/hid-ft260.c` around line 486 and also carefully look at `drivers/i2c/i2c-dev.c`, to pinpoint where the incorrect read size is determined or where the out-of-bounds read is taking place. I need to trace how the `temp` variable is created, populated, and passed down the call chain to identify the source of the size mismatch. Specifically, how does `ft260_smbus_write` get the size parameter that leads it to read more bytes than are actually available in the buffer it receives. First, let's verify the file paths.



8/2 2026/01/29 15:09 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 15:09 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 15:09 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 9681
							output: 152
							thoughts: 200
						

Thoughts:
**Analyzing the HID and I2C Driver Code for a Crash**

Alright, I've got the files in front of me. Time to dig in. My plan is to start by looking at `drivers/hid/hid-ft260.c` to understand the `ft260_smbus_write` and `ft260_smbus_xfer` functions. That seems like the logical starting point, considering the crash is reported as being in the `ft260` driver. I'll need to pay close attention to line 486 within `ft260_smbus_write` and how it deals with the data buffer. Then, I'll switch over to `drivers/i2c/i2c-dev.c` to examine `i2cdev_ioctl_smbus`, specifically around line 317, to look at how that interacts. I might need to glance at `drivers/i2c/i2c-core-smbus.c` briefly for context, but I expect the issue is going to be in `ft260`. I'll focus my time on those specific areas, to try and get to the root cause of the crash as quickly as possible.



11/2 2026/01/29 15:09 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 15:09 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]

13/2 2026/01/29 15:09 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]

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

Tokens:
							input: 14068
							output: 53
							thoughts: 136
						

Thoughts:
**Analyzing the i2cdev_ioctl_smbus Function**

Okay, so I'm looking at `i2cdev_ioctl_smbus` and immediately I see this `temp` variable. It's a `union i2c_smbus_data`, and the important bit is that it's allocated on the stack. The code makes this clear on line 323, with the declaration `union i2c_smbus_data temp = {};`.

Now, I need to understand this `union i2c_smbus_data`. I have to dig into its definition, see what fields it contains, how it's structured. My best bet is to check the header files for the kernel; specifically, I'll need to locate its definition. It's most likely in one of the i2c-related headers. My initial thought is to check `include/uapi/linux/i2c-smbus.h`, or maybe `include/linux/i2c.h`... but I'm leaning towards `include/uapi/linux/i2c.h` as the most likely location. I'll start there.



15/2 2026/01/29 15:09 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/i2c/i2c-core-smbus.c IncludeLines:true Name:i2c_smbus_data]

Results:
map[SourceCode: 142:	union i2c_smbus_data {
 143:		__u8 byte;
 144:		__u16 word;
 145:		__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
 146:				       /* and one more for user-space compatibility */
 147:	};
 SourceFile:include/uapi/linux/i2c.h]

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

Tokens:
							input: 14647
							output: 55
							thoughts: 2162
						

Thoughts:
**Analysis of the I2C SMBus Block Data Vulnerability in ft260 Driver**

Okay, so I'm looking at this crash report and trying to understand what's happening. The `I2C_SMBUS_BLOCK_MAX` is usually 32, which means the block size, including the length byte, is 34 bytes.  The error report, with a size of [32, 66), matches this, suggesting there is a possible problem.

The crash occurs within `ft260_smbus_write`, which is called from `ft260_smbus_xfer` in the `hid-ft260.c` driver.  There are several calls to `ft260_smbus_write` within `ft260_smbus_xfer`, and I need to pinpoint the specific call that's causing the issue. The call trace points us specifically to `ft260_smbus_write+0x156/0x2f0` and `ft260_smbus_xfer+0x8bc/0xdd0`.

Now, looking closer at `ft260_smbus_xfer`, it handles different sizes.  Specifically, if the size is `I2C_SMBUS_BLOCK_DATA`, the write path passes `data->block` and `data->block[0] + 1` to `ft260_smbus_write`. If the size is `I2C_SMBUS_I2C_BLOCK_DATA`, then the write path passes `data->block + 1` and `data->block[0]` to `ft260_smbus_write`.

In `ft260_smbus_write`, I see a critical `memcpy` operation. It copies `data_len` bytes from `data` to `rep->data`. The crash is a `KASAN: stack-out-of-bounds` read, meaning there's a problem on the read side of the `memcpy`, and the source `data` is from the stack, specifically the `temp` variable within `i2cdev_ioctl_smbus`. The size reported in the crash is 42 bytes.

Focusing on the `I2C_SMBUS_I2C_BLOCK_DATA` write path, the `data` passed is `&temp.block[1]`, and the `data_len` is `temp.block[0]`. Since `temp` is 34 bytes, and `temp.block` is 34 bytes, `&temp.block[1]` is at offset 1. This means there are only 33 bytes available, so if `data_len` is greater than 33, it will result in an out-of-bounds read. The crash report specifying a read size of 42 implies `data_len` (and hence `temp.block[0]`) was 42.

The crucial question is, can `temp.block[0]` be 42?  The `i2cdev_ioctl_smbus` function copies data from the user space into `temp` using `copy_from_user`. When the `size` is `I2C_SMBUS_I2C_BLOCK_DATA`, `datasize` is `sizeof(data->block)` (34 bytes), meaning the user controls the value of `temp.block[0]`. The user can set `temp.block[0]` to anything, including 42, but since `temp.block` is only 34 bytes, that's where the problem lies.

In effect, if the user provides a `block[0]` value of 42, `ft260_smbus_write` will be called with `data = &temp.block[1]` and `data_len = 42`. The `memcpy` then tries to read 42 bytes from `&temp.block[1]`, however, only 33 bytes are available starting there. This causes a read 9 bytes past the end of `temp`, leading to the crash.

The core issue appears to be a lack of validation of `block[0]` (the length field) against the buffer size, or the use of `block[0]` without ensuring it fits within `block`. Given that `I2C_SMBUS_BLOCK_MAX` is 32, any value for the length greater than 32 is invalid.  I need to check where such a validation should occur. I do not see it within `i2c-dev.c`, so it seems it should fall to the driver to perform the check.




17/2 2026/01/29 15:10 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]

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

Tokens:
							input: 18065
							output: 51
							thoughts: 15
						

Thoughts:
Okay, here's my summary, expressed as though these were my own thoughts:

**Navigating the FT260 Data Sheet**

Alright, let's get down to brass tacks. I'm staring at the FT260 data sheet and my immediate, crucial need, the one that's screaming the loudest, is understanding and defining `FT260_WR_DATA_MAX`. This isn't just a casual parameter; it's fundamental to getting this chip communicating correctly. I know from experience that data transfer limitations are often subtle but crucial, and I can't afford any assumptions at this stage. I need to nail down the maximum write data size to avoid any overflow errors or unexpected behavior during my implementation. Time to dig in and make sure I fully understand it.



19/2 2026/01/29 15:10 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:]

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

Tokens:
							input: 18319
							output: 27
							thoughts: 78
						

Thoughts:
**Initial Investigation of Potential Define**

Okay, so I'm suspecting this might be related to a define. My first instinct is to check the `drivers/hid/hid-ft260.c` file. Given my expertise, I know the `read file` command is the right tool here. However, I'm already anticipating efficiency. I remember that `codesearch-file-index` is an option, and maybe I could use that on the specific file, or even just check the beginning to see if the define is at the top. Considering my experience and the likely scenario, I'll just start by reading the beginning of the file to see what jumps out first.



21/2 2026/01/29 15:10 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");
]

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

Tokens:
							input: 31416
							output: 54
							thoughts: 4794
						

Thoughts:
**Analysis of the FT260 SMBus Driver Vulnerability**

My investigation reveals a critical vulnerability within the FT260 USB-to-I2C/SMBus driver, specifically within the `ft260_smbus_xfer` function. The core of the problem lies in insufficient input validation, which allows for out-of-bounds reads during SMBus block transfers.

The driver handles both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transaction types. The user provides the data length via `data->block[0]`, which the driver then uses to determine the amount of data to read or write. However, the driver fails to validate that this length is within the acceptable limits.

Specifically, the `i2c_smbus_data` structure, which holds the data for SMBus transactions, has a `block` array of size `I2C_SMBUS_BLOCK_MAX + 2` (34 bytes). `I2C_SMBUS_BLOCK_MAX` is defined as 32. If the user sets `data->block[0]` to a value larger than 32 (e.g. 42, as in the crash report), the driver's `memcpy` operation will read beyond the bounds of the `i2c_smbus_data.block` array.

For `I2C_SMBUS_I2C_BLOCK_DATA`, this is exacerbated because the driver effectively starts reading from `data->block[1]`, meaning an offset relative to the buffer size. This offset is used as the start point, but data length is still larger than the buffer. The crash report's mention of a read of size 42 at address `...fd21` strongly indicates the use of `I2C_SMBUS_I2C_BLOCK_DATA`.

The critical missing check occurs within `ft260_smbus_xfer` *before* the call to `ft260_smbus_write`.  It should validate the length field (`data->block[0]`) against `I2C_SMBUS_BLOCK_MAX` for both SMBus block transfer types.

Specifically, the fix is to add a check for `data->block[0] > I2C_SMBUS_BLOCK_MAX` within `ft260_smbus_xfer` for both the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases, returning an error (`-EINVAL`) if the length is invalid. This will prevent out-of-bounds reads and protect against this stack buffer overflow.



23/2 2026/01/29 15:11 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/i2c/i2c-core-smbus.c IncludeLines:true Name:i2c_smbus_xfer]

Results:
map[SourceCode: 523:	/**
 524:	 * i2c_smbus_xfer - execute SMBus protocol operations
 525:	 * @adapter: Handle to I2C bus
 526:	 * @addr: Address of SMBus slave on that bus
 527:	 * @flags: I2C_CLIENT_* flags (usually zero or I2C_CLIENT_PEC)
 528:	 * @read_write: I2C_SMBUS_READ or I2C_SMBUS_WRITE
 529:	 * @command: Byte interpreted by slave, for protocols which use such bytes
 530:	 * @protocol: SMBus protocol operation to execute, such as I2C_SMBUS_PROC_CALL
 531:	 * @data: Data to be read or written
 532:	 *
 533:	 * This executes an SMBus protocol operation, and returns a negative
 534:	 * errno code else zero on success.
 535:	 */
 536:	s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
 537:			   unsigned short flags, char read_write,
 538:			   u8 command, int protocol, union i2c_smbus_data *data)
 539:	{
 540:		s32 res;
 541:	
 542:		res = __i2c_lock_bus_helper(adapter);
 543:		if (res)
 544:			return res;
 545:	
 546:		res = __i2c_smbus_xfer(adapter, addr, flags, read_write,
 547:				       command, protocol, data);
 548:		i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);
 549:	
 550:		return res;
 551:	}
 SourceFile:drivers/i2c/i2c-core-smbus.c]

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

Tokens:
							input: 37389
							output: 765
							thoughts: 2217
						

Thoughts:
**Analysis of the I2C SMBus Block Data Vulnerability in the FT260 Driver**

Okay, here's the breakdown of what I've figured out regarding this I2C SMBus vulnerability. The core of the problem lies within the `ft260` driver's `smbus_xfer` implementation. Because the `ft260` driver bypasses the emulated transfer logic, it's directly responsible for validating user input. The crash report pinpoints `ft260_smbus_write` as the crash site, specifically within the `memcpy` call.

The vulnerability stems from the `ft260_smbus_xfer` function accepting a length parameter derived directly from user input without sufficient validation. Here's the data flow: a user triggers this through an `ioctl` call with the `I2C_SMBUS_I2C_BLOCK_DATA` command. The `i2cdev_ioctl_smbus` function copies user data into a `union i2c_smbus_data temp`. This `temp` structure is 34 bytes in size. The user sets `temp.block[0]` to a value, say 42, which represents the length of the block data.

Subsequently, `i2c_smbus_xfer` calls `ft260_smbus_xfer`.  `ft260_smbus_xfer` then reads this length (42 in our example) from `temp.block[0]` and passes it to `ft260_smbus_write`.  Crucially, `ft260_smbus_write` performs a check against a hardware limit (e.g., 60 bytes), which passes because 42 < 60. Then, the dangerous `memcpy` is triggered, which attempts to read 42 bytes starting from `&temp.block[1]`.  The `temp` structure is only 34 bytes in total. Thus, the read attempts to read 42 bytes from a 34-byte buffer, causing an out-of-bounds (OOB) read, leading to the crash.

The memory state dump confirms this. The dump indicates the `temp` buffer's memory content. I found the offset from which the read occurred, 33 in the frame dump, corresponding to the `temp` offset, which means that the access starts at `temp + 1`. This aligns perfectly with the `I2C_SMBUS_I2C_BLOCK_DATA` case, which passes `data->block + 1` to the function.

Therefore, the critical fix involves validating the user-provided length within `ft260_smbus_xfer`. Specifically, the code must check that `data->block[0]` is less than or equal to `I2C_SMBUS_BLOCK_MAX` for block transfers (where `I2C_SMBUS_BLOCK_MAX` is 32 bytes). This will prevent the `memcpy` from reading beyond the bounds of the `temp` buffer.

It's also important to note that `I2C_SMBUS_BLOCK_DATA` is also likely vulnerable, even though this crash specifically is with `I2C_SMBUS_I2C_BLOCK_DATA`, since `I2C_SMBUS_BLOCK_DATA` is also passing the block data and uses a similar flow. The same vulnerability can be triggered, and therefore the same fix, the validation of block[0], should be applied to that code path too. Because of this, the fix should address the length check on both the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` paths.

Finally, the `i2cdev_ioctl_smbus` function allows a user to write data into the `block` member of the `i2c_smbus_data` union. Thus, the user has full control over the initial `block[0]` byte. The vulnerability range is between 34 and 59 bytes.



25/1 2026/01/29 15:11 action kernel-scratch-checkouter
0m Results:
map[KernelScratchSrc:/usr/local/google/home/nogikh/dev/syz-agent/bin/workdir/cache/tmp/tmp560659181]

26/1 2026/01/29 15:12 loop
17m
27/2 2026/01/29 15:12 iteration 0
17m
28/3 2026/01/29 15:12 agent patch-generator
1m 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 ffffc9000298fd21 by task syz-executor363/5985

CPU: 1 UID: 0 PID: 5985 Comm: syz-executor363 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:0x7f184d9a2539
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:00007ffeb5abc738 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f184d9a2539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffeb5abc8b8
R13: 0000000000000002 R14: 00007ffeb5abc760 R15: 00007ffeb5abc750
 </TASK>

The buggy address belongs to stack of task syz-executor363/5985
 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:0x4d797
flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
raw: 04fff00000000000 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 5983, tgid 5983 (syz-executor363), ts 88795144180, free_ts 78542443113
 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 5949 tgid 5949 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
 __slab_free+0x2ce/0x320 mm/slub.c:6008
 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:
 ffffc9000298fc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc9000298fc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc9000298fd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc9000298fd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc9000298fe00: 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 the `ft260_smbus_xfer` function in `drivers/hid/hid-ft260.c`, which allows a malicious user to trigger a stack out-of-bounds read by supplying an invalid length in `I2C_SMBUS_I2C_BLOCK_DATA` or `I2C_SMBUS_BLOCK_DATA` transactions.

**Detailed Explanation:**

1.  **User Input:** The `i2cdev_ioctl_smbus` function in `drivers/i2c/i2c-dev.c` copies user-provided data into a local stack variable `union i2c_smbus_data temp`. This union contains a `block` array of size `I2C_SMBUS_BLOCK_MAX + 2` (32 + 2 = 34 bytes). The first byte, `block[0]`, represents the length of the data block.
2.  **No Validation in Core:** The core I2C layer does not validate the content of the data copied from userspace, so `block[0]` can hold any value (e.g., 42), even if it exceeds `I2C_SMBUS_BLOCK_MAX` (32).
3.  **Vulnerable Callback:** The `ft260` driver implements the `smbus_xfer` callback. In `ft260_smbus_xfer`, when handling `I2C_SMBUS_I2C_BLOCK_DATA` (write), it uses `data->block[0]` as the length of the data to be written.
    ```c
    case I2C_SMBUS_I2C_BLOCK_DATA:
        ...
        ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
                    data->block[0], FT260_FLAG_START_STOP);
    ```
4.  **OOB Read:** It passes `data->block + 1` (offset 1 in `temp`) as the source buffer and `data->block[0]` (e.g., 42) as the length to `ft260_smbus_write`.
5.  **Insufficient Check:** Inside `ft260_smbus_write`, there is a check `if (data_len >= sizeof(rep->data))`, where `sizeof(rep->data)` is 60 (`FT260_WR_DATA_MAX`). Since 42 < 60, the check passes.
6.  **Crash:** The function then performs `memcpy(&rep->data[1], data, data_len)`. It attempts to read 42 bytes from `data` (which is `&temp.block[1]`). However, `temp` is only 34 bytes long, and starting from offset 1, only 33 bytes remain valid. Reading 42 bytes accesses 9 bytes past the end of the stack variable `temp`, causing the KASAN stack-out-of-bounds error.

**Fix:**
The `ft260_smbus_xfer` function must validate that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` (32) before processing `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions.



Reply:
The bug was a stack-out-of-bounds access in `ft260_smbus_xfer` in `drivers/hid/hid-ft260.c`.
The function `ft260_smbus_xfer` handles SMBus transactions. For `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`, it uses `data->block[0]` as the length of the data to be transferred.
The `data` pointer points to a `union i2c_smbus_data` allocated on the stack in `i2cdev_ioctl_smbus`. The `block` member of this union has a size of `I2C_SMBUS_BLOCK_MAX + 2` (34 bytes).
The code failed to validate that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` (32).
If a user provides a larger length (e.g., 42, as seen in the crash report), the driver attempts to read or write past the bounds of the `data->block` array, leading to a stack-out-of-bounds access.

The fix adds a check to ensure `data->block[0] <= I2C_SMBUS_BLOCK_MAX` for both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases before proceeding with the transaction.

```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) {
...
	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) {
```

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

Tokens:
							input: 6639
							output: 27
							thoughts: 159
						

Thoughts:
**Fixing the FT260 Stack Overflow**

Okay, so I've got a stack-out-of-bounds bug on my hands in `drivers/hid/hid-ft260.c`, and it looks like the culprit is a lack of proper validation on `data->block[0]` within the `ft260_smbus_xfer` function. Seems straightforward, though I need to be careful.

My first step is to dive into the code. I need to pinpoint the exact location of `ft260_smbus_xfer` within the `hid-ft260.c` file. Once I've found it, I need to understand the surrounding logic, so I can seamlessly integrate the required check to prevent `data->block[0]` from exceeding `I2C_SMBUS_BLOCK_MAX`. Context is crucial here – I don't want to introduce any new issues or break existing functionality. I'll take a look at the surrounding code to ensure I insert the check in the right place, ideally as early as possible within the function. This should be a quick one, but it's important to be methodical to prevent any further issues.



30/4 2026/01/29 15:12 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");
]

31/4 2026/01/29 15:12 llm patch-generator
1m Model:
gemini-3-pro-preview

Tokens:
							input: 19928
							output: 913
							thoughts: 7125
						

Thoughts:
**Summary of the analysis and fix:**

Alright, I've dived into this code and, as expected, the `ft260_smbus_xfer` function is the culprit. Specifically, the handling of `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` is vulnerable to a buffer overflow. The crash report pointed directly to the `ft260_smbus_write` function, and after tracing the data flow, it's clear that the size field in the `data->block` array isn't being properly validated.

The core issue lies in how the length of the block data is determined. In both the read and write paths for these SMBus block commands, `data->block[0]` is used to specify the number of bytes to transfer.  However, this length isn't checked against `I2C_SMBUS_BLOCK_MAX`, which defines the maximum size of the `block` buffer (32 bytes in `drivers/i2c/i2c-core-smbus.c`, and the `block` array has `I2C_SMBUS_BLOCK_MAX + 2` bytes for storage).  The vulnerability occurs when `data->block[0]` is larger than `I2C_SMBUS_BLOCK_MAX`, leading to out-of-bounds reads and writes in `ft260_smbus_write` and `ft260_i2c_read`, resulting in the crash.

In the case of `I2C_SMBUS_I2C_BLOCK_DATA`, this is rather clear.  We have the length passed from user space, and we're blindly using it.  For `I2C_SMBUS_BLOCK_DATA`, the read path uses the first byte of `data->block` for the length (as per the SMBus spec). However, the ft260 driver is treating that as the *requested* read length, not the *actual* length the device will return (as it does on the standard SMBus). The current code blindly trusts what is stored in `data->block[0]` by userspace, and instructs the `ft260` controller to read that many bytes. So we need to protect against a malicious `data->block[0]` in *all* these cases.

The fix is straightforward.  I need to add a check within the `ft260_smbus_xfer` function to verify that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` for both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases.  If it does, I'll return an error (`-EINVAL`) immediately, preventing the out-of-bounds access.  I've double-checked the logic for read and write operations, and this check is essential for both, and that it can be a byte value that is larger than 32. I'm going to add these checks. It's a simple, but critical, addition.



32/4 2026/01/29 15:13 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[]

33/4 2026/01/29 15:13 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 28655
							output: 489
							thoughts: 805
						

Thoughts:
**My Analysis of the FT260 SMBus Vulnerability Fix**

Okay, I've implemented the fix. The core of it involves a crucial check within `ft260_smbus_xfer`. Specifically, I've added a validation to ensure `data->block[0]` doesn't exceed `I2C_SMBUS_BLOCK_MAX` for both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` types. This is essential to prevent any out-of-bounds access to the `data->block` array, which, I recall, is allocated on the stack within the calling function.

Before moving forward, I quickly verified that `I2C_SMBUS_BLOCK_MAX` is indeed 32, as expected, using the definition in `include/uapi/linux/i2c.h`. While it's a standard value, a quick double-check never hurts. Confirmed!

Now, for a more detailed explanation. The underlying bug was a stack-based out-of-bounds read/write vulnerability. The function `ft260_smbus_xfer` receives a `union i2c_smbus_data *data`. The size of the `data->block` array is `I2C_SMBUS_BLOCK_MAX + 2`, which is 34 bytes. The code uses `data->block[0]` to indicate the length of the data to be transferred. The problem was that the code failed to validate this length against `I2C_SMBUS_BLOCK_MAX`. The caller, `i2cdev_ioctl_smbus`, copies data from user space into this `data` structure, but it lacked proper bounds checking.

This means a malicious user could supply an arbitrarily large value in `block[0]`, say 42. This would lead to `ft260_smbus_write` reading past the end of the `data->block` array or `ft260_i2c_read` writing past the end. My fix explicitly addresses this by validating that `data->block[0]` remains within the bounds of the array.

A point I had to consider further, specifically regarding the `I2C_SMBUS_BLOCK_DATA` read operation. The SMBus specification, as I recall, doesn't use `data->block[0]` as the input length for this type of operation (it's for output length). However, the `ft260` driver, in its implementation, does use it as the input length:

```c
			ret = ft260_i2c_read(dev, addr, data->block,
					     data->block[0] + 1,
					     FT260_FLAG_START_STOP_REPEATED);
```

This could indicate that the driver relies on the user to specify the maximum length of the data, then my validation is still important to prevent potential overflow of the buffer. If that's not the case, there might be a driver bug, which requires a separate fix to address the logic in which the length is determined. In either case, the overflow protection is the priority, regardless of the implementation's nuances. However, for `I2C_SMBUS_I2C_BLOCK_DATA`, providing the length in `block[0]` is standard behavior.

Given this, checking for `data->block[0]` exceeding `I2C_SMBUS_BLOCK_MAX` is definitely required. Since both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` exhibited the same vulnerability pattern, applying the fix to both was the safest and most comprehensive approach.

That should be the gist of it. All done!



34/3 2026/01/29 15:14 action test-patch
15m 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:]

35/1 2026/01/29 15:29 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]]]

36/1 2026/01/29 15:29 agent description-generator
2m 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 ffffc9000298fd21 by task syz-executor363/5985

CPU: 1 UID: 0 PID: 5985 Comm: syz-executor363 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:0x7f184d9a2539
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:00007ffeb5abc738 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f184d9a2539
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00000000000f4240 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffeb5abc8b8
R13: 0000000000000002 R14: 00007ffeb5abc760 R15: 00007ffeb5abc750
 </TASK>

The buggy address belongs to stack of task syz-executor363/5985
 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:0x4d797
flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
raw: 04fff00000000000 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 5983, tgid 5983 (syz-executor363), ts 88795144180, free_ts 78542443113
 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 5949 tgid 5949 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
 __slab_free+0x2ce/0x320 mm/slub.c:6008
 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:
 ffffc9000298fc00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffffc9000298fc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc9000298fd00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
                                           ^
 ffffc9000298fd80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
 ffffc9000298fe00: 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 the `ft260_smbus_xfer` function in `drivers/hid/hid-ft260.c`, which allows a malicious user to trigger a stack out-of-bounds read by supplying an invalid length in `I2C_SMBUS_I2C_BLOCK_DATA` or `I2C_SMBUS_BLOCK_DATA` transactions.

**Detailed Explanation:**

1.  **User Input:** The `i2cdev_ioctl_smbus` function in `drivers/i2c/i2c-dev.c` copies user-provided data into a local stack variable `union i2c_smbus_data temp`. This union contains a `block` array of size `I2C_SMBUS_BLOCK_MAX + 2` (32 + 2 = 34 bytes). The first byte, `block[0]`, represents the length of the data block.
2.  **No Validation in Core:** The core I2C layer does not validate the content of the data copied from userspace, so `block[0]` can hold any value (e.g., 42), even if it exceeds `I2C_SMBUS_BLOCK_MAX` (32).
3.  **Vulnerable Callback:** The `ft260` driver implements the `smbus_xfer` callback. In `ft260_smbus_xfer`, when handling `I2C_SMBUS_I2C_BLOCK_DATA` (write), it uses `data->block[0]` as the length of the data to be written.
    ```c
    case I2C_SMBUS_I2C_BLOCK_DATA:
        ...
        ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
                    data->block[0], FT260_FLAG_START_STOP);
    ```
4.  **OOB Read:** It passes `data->block + 1` (offset 1 in `temp`) as the source buffer and `data->block[0]` (e.g., 42) as the length to `ft260_smbus_write`.
5.  **Insufficient Check:** Inside `ft260_smbus_write`, there is a check `if (data_len >= sizeof(rep->data))`, where `sizeof(rep->data)` is 60 (`FT260_WR_DATA_MAX`). Since 42 < 60, the check passes.
6.  **Crash:** The function then performs `memcpy(&rep->data[1], data, data_len)`. It attempts to read 42 bytes from `data` (which is `&temp.block[1]`). However, `temp` is only 34 bytes long, and starting from offset 1, only 33 bytes remain valid. Reading 42 bytes accesses 9 bytes past the end of the stack variable `temp`, causing the KASAN stack-out-of-bounds error.

**Fix:**
The `ft260_smbus_xfer` function must validate that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` (32) before processing `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 a stack-out-of-bounds access in `ft260_smbus_xfer` in `drivers/hid/hid-ft260.c`.
The function `ft260_smbus_xfer` handles SMBus transactions. For `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`, it uses `data->block[0]` as the length of the data to be transferred.
The `data` pointer points to a `union i2c_smbus_data` allocated on the stack in `i2cdev_ioctl_smbus`. The `block` member of this union has a size of `I2C_SMBUS_BLOCK_MAX + 2` (34 bytes).
The code failed to validate that `data->block[0]` does not exceed `I2C_SMBUS_BLOCK_MAX` (32).
If a user provides a larger length (e.g., 42, as seen in the crash report), the driver attempts to read or write past the bounds of the `data->block` array, leading to a stack-out-of-bounds access.

The fix adds a check to ensure `data->block[0] <= I2C_SMBUS_BLOCK_MAX` for both `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases before proceeding with the transaction.

```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) {
...
	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) {
```



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

A KASAN stack-out-of-bounds report was observed in ft260_smbus_write.
The crash is triggered by a user-supplied length in I2C_SMBUS_BLOCK_DATA
or I2C_SMBUS_I2C_BLOCK_DATA transactions that exceeds the size of the
data->block buffer.

The ft260_smbus_xfer function in drivers/hid/hid-ft260.c retrieves the
length from data->block[0]. This value is passed to ft260_smbus_write
which then performs a memcpy from the stack-allocated data buffer. The
i2c_smbus_data union in i2cdev_ioctl_smbus has a block size of
I2C_SMBUS_BLOCK_MAX + 2 (34 bytes). If data->block[0] is large (e.g.,
42), the memcpy reads past the end of the stack variable.

Add a check in ft260_smbus_xfer to ensure that data->block[0] does
not exceed I2C_SMBUS_BLOCK_MAX before processing the data.

37/2 2026/01/29 15:29 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 7415
							output: 101
							thoughts: 662
						

Thoughts:
**Kernel Bug Fix - Preparing a Commit**

Okay, so I've got a crash report indicating a KASAN stack-out-of-bounds in `ft260_smbus_write`, specifically within the `hid-ft260.c` driver. The report pinpoints the vulnerable line. My understanding is that the `ft260_smbus_xfer` function is the culprit because it doesn't adequately validate the `data->block[0]` value, which represents the length of the data being transferred via SMBus, for the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` types.

Basically, a malicious user could craft a length value that exceeds `I2C_SMBUS_BLOCK_MAX`, which is 32, and send it through, causing a read past the end of the `i2c_smbus_data` union, which is only 34 bytes in size on the stack when accessed in `ft260_smbus_write`. This is the classic stack buffer overflow scenario.

Before I write the commit message, I'll quickly glance over the relevant section of `hid-ft260.c` to confirm the variable names and make sure the context is exactly as described. I want to double-check that this explanation I have is accurate, particularly that the length byte, `data->block[0]`, does indeed come from user space. I need to verify that. I'm already pretty confident in the analysis, but a quick review won't hurt. I'll focus on the area around line 486.

Now, for the commit message itself: I'll need a concise title. I'm thinking something like "HID: ft260: fix OOB access in ft260_smbus_xfer" or possibly "HID: ft260: fix stack-out-of-bounds in ft260_smbus_write". The crash happens in `ft260_smbus_write`, but the root vulnerability is in `ft260_smbus_xfer`, so I'll need to make that clear.

In the body, I'll explain the KASAN report, of course, and I'll explicitly mention `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` since that's where the vulnerability lies. I'll need to highlight that `data->block[0]` is user-controlled, originating from `i2cdev_ioctl_smbus` making it untrusted. This is the crux of the issue. Also, I'll explain that data->block is I2C_SMBUS_BLOCK_MAX + 2 bytes, as per the code.

The commit message needs to be absolutely clear. I'll need to explain that if the length provided by `data->block[0]` is bigger than `I2C_SMBUS_BLOCK_MAX`, then the subsequent `memcpy` or access in `ft260_smbus_write` will go out of bounds. The fix is to validate that length. That's the essence of the patch. Okay, let's go check the code, just to be sure...



38/2 2026/01/29 15:29 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]

39/2 2026/01/29 15:29 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]

40/2 2026/01/29 15:29 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 11144
							output: 55
							thoughts: 671
						

Thoughts:
**Analyzing the FT260 SMBus Driver for Potential Out-of-Bounds Reads**

Okay, I've confirmed the issue. Looking at the `ft260_smbus_xfer` function, specifically with the `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` cases, it's pretty clear where the problem lies.

In the `I2C_SMBUS_BLOCK_DATA` case, we're using `data->block` and `data->block[0]`. Line 720 calls `ft260_smbus_write` with `data->block` and a length of `data->block[0] + 1`. Similarly in the `I2C_SMBUS_I2C_BLOCK_DATA` case (lines 725+), line 736 passes `data->block + 1` and `data->block[0]` to `ft260_smbus_write`.

Now, digging into `ft260_smbus_write`, we see the critical lines. Line 466 declares `data, data_len` and in line 486, we have a `memcpy(&rep->data[1], data, data_len)`. If `data_len` becomes too large, this is where it reads OOB.

Let's break this down. In the `I2C_SMBUS_I2C_BLOCK_DATA` case, `data` is `data->block + 1`. The `data` in `ft260_smbus_xfer` is actually a `union i2c_smbus_data *`. This union is roughly 34 bytes due to `I2C_SMBUS_BLOCK_MAX + 2`. Therefore, `data->block` is 34 bytes. So, `data->block + 1` means we only have 33 bytes to read from.

If `data_len` from `ft260_smbus_xfer` is large, say 42, then this `memcpy` would definitely read out-of-bounds, causing all sorts of problems.

The fix provided adds checks to ensure `data->block[0]` is not greater than `I2C_SMBUS_BLOCK_MAX` (which is 32). This looks correct to mitigate the issue.

Now, revisiting `I2C_SMBUS_BLOCK_DATA`, it calls `ft260_smbus_write` with `data->block` and length `data->block[0] + 1`. If `block[0]` is the maximum, 32, the length is 33. The data buffer is 34 bytes large, so we are fine here. However, if `block[0]` is 42, then we'd have a length of 43, which would cause an OOB read. The check `block[0] > I2C_SMBUS_BLOCK_MAX` therefore covers this case, too.

I should also consider that `ft260_smbus_write` has a size check, that returns `-EINVAL` if `data_len` is too big: `if (data_len >= sizeof(rep->data)) return -EINVAL;`. I'll want to dig into `struct ft260_i2c_write_request_report` to understand `sizeof(rep->data)`. This might provide an additional layer of protection.



41/2 2026/01/29 15:29 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]

42/2 2026/01/29 15:29 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 12823
							output: 51
							thoughts: 15
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Navigating the FT260 Data Limits**

Right, so I'm thinking about data throughput and the FT260, and I need to zero in on a key parameter: `FT260_WR_DATA_MAX`. This feels critical. I understand the inherent limitations of the USB interface and the FT260's internal buffering, especially when dealing with high-speed data transfers. I'm likely designing or debugging something that involves writing data, and I'm probably looking to optimize write performance. Perhaps I'm trying to squeeze out every last bit of bandwidth, or maybe I'm troubleshooting an issue where I suspect data overflows or timing errors.

I've got to find that optimal `FT260_WR_DATA_MAX` value, which, as I recall, defines the maximum data size for a single write operation. Knowing this helps manage USB packet size effectively. My intuition tells me that going too high could lead to fragmentation and increased overhead, but going too low could incur a similar penalty with additional USB transactions. So, I need to know the optimal buffer size or at least the value that'll avoid performance bottlenecks. Maybe I'm considering factors like interrupt latency, USB bus contention, and the FT260's internal buffer depth. Is the default value sufficient? I doubt it. Does it depend on the endpoints in use? Probably. I might have to experiment, or dive into the datasheet and application notes and find some hard limits to prevent data corruption.



43/2 2026/01/29 15:30 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:]

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

Tokens:
							input: 13248
							output: 31
							thoughts: 11
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:

**Locating the Definition: A Targeted Approach**

Right, let's zero in on the relevant definition within this file. No need for a general read-through; that's inefficient. Instead, I'm thinking strategically. I need to pinpoint the exact location of the `define`. This implies a specific keyword search, likely targeting the declaration or assignment of a variable or function, depending on the context. If I am looking for the definition of a specific term, I will search for that specific term, with the appropriate syntax for the language, while if I am looking to define a new function or variable, I will search for a `define` or `def` statement. My focus is precise extraction, minimizing wasted time on irrelevant sections. Essentially, I'm aiming for surgical precision in locating the necessary information.



45/2 2026/01/29 15:30 tool codesearch-file-index
0m Args:
map[SourceFile:drivers/hid/hid-ft260.c]

Results:
map[Entities:[map[Kind:function Name:__check_debug] map[Kind:function Name:chip_mode_show] map[Kind:function Name:clock_ctl_show] map[Kind:function Name:clock_ctl_store] map[Kind:function Name:clock_show] map[Kind:function Name:clock_store] map[Kind:function Name:ft260_byte_show] map[Kind:function Name:ft260_driver_exit] map[Kind:function Name:ft260_driver_init] map[Kind:function Name:ft260_functionality] map[Kind:function Name:ft260_get_system_config] map[Kind:function Name:ft260_hid_feature_report_get] map[Kind:function Name:ft260_hid_feature_report_set] map[Kind:function Name:ft260_hid_output_report] map[Kind:function Name:ft260_hid_output_report_check_status] map[Kind:function Name:ft260_i2c_read] map[Kind:function Name:ft260_i2c_reset] map[Kind:function Name:ft260_i2c_write] map[Kind:function Name:ft260_i2c_write_read] map[Kind:function Name:ft260_i2c_xfer] map[Kind:function Name:ft260_is_interface_enabled] map[Kind:function Name:ft260_probe] map[Kind:function Name:ft260_raw_event] map[Kind:function Name:ft260_remove] map[Kind:function Name:ft260_smbus_write] map[Kind:function Name:ft260_smbus_xfer] map[Kind:function Name:ft260_word_show] map[Kind:function Name:ft260_xfer_status] map[Kind:function Name:hid_over_i2c_en_show] map[Kind:function Name:i2c_enable_show] map[Kind:function Name:i2c_enable_store] map[Kind:function Name:i2c_reset_store] map[Kind:function Name:power_saving_en_show] map[Kind:function Name:pwren_status_show] map[Kind:function Name:suspend_status_show] map[Kind:function Name:uart_mode_show] map[Kind:function Name:uart_mode_store] map[Kind:struct Name:ft260_device] map[Kind:struct Name:ft260_get_chip_version_report] map[Kind:struct Name:ft260_get_i2c_status_report] map[Kind:struct Name:ft260_get_system_status_report] map[Kind:struct Name:ft260_i2c_input_report] map[Kind:struct Name:ft260_i2c_read_request_report] map[Kind:struct Name:ft260_i2c_write_request_report] map[Kind:struct Name:ft260_set_i2c_mode_report] map[Kind:struct Name:ft260_set_i2c_reset_report] map[Kind:struct Name:ft260_set_i2c_speed_report] map[Kind:struct Name:ft260_set_system_clock_report] map[Kind:struct Name:ft260_set_uart_mode_report]]]

46/2 2026/01/29 15:30 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 14485
							output: 45
							thoughts: 1014
						

Thoughts:
**Analyzing the Stack-Out-of-Bounds Bug in ft260 Driver**

Okay, so I'm digging into this `ft260` HID driver issue. It seems like the `defines` aren't explicitly listed in the entities, but no problem, I can deduce them. I could use `read-file`, but I'll try to figure it out from the context. The explanation mentions `sizeof(rep->data)` is 60, which is `FT260_WR_DATA_MAX`. Right, so if the `data_len` is 42, then the check `42 < 60` passes. But the `data` source itself is small, like 34 bytes or effectively 33. This means `memcpy` is reading 42 bytes from a valid, but smaller 33-byte buffer. That's the bug: a classic stack-out-of-bounds read. I have all the key pieces now to put together the commit message.

For the commit, the subject line needs to be concise, so I need a clear summary. "HID: ft260: fix stack-out-of-bounds in ft260_smbus_write" works well, it's 54 chars. Or maybe "HID: ft260: validate user supplied length in ft260_smbus_xfer". The crash is in `ft260_smbus_write`, but the root cause is in `ft260_smbus_xfer`. It's often better to name the crash location or the fix action in the subject when dealing with an out-of-bounds (OOB) bug, and "fix stack-out-of-bounds in ft260_smbus_write" describes the issue effectively.

The body of the commit message should be precise. I'll describe the problem: the `ft260` driver implementation of `smbus_xfer` is missing a check on the user-supplied length for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA` transactions. The `data->block[0]` value, provided from user space via `i2cdev_ioctl_smbus`, is used as the length.

If this length exceeds `I2C_SMBUS_BLOCK_MAX` (32), the driver still passes this unchecked length to `ft260_smbus_write`. Although `ft260_smbus_write` checks if the length fits the report buffer (60 bytes), it *doesn't* verify if the source buffer `data->block` is big enough. This `data->block` is allocated on the stack within `i2cdev_ioctl_smbus`.

The `i2c_smbus_data` union's block array is sized `I2C_SMBUS_BLOCK_MAX + 2`. When a large length (e.g., 42) is used, `memcpy` reads past the end of the stack variable, which triggers a KASAN stack-out-of-bounds warning.

The fix is simply to ensure `data->block[0]` doesn't exceed `I2C_SMBUS_BLOCK_MAX`.

I want to make sure the body is complete and precise. Let's include the specific location that was mentioned in the text: the buggy address belongs to the stack of task ... i2cdev_ioctl_smbus ... [32, 66) 'temp'. And 32 + 34 = 66, so `temp` is the `union i2c_smbus_data`.

Before I send the patch, just a quick sanity check to make sure `ft260_smbus_write` is only used where I think it is. Since it's a static function, I should only be concerned if it's called somewhere else where this check might be needed. Let's look at the callers: it's called by `ft260_smbus_xfer` multiple times. Also, does `ft260_i2c_write` call it? No, `ft260_i2c_write` is a separate function. Ok, double checked. I'm good to go.



47/2 2026/01/29 15:30 tool codesearch-find-references
0m Args:
map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_smbus_write]

Results:
map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:677] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:682] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:690] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:696] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:704] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:711] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:720] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:727] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ft260_smbus_xfer SourceFile:drivers/hid/hid-ft260.c SourceLine:736]] TruncatedOutput:false]

48/2 2026/01/29 15:30 llm description-generator
1m Model:
gemini-3-pro-preview

Tokens:
							input: 17039
							output: 284
							thoughts: 4714
						

Thoughts:
**HID: ft260: fix stack-out-of-bounds in ft260_smbus_write**

A KASAN stack-out-of-bounds report was observed in ft260_smbus_write.
The crash is triggered by a user-supplied length in I2C_SMBUS_BLOCK_DATA
or I2C_SMBUS_I2C_BLOCK_DATA transactions that exceeds the size of the
data->block buffer.

The ft260_smbus_xfer function in drivers/hid/hid-ft260.c retrieves the
length from data->block[0]. This value is passed to ft260_smbus_write
which then performs a memcpy from the stack-allocated data buffer. The
i2c_smbus_data union in i2cdev_ioctl_smbus has a block size of
I2C_SMBUS_BLOCK_MAX + 2 (34 bytes). If data->block[0] is large (e.g.,
42), the memcpy reads past the end of the stack variable.

Add a check in ft260_smbus_xfer to ensure that data->block[0] does
not exceed I2C_SMBUS_BLOCK_MAX before processing the data.



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)