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

From: XIAO WU

Date: Sat Jun 20 2026 - 20:48:00 EST


From: XIAOWU <xiaowu.417@xxxxxx>
To: Kyle Zeng <kylebot@xxxxxxxxxx>
Cc: linux-kernel@xxxxxxxxxxxxxxx, linux-fsdevel@xxxxxxxxxxxxxxx
Subject: Re: [PATCH] fs/ntfs3: reserve NUL byte when converting UTF-16 names
In-Reply-To: <20260611213331.16763-1-kylebot@xxxxxxxxxx>

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