* [Linux Kernel Bug] KASAN: slab-out-of-bounds Write in ext4_xattr_set_entry @ 2026-09-15 13:03 Jiaming Zhang 2026-09-16 8:47 ` [PATCH] ext4: clear in-inode xattr space before adding the first xattr Jiaming Zhang 0 siblings, 1 reply; 6+ messages in thread From: Jiaming Zhang @ 2026-09-15 13:03 UTC (permalink / raw) To: linux-ext4, tytso Cc: adilger.kernel, jack, libaokun, linux-kernel, ojaswin, ritesh.list, yi.zhang, syzkaller, r772577952 Dear Linux kernel developers and maintainers, We are writing to report an issue discovered in the EXT4 subsystem. The issue is reproducible on the latest version of Linux (v7.3-rc3, commit fd73f4a6659897191fa0d40695fe370925dd3780). Below is the kernel report: loop0: detected capacity change from 0 to 512 EXT4-fs (loop0): mounted filesystem 00000000-0000-0000-0000-000000000000 r/w without journal. Quota mode: writeback. ================================================================== BUG: KASAN: slab-out-of-bounds in ext4_xattr_set_entry+0xdc5/0x1e80 fs/ext4/xattr.c:1812 Write of size 3803 at addr ffff8880476bc9ac by task syz-executor360/9770 CPU: 1 UID: 0 PID: 9770 Comm: syz-executor360 Not tainted 7.3.0-rc3 #49 PREEMPT(full) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 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+0x153/0x7e0 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+0x40/0x70 mm/kasan/shadow.c:106 ext4_xattr_set_entry+0xdc5/0x1e80 fs/ext4/xattr.c:1812 ext4_xattr_ibody_set+0x254/0x6a0 fs/ext4/xattr.c:2277 ext4_xattr_set_handle+0xcb7/0x1530 fs/ext4/xattr.c:2449 ext4_xattr_set+0x244/0x330 fs/ext4/xattr.c:2563 __vfs_setxattr+0x43c/0x480 fs/xattr.c:223 __vfs_setxattr_noperm+0x12d/0x660 fs/xattr.c:257 vfs_setxattr+0x162/0x360 fs/xattr.c:344 do_setxattr fs/xattr.c:662 [inline] filename_setxattr+0x286/0x610 fs/xattr.c:690 path_setxattrat+0x367/0x3b0 fs/xattr.c:734 __do_sys_lsetxattr fs/xattr.c:775 [inline] __se_sys_lsetxattr fs/xattr.c:771 [inline] __x64_sys_lsetxattr+0xbf/0xe0 fs/xattr.c:771 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x170/0x540 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f7c0004703d Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 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 b8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007f7bff803208 EFLAGS: 00000246 ORIG_RAX: 00000000000000bd RAX: ffffffffffffffda RBX: 00007f7c000f3ae8 RCX: 00007f7c0004703d RDX: 0000200000000180 RSI: 0000200000000040 RDI: 0000200000000000 RBP: 00007f7c000f3ae0 R08: 0000000000000001 R09: 0000000000000000 R10: 0000000000000edb R11: 0000000000000246 R12: 00007f7c000bfec0 R13: 0000200000000c00 R14: 0031656c69662f2e R15: 0000200000000580 </TASK> Allocated by task 9469: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 unpoison_slab_object mm/kasan/common.c:340 [inline] __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366 kasan_slab_alloc include/linux/kasan.h:253 [inline] slab_post_alloc_hook mm/slub.c:4683 [inline] slab_alloc_node mm/slub.c:4996 [inline] kmem_cache_alloc_noprof+0x2be/0x610 mm/slub.c:5010 __kernfs_new_node+0xd9/0x980 fs/kernfs/dir.c:665 kernfs_new_node+0xea/0x140 fs/kernfs/dir.c:751 __kernfs_create_file+0x4b/0x2e0 fs/kernfs/file.c:1050 sysfs_add_file_mode_ns+0x238/0x300 fs/sysfs/file.c:316 create_files fs/sysfs/group.c:82 [inline] internal_create_group+0x66e/0x1170 fs/sysfs/group.c:189 internal_create_groups fs/sysfs/group.c:229 [inline] sysfs_create_groups+0x59/0x120 fs/sysfs/group.c:255 device_add_groups drivers/base/core.c:2911 [inline] device_add_attrs+0xde/0x600 drivers/base/core.c:2975 device_add+0x496/0xb90 drivers/base/core.c:3715 netdev_register_kobject+0x197/0x350 net/core/net-sysfs.c:2340 register_netdevice+0x152e/0x1da0 net/core/dev.c:11503 veth_newlink+0x639/0xd50 drivers/net/veth.c:1899 rtnl_newlink_create+0x2f7/0x780 net/core/rtnetlink.c:3974 __rtnl_newlink net/core/rtnetlink.c:4103 [inline] rtnl_newlink+0x1487/0x19f0 net/core/rtnetlink.c:4220 rtnetlink_rcv_msg+0x7cf/0xb70 net/core/rtnetlink.c:7137 netlink_rcv_skb+0x208/0x470 net/netlink/af_netlink.c:2556 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline] netlink_unicast+0x75e/0x8e0 net/netlink/af_netlink.c:1345 netlink_sendmsg+0x805/0xb30 net/netlink/af_netlink.c:1900 sock_sendmsg_nosec+0x13a/0x180 net/socket.c:800 __sock_sendmsg net/socket.c:815 [inline] __sys_sendto+0x3b4/0x520 net/socket.c:2281 __do_sys_sendto net/socket.c:2288 [inline] __se_sys_sendto net/socket.c:2284 [inline] __x64_sys_sendto+0xde/0x100 net/socket.c:2284 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x170/0x540 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff8880476bc960 which belongs to the cache kernfs_node_cache of size 176 The buggy address is located 76 bytes inside of allocated 176-byte region [ffff8880476bc960, ffff8880476bca10) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x476bc flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff) page_type: f5(slab) raw: 04fff00000000000 ffff88801be9b000 dead000000000122 0000000000000000 raw: 0000000000000000 0000000200110011 00000000f5000000 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 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 9469, tgid 9469 (syz-executor360), ts 217789819284 set_page_owner include/linux/page_owner.h:33 [inline] post_alloc_hook+0x205/0x260 mm/page_alloc.c:1871 prep_new_page mm/page_alloc.c:1879 [inline] get_page_from_freelist+0x235b/0x23e0 mm/page_alloc.c:3943 __alloc_frozen_pages_noprof+0x209/0x5a0 mm/page_alloc.c:5436 alloc_slab_page mm/slub.c:3347 [inline] allocate_slab+0x7d/0x630 mm/slub.c:3462 new_slab mm/slub.c:3513 [inline] refill_objects+0x301/0x380 mm/slub.c:7417 refill_sheaf mm/slub.c:2885 [inline] __pcs_replace_empty_main+0x2cb/0x6c0 mm/slub.c:4774 alloc_from_pcs mm/slub.c:4850 [inline] slab_alloc_node mm/slub.c:4984 [inline] kmem_cache_alloc_noprof+0x3a0/0x610 mm/slub.c:5010 __kernfs_new_node+0xd9/0x980 fs/kernfs/dir.c:665 kernfs_new_node+0xea/0x140 fs/kernfs/dir.c:751 __kernfs_create_file+0x4b/0x2e0 fs/kernfs/file.c:1050 sysfs_add_file_mode_ns+0x238/0x300 fs/sysfs/file.c:316 create_files fs/sysfs/group.c:82 [inline] internal_create_group+0x66e/0x1170 fs/sysfs/group.c:189 internal_create_groups fs/sysfs/group.c:229 [inline] sysfs_create_groups+0x59/0x120 fs/sysfs/group.c:255 device_add_groups drivers/base/core.c:2911 [inline] device_add_attrs+0xde/0x600 drivers/base/core.c:2975 device_add+0x496/0xb90 drivers/base/core.c:3715 netdev_register_kobject+0x197/0x350 net/core/net-sysfs.c:2340 page last free pid 9469 tgid 9469 ts 217788989395 stack trace: reset_page_owner include/linux/page_owner.h:26 [inline] __free_pages_prepare mm/page_alloc.c:1418 [inline] __free_frozen_pages+0xc71/0xd50 mm/page_alloc.c:2962 __slab_free+0x282/0x2d0 mm/slub.c:5823 qlink_free mm/kasan/quarantine.c:163 [inline] qlist_free_all+0x99/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:4683 [inline] slab_alloc_node mm/slub.c:4996 [inline] __kmalloc_cache_noprof+0x2a4/0x620 mm/slub.c:5559 _kmalloc_noprof include/linux/slab.h:991 [inline] netdev_name_node_alloc net/core/dev.c:276 [inline] netdev_name_node_head_alloc net/core/dev.c:290 [inline] register_netdevice+0x58d/0x1da0 net/core/dev.c:11416 veth_newlink+0x639/0xd50 drivers/net/veth.c:1899 rtnl_newlink_create+0x2f7/0x780 net/core/rtnetlink.c:3974 __rtnl_newlink net/core/rtnetlink.c:4103 [inline] rtnl_newlink+0x1487/0x19f0 net/core/rtnetlink.c:4220 rtnetlink_rcv_msg+0x7cf/0xb70 net/core/rtnetlink.c:7137 netlink_rcv_skb+0x208/0x470 net/netlink/af_netlink.c:2556 netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline] netlink_unicast+0x75e/0x8e0 net/netlink/af_netlink.c:1345 netlink_sendmsg+0x805/0xb30 net/netlink/af_netlink.c:1900 sock_sendmsg_nosec+0x13a/0x180 net/socket.c:800 __sock_sendmsg net/socket.c:815 [inline] __sys_sendto+0x3b4/0x520 net/socket.c:2281 Memory state around the buggy address: ffff8880476bc900: 00 00 00 00 fc fc fc fc fc fc fc fc 00 00 00 00 ffff8880476bc980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ffff8880476bca00: 00 00 fc fc fc fc fc fc fc fc 00 00 00 00 00 00 ^ ffff8880476bca80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ffff8880476bcb00: fc fc fc fc fc fc fc fc 00 00 00 00 00 00 00 00 ================================================================== Following is the root cause analysis for this issue. Note that the analysis was performed with the assistance of an LLM, but we have tried our best to ensure its accuracy. The write happens while ext4_xattr_set_entry() adds an extended attribute to the space inside the inode that follows the extra inode fields, whose size is given by i_extra_isize. To store a new value, the function walks the entries to find the lowest value offset, min_offs, checks that the gap between the end of the entry list and min_offs is large enough, and copies the value to just below min_offs. In this report min_offs is 0, so the value is copied to memory in front of that space. That offset of 0 comes from bytes that were never validated. When ext4 loads an inode, ext4_iget_extra_inode() looks for the xattr magic right after the extra inode fields. Only if the magic is there does it validate the entries and set EXT4_STATE_XATTR. Otherwise it treats the space as empty and checks nothing. ext4_xattr_ibody_set() does not test that flag before calling ext4_xattr_set_entry(), which walks whatever entries it finds at the start of the space. A crafted image can keep old attribute data in an inode and change i_extra_isize so that the magic is no longer where ext4 looks for it. The old data after the new start of the space is then parsed as entries, and one of them has a non-zero value size and a value offset of 0. With min_offs set to 0, the free space calculation underflows, because free is a size_t: size_t free; free = min_offs - ((void *)last - s->base) - sizeof(__u32); if (!s->not_found) free += EXT4_XATTR_LEN(name_len) + old_size; if (free < EXT4_XATTR_LEN(name_len) + new_size) { ret = -ENOSPC; goto out; } The subtraction wraps around to a huge value, so the check passes. That check is what normally keeps the copy inside the space: with a correct free space value, it would have returned -ENOSPC. The copy then goes to s->base + min_offs - new_size, which with min_offs being 0 is new_size bytes before the start of the space. It overwrites the memory in front of the inode. Depending on where the write lands, the following issue was also triggered during testing: - KASAN: slab-use-after-free Write in ext4_xattr_set_entry To fix this issue, ext4_xattr_ibody_set() should clear the attribute space of an inode without EXT4_STATE_XATTR before adding an attribute to it, so that leftover bytes can no longer be parsed as entries: diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index 5c310747b965..2e5f39b710bd 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -2274,6 +2274,10 @@ int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode, if (IS_ERR(ea_inode)) return PTR_ERR(ea_inode); } + + if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) + memset(s->first, 0, s->end - (void *)s->first); + error = ext4_xattr_set_entry(i, s, handle, inode, ea_inode, false /* is_block */); if (error) { With the above patch applied, the reproducer no longer triggers the issue on our machine. If this solution is acceptable, we are happy to submit a formal patch. The kernel console output, kernel config, syzkaller reproducer, and C reproducer are available on Google Drive: https://drive.google.com/drive/folders/1o-aZtZqL93I06eP1GSC8g_0V0QMvbm7U?usp=sharing Please let us know if any further information is required. Best regards, Jiaming Zhang ^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH] ext4: clear in-inode xattr space before adding the first xattr 2026-09-15 13:03 [Linux Kernel Bug] KASAN: slab-out-of-bounds Write in ext4_xattr_set_entry Jiaming Zhang @ 2026-09-16 8:47 ` Jiaming Zhang 2026-09-16 8:56 ` sashiko-bot ` (3 more replies) 0 siblings, 4 replies; 6+ messages in thread From: Jiaming Zhang @ 2026-09-16 8:47 UTC (permalink / raw) To: linux-ext4, tytso Cc: r772577952, adilger.kernel, jack, libaokun, linux-kernel, ojaswin, ritesh.list, syzkaller, yi.zhang, stable An inode can hold extended attributes in the space that follows its extra fields, whose size is given by i_extra_isize. When ext4 loads an inode, ext4_iget_extra_inode() validates that space only if it starts with the xattr magic, and only then sets EXT4_STATE_XATTR. Without the magic, the space is treated as empty and is not checked. ext4_xattr_ibody_set() passes the space to ext4_xattr_set_entry() whether or not EXT4_STATE_XATTR is set. ext4_xattr_set_entry() walks the entries there to find min_offs, the lowest value offset. That offset decides whether the new value fits and where the value is copied: free = min_offs - ((void *)last - s->base) - sizeof(__u32); ... void *val = s->base + min_offs - new_size; A crafted image can leave non-zero bytes in that space without the magic where ext4 looks for it, for example by shrinking i_extra_isize. Those bytes are parsed as entries, and an entry that claims an in-inode value at offset 0 sets min_offs to 0. Because free is a size_t, the subtraction then wraps around to a huge value, and the free space check passes although the value does not fit. The value is copied new_size bytes before the start of the space, over the inode's own fields and, for a large value, the memory in front of the inode. On a kernel without KASAN, the copy corrupts memory: it overwrites the inode's own fields and, for a large value, also the memory in front of the inode. The bytes and their length are decided by the setxattr() caller. Nothing is freed and reused here, so the use-after-free in the report only reflects where the write happened to land. Triggering this issue requires mounting a crafted image, which needs CAP_SYS_ADMIN or automounting of removable media; after that, any setxattr() on the file reaches this path. The issue was found by fuzzing and has not been seen in production; a reproducer is available in the linked report. Clear the space in ext4_xattr_ibody_set() before calling ext4_xattr_set_entry() when EXT4_STATE_XATTR is not set, so that the walk starts from the empty entry list. Fixes: ac27a0ec112a ("[PATCH] ext4: initial copy of files from ext3") Closes: https://lore.kernel.org/lkml/CANypQFZzxTAH=4QRRDoqH6VAw1daAq88NDWiQKoM7QTOnP40sg@mail.gmail.com/ Cc: stable@vger.kernel.org Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Jiaming Zhang <r772577952@gmail.com> --- fs/ext4/xattr.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index 5c310747b965..2e5f39b710bd 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -2274,6 +2274,10 @@ int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode, if (IS_ERR(ea_inode)) return PTR_ERR(ea_inode); } + + if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) + memset(s->first, 0, s->end - (void *)s->first); + error = ext4_xattr_set_entry(i, s, handle, inode, ea_inode, false /* is_block */); if (error) { -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH] ext4: clear in-inode xattr space before adding the first xattr 2026-09-16 8:47 ` [PATCH] ext4: clear in-inode xattr space before adding the first xattr Jiaming Zhang @ 2026-09-16 8:56 ` sashiko-bot 2026-09-23 8:52 ` Jiaming Zhang ` (2 subsequent siblings) 3 siblings, 0 replies; 6+ messages in thread From: sashiko-bot @ 2026-09-16 8:56 UTC (permalink / raw) To: Jiaming Zhang; +Cc: tytso, linux-ext4 > An inode can hold extended attributes in the space that follows its extra > fields, whose size is given by i_extra_isize. When ext4 loads an inode, > ext4_iget_extra_inode() validates that space only if it starts with the > xattr magic, and only then sets EXT4_STATE_XATTR. Without the magic, the > space is treated as empty and is not checked. > > ext4_xattr_ibody_set() passes the space to ext4_xattr_set_entry() whether > or not EXT4_STATE_XATTR is set. ext4_xattr_set_entry() walks the entries > there to find min_offs, the lowest value offset. That offset decides > whether the new value fits and where the value is copied: > > free = min_offs - ((void *)last - s->base) - sizeof(__u32); > ... > void *val = s->base + min_offs - new_size; > > [ ... ] > Fixes: ac27a0ec112a ("[PATCH] ext4: initial copy of files from ext3") > Closes: https://lore.kernel.org/lkml/CANypQFZzxTAH=4QRRDoqH6VAw1daAq88NDWiQKoM7QTOnP40sg@mail.gmail.com/ > Cc: stable@vger.kernel.org > Assisted-by: Claude Code:claude-opus-5 > Signed-off-by: Jiaming Zhang <r772577952@gmail.com> Sashiko has reviewed this patch and found no issues. It looks great! -- Sashiko AI review · https://sashiko.dev/#/patchset/20260916084723.877552-1-r772577952@gmail.com?part=1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] ext4: clear in-inode xattr space before adding the first xattr 2026-09-16 8:47 ` [PATCH] ext4: clear in-inode xattr space before adding the first xattr Jiaming Zhang 2026-09-16 8:56 ` sashiko-bot @ 2026-09-23 8:52 ` Jiaming Zhang 2026-09-23 11:10 ` Andreas Dilger 2026-10-14 14:08 ` Jiaming Zhang 3 siblings, 0 replies; 6+ messages in thread From: Jiaming Zhang @ 2026-09-23 8:52 UTC (permalink / raw) To: linux-ext4, tytso Cc: adilger.kernel, jack, libaokun, linux-kernel, ojaswin, ritesh.list, syzkaller, yi.zhang, stable Jiaming Zhang <r772577952@gmail.com> 于2026年9月16日周三 16:47写道: > > An inode can hold extended attributes in the space that follows its extra > fields, whose size is given by i_extra_isize. When ext4 loads an inode, > ext4_iget_extra_inode() validates that space only if it starts with the > xattr magic, and only then sets EXT4_STATE_XATTR. Without the magic, the > space is treated as empty and is not checked. > > ext4_xattr_ibody_set() passes the space to ext4_xattr_set_entry() whether > or not EXT4_STATE_XATTR is set. ext4_xattr_set_entry() walks the entries > there to find min_offs, the lowest value offset. That offset decides > whether the new value fits and where the value is copied: > > free = min_offs - ((void *)last - s->base) - sizeof(__u32); > ... > void *val = s->base + min_offs - new_size; > > A crafted image can leave non-zero bytes in that space without the magic > where ext4 looks for it, for example by shrinking i_extra_isize. Those > bytes are parsed as entries, and an entry that claims an in-inode value at > offset 0 sets min_offs to 0. Because free is a size_t, the subtraction > then wraps around to a huge value, and the free space check passes although > the value does not fit. The value is copied new_size bytes before the > start of the space, over the inode's own fields and, for a large value, the > memory in front of the inode. > > On a kernel without KASAN, the copy corrupts memory: it overwrites the > inode's own fields and, for a large value, also the memory in front of the > inode. The bytes and their length are decided by the setxattr() caller. > Nothing is freed and reused here, so the use-after-free in the report only > reflects where the write happened to land. > > Triggering this issue requires mounting a crafted image, which needs > CAP_SYS_ADMIN or automounting of removable media; after that, any > setxattr() on the file reaches this path. The issue was found by fuzzing > and has not been seen in production; a reproducer is available in the > linked report. > > Clear the space in ext4_xattr_ibody_set() before calling > ext4_xattr_set_entry() when EXT4_STATE_XATTR is not set, so that the walk > starts from the empty entry list. > > Fixes: ac27a0ec112a ("[PATCH] ext4: initial copy of files from ext3") > Closes: https://lore.kernel.org/lkml/CANypQFZzxTAH=4QRRDoqH6VAw1daAq88NDWiQKoM7QTOnP40sg@mail.gmail.com/ > Cc: stable@vger.kernel.org > Assisted-by: Claude Code:claude-opus-5 > Signed-off-by: Jiaming Zhang <r772577952@gmail.com> > --- > fs/ext4/xattr.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c > index 5c310747b965..2e5f39b710bd 100644 > --- a/fs/ext4/xattr.c > +++ b/fs/ext4/xattr.c > @@ -2274,6 +2274,10 @@ int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode, > if (IS_ERR(ea_inode)) > return PTR_ERR(ea_inode); > } > + > + if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) > + memset(s->first, 0, s->end - (void *)s->first); > + > error = ext4_xattr_set_entry(i, s, handle, inode, ea_inode, > false /* is_block */); > if (error) { > -- > 2.43.0 > Friendly ping, please tell me if any further change is needed. ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] ext4: clear in-inode xattr space before adding the first xattr 2026-09-16 8:47 ` [PATCH] ext4: clear in-inode xattr space before adding the first xattr Jiaming Zhang 2026-09-16 8:56 ` sashiko-bot 2026-09-23 8:52 ` Jiaming Zhang @ 2026-09-23 11:10 ` Andreas Dilger 2026-10-14 14:08 ` Jiaming Zhang 3 siblings, 0 replies; 6+ messages in thread From: Andreas Dilger @ 2026-09-23 11:10 UTC (permalink / raw) To: Jiaming Zhang Cc: linux-ext4, tytso, jack, libaokun, linux-kernel, ojaswin, ritesh.list, syzkaller, yi.zhang, stable On Sep 16, 2026, at 02:47, Jiaming Zhang <r772577952@gmail.com> wrote: > > An inode can hold extended attributes in the space that follows its extra > fields, whose size is given by i_extra_isize. When ext4 loads an inode, > ext4_iget_extra_inode() validates that space only if it starts with the > xattr magic, and only then sets EXT4_STATE_XATTR. Without the magic, the > space is treated as empty and is not checked. > > ext4_xattr_ibody_set() passes the space to ext4_xattr_set_entry() whether > or not EXT4_STATE_XATTR is set. ext4_xattr_set_entry() walks the entries > there to find min_offs, the lowest value offset. That offset decides > whether the new value fits and where the value is copied: > > free = min_offs - ((void *)last - s->base) - sizeof(__u32); > ... > void *val = s->base + min_offs - new_size; > > A crafted image can leave non-zero bytes in that space without the magic > where ext4 looks for it, for example by shrinking i_extra_isize. Those > bytes are parsed as entries, and an entry that claims an in-inode value at > offset 0 sets min_offs to 0. Because free is a size_t, the subtraction > then wraps around to a huge value, and the free space check passes although > the value does not fit. The value is copied new_size bytes before the > start of the space, over the inode's own fields and, for a large value, the > memory in front of the inode. > > On a kernel without KASAN, the copy corrupts memory: it overwrites the > inode's own fields and, for a large value, also the memory in front of the > inode. The bytes and their length are decided by the setxattr() caller. > Nothing is freed and reused here, so the use-after-free in the report only > reflects where the write happened to land. > > Triggering this issue requires mounting a crafted image, which needs > CAP_SYS_ADMIN or automounting of removable media; after that, any > setxattr() on the file reaches this path. The issue was found by fuzzing > and has not been seen in production; a reproducer is available in the > linked report. > > Clear the space in ext4_xattr_ibody_set() before calling > ext4_xattr_set_entry() when EXT4_STATE_XATTR is not set, so that the walk > starts from the empty entry list. > > Fixes: ac27a0ec112a ("[PATCH] ext4: initial copy of files from ext3") > Closes: https://lore.kernel.org/lkml/CANypQFZzxTAH=4QRRDoqH6VAw1daAq88NDWiQKoM7QTOnP40sg@mail.gmail.com/ > Cc: stable@vger.kernel.org > Assisted-by: Claude Code:claude-opus-5 > Signed-off-by: Jiaming Zhang <r772577952@gmail.com> Looks good to me. Reviewed-by: Andreas Dilger <adilger@dilger.ca> > --- > fs/ext4/xattr.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c > index 5c310747b965..2e5f39b710bd 100644 > --- a/fs/ext4/xattr.c > +++ b/fs/ext4/xattr.c > @@ -2274,6 +2274,10 @@ int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode, > if (IS_ERR(ea_inode)) > return PTR_ERR(ea_inode); > } > + > + if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) > + memset(s->first, 0, s->end - (void *)s->first); > + > error = ext4_xattr_set_entry(i, s, handle, inode, ea_inode, > false /* is_block */); > if (error) { > -- > 2.43.0 > Cheers, Andreas ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] ext4: clear in-inode xattr space before adding the first xattr 2026-09-16 8:47 ` [PATCH] ext4: clear in-inode xattr space before adding the first xattr Jiaming Zhang ` (2 preceding siblings ...) 2026-09-23 11:10 ` Andreas Dilger @ 2026-10-14 14:08 ` Jiaming Zhang 3 siblings, 0 replies; 6+ messages in thread From: Jiaming Zhang @ 2026-10-14 14:08 UTC (permalink / raw) To: linux-ext4, tytso Cc: adilger.kernel, jack, libaokun, linux-kernel, ojaswin, ritesh.list, syzkaller, yi.zhang, stable Jiaming Zhang <r772577952@gmail.com> 于2026年9月16日周三 16:47写道: > > An inode can hold extended attributes in the space that follows its extra > fields, whose size is given by i_extra_isize. When ext4 loads an inode, > ext4_iget_extra_inode() validates that space only if it starts with the > xattr magic, and only then sets EXT4_STATE_XATTR. Without the magic, the > space is treated as empty and is not checked. > > ext4_xattr_ibody_set() passes the space to ext4_xattr_set_entry() whether > or not EXT4_STATE_XATTR is set. ext4_xattr_set_entry() walks the entries > there to find min_offs, the lowest value offset. That offset decides > whether the new value fits and where the value is copied: > > free = min_offs - ((void *)last - s->base) - sizeof(__u32); > ... > void *val = s->base + min_offs - new_size; > > A crafted image can leave non-zero bytes in that space without the magic > where ext4 looks for it, for example by shrinking i_extra_isize. Those > bytes are parsed as entries, and an entry that claims an in-inode value at > offset 0 sets min_offs to 0. Because free is a size_t, the subtraction > then wraps around to a huge value, and the free space check passes although > the value does not fit. The value is copied new_size bytes before the > start of the space, over the inode's own fields and, for a large value, the > memory in front of the inode. > > On a kernel without KASAN, the copy corrupts memory: it overwrites the > inode's own fields and, for a large value, also the memory in front of the > inode. The bytes and their length are decided by the setxattr() caller. > Nothing is freed and reused here, so the use-after-free in the report only > reflects where the write happened to land. > > Triggering this issue requires mounting a crafted image, which needs > CAP_SYS_ADMIN or automounting of removable media; after that, any > setxattr() on the file reaches this path. The issue was found by fuzzing > and has not been seen in production; a reproducer is available in the > linked report. > > Clear the space in ext4_xattr_ibody_set() before calling > ext4_xattr_set_entry() when EXT4_STATE_XATTR is not set, so that the walk > starts from the empty entry list. > > Fixes: ac27a0ec112a ("[PATCH] ext4: initial copy of files from ext3") > Closes: https://lore.kernel.org/lkml/CANypQFZzxTAH=4QRRDoqH6VAw1daAq88NDWiQKoM7QTOnP40sg@mail.gmail.com/ > Cc: stable@vger.kernel.org > Assisted-by: Claude Code:claude-opus-5 > Signed-off-by: Jiaming Zhang <r772577952@gmail.com> > --- > fs/ext4/xattr.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c > index 5c310747b965..2e5f39b710bd 100644 > --- a/fs/ext4/xattr.c > +++ b/fs/ext4/xattr.c > @@ -2274,6 +2274,10 @@ int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode, > if (IS_ERR(ea_inode)) > return PTR_ERR(ea_inode); > } > + > + if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) > + memset(s->first, 0, s->end - (void *)s->first); > + > error = ext4_xattr_set_entry(i, s, handle, inode, ea_inode, > false /* is_block */); > if (error) { > -- > 2.43.0 > Friendly ping, please tell me if any further change is needed. ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-14 14:09 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-15 13:03 [Linux Kernel Bug] KASAN: slab-out-of-bounds Write in ext4_xattr_set_entry Jiaming Zhang 2026-09-16 8:47 ` [PATCH] ext4: clear in-inode xattr space before adding the first xattr Jiaming Zhang 2026-09-16 8:56 ` sashiko-bot 2026-09-23 8:52 ` Jiaming Zhang 2026-09-23 11:10 ` Andreas Dilger 2026-10-14 14:08 ` Jiaming Zhang
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.