Re: KASAN: slab-out-of-bounds in udf_adinicb_writepages
From: Jan Kara
Date: Fri Mar 20 2026 - 11:30:13 EST
Hello,
On Wed 11-03-26 15:47:30, Jianzhou Zhao wrote:
> Subject: [BUG] udf: KASAN: slab-out-of-bounds in udf_adinicb_writepages
>
> Dear Maintainers,
>
> We are writing to report a KASAN-detected slab-out-of-bounds
> vulnerability in the Linux kernel within the UDF subsystem. This bug was
> found by our custom fuzzing tool, RacePilot. The bug occurs because
> `udf_adinicb_writepages` blind-copies folio data up to
> `i_size_read(inode)` into a fixed-size `iinfo->i_data` allocation, which
> is bound by the superblock blocksize minus the UDF file entry structure
> size. When a file grows beyond this capacity without being expanded out
> of its ICB, a buffer overflow takes place. We observed this on the Linux
> kernel version 6.18.0-08691-g2061f18ad76e-dirty.
Ho hum, your fix is definitely wrong but I think I can see what's
happening. udf_expand_file_adinicb() runs with all kinds of locks (inode
lock, i_data_sem, folio lock) but udf_writepages() is called without any
locks and thus the decision to call udf_adinicb_writepages() and following
copy of data into inode can race with file expansion resulting in nasty
corruption as you report. I'll have to think a bit how to properly fix
this. Thanks for report!
Honza
>
> Call Trace & Context
> ==================================================================
> BUG: KASAN: slab-out-of-bounds in memcpy_from_file_folio include/linux/highmem.h:629 [inline]
> BUG: KASAN: slab-out-of-bounds in udf_adinicb_writepages fs/udf/inode.c:195 [inline]
> BUG: KASAN: slab-out-of-bounds in udf_writepages+0x380/0x470 fs/udf/inode.c:211
> Write of size 4096 at addr ffff88804e3a1400 by task kworker/u10:5/1152
>
> CPU: 1 UID: 0 PID: 1152 Comm: kworker/u10:5 Not tainted 6.18.0-08691-g2061f18ad76e-dirty #43 PREEMPT(full)
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
> Workqueue: writeback wb_workfn (flush-7:4)
> Call Trace:
> <TASK>
> __dump_stack lib/dump_stack.c:94 [inline]
> dump_stack_lvl+0x116/0x1b0 lib/dump_stack.c:120
> print_address_description mm/kasan/report.c:378 [inline]
> print_report+0xca/0x5f0 mm/kasan/report.c:482
> kasan_report+0xca/0x100 mm/kasan/report.c:595
> check_region_inline mm/kasan/generic.c:194 [inline]
> kasan_check_range+0x39/0x1c0 mm/kasan/generic.c:200
> __asan_memcpy+0x3d/0x60 mm/kasan/shadow.c:106
> memcpy_from_file_folio include/linux/highmem.h:629 [inline]
> udf_adinicb_writepages fs/udf/inode.c:195 [inline]
> udf_writepages+0x380/0x470 fs/udf/inode.c:211
> do_writepages+0x242/0x5b0 mm/page-writeback.c:2608
> __writeback_single_inode+0x127/0x12c0 fs/fs-writeback.c:1743
> writeback_sb_inodes+0x71a/0x1b20 fs/fs-writeback.c:2049
> __writeback_inodes_wb+0xbe/0x270 fs/fs-writeback.c:2129
> wb_writeback+0x6dd/0xae0 fs/fs-writeback.c:2240
> wb_check_start_all fs/fs-writeback.c:2365 [inline]
> wb_do_writeback fs/fs-writeback.c:2390 [inline]
> wb_workfn+0x87e/0xb80 fs/fs-writeback.c:2423
> process_one_work+0x90e/0x1b10 kernel/workqueue.c:3262
> process_scheduled_works kernel/workqueue.c:3345 [inline]
> worker_thread+0x67e/0xe90 kernel/workqueue.c:3426
> kthread+0x3d0/0x780 kernel/kthread.c:463
> ret_from_fork+0x966/0xaf0 arch/x86/kernel/process.c:161
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
> </TASK>
>
> Allocated by task 23775:
> kasan_save_stack+0x24/0x50 mm/kasan/common.c:56
> kasan_save_track+0x14/0x30 mm/kasan/common.c:77
> poison_kmalloc_redzone mm/kasan/common.c:400 [inline]
> __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:417
> kasan_kmalloc include/linux/kasan.h:262 [inline]
> __do_kmalloc_node mm/slub.c:5652 [inline]
> __kmalloc_noprof+0x32c/0x940 mm/slub.c:5664
> kmalloc_noprof include/linux/slab.h:961 [inline]
> kzalloc_noprof include/linux/slab.h:1094 [inline]
> udf_new_inode+0xab4/0xe60 fs/udf/ialloc.c:56
> ...
>
> The buggy address belongs to the object at ffff88804e3a1400
> which belongs to the cache kmalloc-512 of size 512
> The buggy address is located 0 bytes inside of
> allocated 336-byte region [ffff88804e3a1400, ffff88804e3a1550)
> ==================================================================
>
> Execution Flow & Code Context
> When a new UDF inode is created `udf_new_inode` allocates an internal `i_data` slab cache tailored precisely to fit inline file information within a single logical block:
> ```c
> // fs/udf/ialloc.c
> struct inode *udf_new_inode(struct inode *dir, umode_t mode)
> {
> ...
> iinfo->i_data = kzalloc(inode->i_sb->s_blocksize -
> sizeof(struct fileEntry),
> GFP_KERNEL);
> ...
> }
> ```
> If the file operates in ICB (In-ICB data allocation), the file payload resides fully buffered in `iinfo->i_data`. During dirty page writeback, `udf_adinicb_writepages` iterates the data mapping copying directly from the page folio into `iinfo->i_data`:
> ```c
> // fs/udf/inode.c
> static int udf_adinicb_writepages(struct address_space *mapping,
> struct writeback_control *wbc)
> {
> struct inode *inode = mapping->host;
> struct udf_inode_info *iinfo = UDF_I(inode);
> struct folio *folio = NULL;
> int error = 0;
>
> while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
> BUG_ON(!folio_test_locked(folio));
> BUG_ON(folio->index != 0);
> memcpy_from_file_folio(iinfo->i_data + iinfo->i_lenEAttr, folio,
> 0, i_size_read(inode)); // <-- Out of bounds write
> folio_unlock(folio);
> }
> ...
> }
> ```
>
> Root Cause Analysis
> A slab-out-of-bounds defect occurs because `udf_adinicb_writepages` trusts `i_size_read(inode)` to determine the length parameter for the `memcpy`. However, a malicious or fuzzed operation can stretch the generic `i_size` past the maximum limit bound by the slab allocation limit (`inode->i_sb->s_blocksize - sizeof(struct fileEntry)`) without triggering `udf_expand_file_adinicb` logic appropriately. Because `memcpy_from_file_folio` writes into the exact chunk boundary, anything exceeding the residual allocation capacity writes straight over the adjacent kmalloc chunk redzones.
> Unfortunately, we were unable to generate a reproducer for this bug.
>
> Potential Impact
> This out-of-bounds write is severe as the overflowing buffer is fully externally controlled from user-space data stored in the folio. This capability enables targeted memory corruption into adjacent slab objects (e.g., inside kmalloc-512 allocations), likely leading to Privilege Escalation or remote code execution primitives, or at minimum triggering Denial-of-Service via Kernel Panics.
>
> Proposed Fix
> A swift defensive measure is enforcing a capacity sanity limit constraint before committing the memory copy inside `udf_adinicb_writepages()`. The maximum allowed write length should be strictly bounded to `iinfo->i_lenAlloc` or the underlying static allocation limit `inode->i_sb->s_blocksize - sizeof(struct fileEntry)`.
>
> ```diff
> --- a/fs/udf/inode.c
> +++ b/fs/udf/inode.c
> @@ -188,12 +188,14 @@ static int udf_adinicb_writepages(struct address_space *mapping,
> struct udf_inode_info *iinfo = UDF_I(inode);
> struct folio *folio = NULL;
> int error = 0;
> + size_t max_len;
>
> while ((folio = writeback_iter(mapping, wbc, folio, &error))) {
> BUG_ON(!folio_test_locked(folio));
> BUG_ON(folio->index != 0);
> + max_len = min_t(size_t, i_size_read(inode), iinfo->i_lenAlloc);
> memcpy_from_file_folio(iinfo->i_data + iinfo->i_lenEAttr, folio,
> - 0, i_size_read(inode));
> + 0, max_len);
> folio_unlock(folio);
> }
> ```
>
> We would be highly honored if this could be of any help.
>
> Best regards,
> RacePilot Team
--
Jan Kara <jack@xxxxxxxx>
SUSE Labs, CR