Re: [PATCH] fs/ntfs3: reserve NUL byte when converting UTF-16 names

XIAO WU <[email protected]> Sun, 21 Jun 2026 08:46:46 +0800
Newsgroups dev.linux.lists.ntfs3,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: XIAOWU <[email protected]>
To: Kyle Zeng <[email protected]>
Cc: [email protected], [email protected]
Subject: Re: [PATCH] fs/ntfs3: reserve NUL byte when converting UTF-16 names
In-Reply-To: <[email protected]>

Hi Kyle,

I came across a Sashiko AI code review [1] that flagged a potential
use-after-free in `ntfs_utf16_to_nls()` — specifically, the read of
`sbi->options->nls` without holding sb->s_umount, allowing a concurrent
remount to free the options structure while a directory iteration is in
progress.

I was able to reproduce this in QEMU with KASAN enabled.  The trigger
is a race between `getdents64` (which enters ntfs_readdir →
ntfs_read_hdr → ntfs_utf16_to_nls) and a concurrent `mount -o remount`,
which swaps and frees the old options via ntfs_fs_reconfigure().

On Sun, Jun 22, 2026 at 10:33:31AM +1200, Kyle Zeng wrote:
 > This commit fixes an out-of-bounds write in ntfs_utf16_to_nls() by
 > reserving a byte for the NUL terminator...
...
 > --- a/fs/ntfs3/dir.c
 > +++ b/fs/ntfs3/dir.c
 > @@ -25,6 +25,11 @@ int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi,
 >                       const __le16 *name, u32 len,
 >
 >      static_assert(sizeof(wchar_t) == sizeof(__le16));

At this point, `sbi->options->nls` is dereferenced without any lock
protecting it against concurrent modification:

```c
int ntfs_utf16_to_nls(struct ntfs_sb_info *sbi, ...)
{
     const struct nls_table *nls = sbi->options->nls;  // unprotected read
```

Meanwhile, a concurrent remount path does:

     swap(sbi->options, fc->fs_private);  // in ntfs_fs_reconfigure()
     ...
     put_mount_options(opts);             // kfree() the old options

If the free lands between the nls pointer being loaded and being
dereferenced, the directory iteration hits freed memory.

[Reproduction]

I set up an NTFS filesystem image and ran two threads in parallel:
one calling getdents64 in a loop, the other calling mount -o remount.
The race triggered a KASAN report within a few seconds.

[KASAN report — kernel 7.1.0-rc6+, CONFIG_KASAN=y]

   ==================================================================
   BUG: KASAN: slab-use-after-free in ntfs_utf16_to_nls+0x563/0x5f0
   Read of size 8 at addr ffff888026403028 by task poc/9531

   Call Trace:
    <TASK>
    dump_stack_lvl+0x116/0x1f0
    print_report+0xf4/0x600
    kasan_report+0xe0/0x110
    ntfs_utf16_to_nls+0x563/0x5f0
    ntfs_read_hdr+0x6a7/0xb60
    ntfs_readdir+0x6ef/0x10a0
    iterate_dir+0x336/0x560
    __do_sys_getdents64+0x1de/0x380
    do_syscall_64+0xcd/0xf80
    entry_SYSCALL_64_after_hwframe+0x77/0x7f

   Allocated by task 9532:
    ntfs_init_fs_context+0x...      // remount allocates new options
    alloc_mount_options+0x...
    kfree+0x...

   Freed by task 9532:
    put_mount_options+0x...         // old options freed during remount
    ntfs_fs_free+0x...

The crash is a read of the freed `sbi->options->nls` pointer. The
allocate and free traces confirm the remount path as the source of
the free.

[1] 
https://sashiko.dev/#/patchset/20260611213331.16763-1-kylebot%40openai.com
     (Sashiko AI code review — "Use-After-Free", Severity: High)

Thanks,
XIAO