KASAN: slab-out-of-bounds in udf_adinicb_writepages

From: Jianzhou Zhao

Date: Wed Mar 11 2026 - 03:48:08 EST




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.

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