KASAN: vmalloc-out-of-bounds Write in vfree_atomic
From: Jianzhou Zhao
Date: Wed Mar 11 2026 - 04:49:57 EST
To: linkinjeon@xxxxxxxxxx, sj1557.seo@xxxxxxxxxxx
Cc: linux-fsdevel@xxxxxxxxxxxxxxx, linux-kernel@xxxxxxxxxxxxxxx
Subject: [BUG] exfat: KASAN: vmalloc-out-of-bounds Write in delayed_free due to double-free of vol_utbl
Dear exFAT Maintainers,
Our custom fuzzing tool, RacePilot, has detected a vmalloc-out-of-bounds write inside the `delayed_free` function. The underlying issue is a double-free vulnerability regarding the upcase table (`vol_utbl`).
### Call Trace:
```
BUG: KASAN: vmalloc-out-of-bounds in llist_add_batch+0x15f/0x180 lib/llist.c:32
Write of size 8 at addr ffffc90005319000 by task syz.4.373/14262
Call Trace:
<IRQ>
...
kasan_report+0x96/0xd0 mm/kasan/report.c:634
llist_add_batch+0x15f/0x180 lib/llist.c:32
llist_add include/linux/llist.h:248 [inline]
vfree_atomic+0x5e/0xe0 mm/vmalloc.c:3326
vfree+0x708/0x8a0 mm/vmalloc.c:3353
delayed_free+0x49/0xb0 fs/exfat/super.c:799
rcu_do_batch kernel/rcu/tree.c:2568 [inline]
...
```
### Underlying Root Cause:
The issue occurs when `exfat_create_upcase_table()` fails to load an upcase table and subsequently fails to load the default fallback due to memory exhaustion (-ENOMEM), causing `sbi->vol_utbl` to be double-freed.
1. During `exfat_create_upcase_table()`, if `exfat_load_upcase_table()` returns an error other than `-EIO` (e.g. `-EINVAL` from a checksum mismatch), the code triggers `exfat_free_upcase_table()` to free the invalid table before jumping to `load_default`.
2. Inside `exfat_free_upcase_table()`, `kvfree(sbi->vol_utbl)` is executed, but `sbi->vol_utbl` is **not set to NULL**.
3. In `load_default:`, the execution calls `exfat_load_default_upcase_table(sb)`.
4. If the system is under memory pressure, `kvcalloc()` inside the default loader fails and returns `-ENOMEM`. Because of this failure, `sbi->vol_utbl` is not overwritten with a new pointer, leaving it holding the previously freed vmalloc address.
5. The `-ENOMEM` failure propagates up, aborting the filesystem mount. The VFS superblock teardown eventually processes to `exfat_kill_sb()`, queuing the RCU callback `delayed_free()`.
6. When `delayed_free()` asynchronously runs, it again calls `exfat_free_upcase_table(sbi)`.
7. `kvfree()` is called for a second time on the old `sbi->vol_utbl`, constituting a double free.
8. Because `delayed_free` executes in a softirq context, `kvfree()` delegates the cleanup to `vfree_atomic()`. `vfree_atomic` casts the virtual address to an `llist_node` and writes its `next` pointer. KASAN catches this illicit write operation on previously freed, unmapped vmalloc memory, emitting the `vmalloc-out-of-bounds` exception.
### Key Code Snippets:
In `fs/exfat/nls.c`, `exfat_create_upcase_table()`:
```c
ret = exfat_load_upcase_table(sb, sector, num_sectors, ...);
brelse(bh);
if (ret && ret != -EIO) {
/* free memory from exfat_load_upcase_table call */
exfat_free_upcase_table(sbi); // [1] Frees ->vol_utbl but leaves a dangling pointer
goto load_default;
}
...
load_default:
/* load default upcase table */
return exfat_load_default_upcase_table(sb); // [2] Fails on kvcalloc and returns -ENOMEM
```
In `fs/exfat/super.c`, inside `delayed_free()`:
```c
static void delayed_free(struct rcu_head *p)
{
...
exfat_free_upcase_table(sbi); // [3] Second invocation leads to double free UAF
exfat_free_sbi(sbi);
}
```
### Proposed Fix:
The solution is natively simple: nullify `sbi->vol_utbl` directly inside `exfat_free_upcase_table()` after freeing it. This safely converts the secondary asynchronous `kvfree` into a no-op.
```c
void exfat_free_upcase_table(struct exfat_sb_info *sbi)
{
kvfree(sbi->vol_utbl);
+ sbi->vol_utbl = NULL;
}
```
Best regards,
The RacePilot Team