Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH] ntfs: fix default_upcase refcount underflow and UAF on fs_context teardown
@ 2026-05-05 13:07 DaeMyung Kang
  2026-05-06  5:57 ` Hyunchul Lee
  2026-05-06 11:34 ` Namjae Jeon
  0 siblings, 2 replies; 3+ messages in thread
From: DaeMyung Kang @ 2026-05-05 13:07 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang

ntfs_init_fs_context() allocates a fresh ntfs_volume with vol->upcase
left as NULL. ntfs_free_fs_context() unconditionally calls
ntfs_volume_free() during fs_context teardown, even when ntfs_fill_super()
never ran or already cleaned up. ntfs_volume_free() then executes:

	mutex_lock(&ntfs_lock);
	if (vol->upcase == default_upcase) {
		ntfs_nr_upcase_users--;
		vol->upcase = NULL;
	}

When the global default_upcase is also NULL (very first mount attempt,
or all prior mounts have released the table), the comparison is
NULL == NULL, and ntfs_nr_upcase_users is decremented even though this
volume never claimed a reference. ntfs_nr_upcase_users is unsigned long,
so the decrement wraps to ULONG_MAX.

A subsequent successful mount can then free the shared table while the
mounted volume still points at it:

  1. ntfs_fill_super() does the temporary ntfs_nr_upcase_users++ at the
     "Generate the global default upcase table if necessary" block. With
     the prior wraparound this brings the counter back to 0.
  2. If the volume's $UpCase matches the default, the match path does
     ntfs_nr_upcase_users++ and sets vol->upcase = default_upcase. The
     counter is now 1.
  3. On the success path, !--ntfs_nr_upcase_users evaluates true and
     default_upcase is kvfree()'d while vol->upcase still points at it.
     Subsequent upcase comparisons through that mount touch freed
     memory.

This was reproduced with KASAN by closing a fresh fsopen("ntfs") context,
then mounting an NTFS image whose $UpCase table matches
generate_default_upcase(), and finally doing a case-insensitive lookup.
KASAN reports the dangling vol->upcase access:

  BUG: KASAN: use-after-free in ntfs_collate_names+0x3b4/0x420
  Read of size 2 at addr ffff888008d40048 by task init/1
   ntfs_collate_names+0x3b4/0x420
   ntfs_lookup_inode_by_name+0x1921/0x3130
   ntfs_lookup+0x193/0xc40
   vfs_statx+0xc7/0x190
   vfs_fstatat+0x4b/0xa0
   __do_sys_newfstatat+0x92/0xf0

The same QEMU reproducer was rerun after this change with KASAN
enabled. It reached "reproducer finished", and the log contained no
KASAN, use-after-free, Oops, or panic signatures.

Guard each comparison with an explicit vol->upcase non-NULL check so a
volume that never took a reference cannot decrement the global users
counter. Apply the same guard to the other default_upcase release sites
so all cleanup paths follow the same ownership rule: only volumes that
actually hold a default_upcase reference may drop one.

Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 fs/ntfs/super.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 22dc7865eca7..e9de84fb8297 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -1671,7 +1671,7 @@ static bool load_system_files(struct ntfs_volume *vol)
 iput_upcase_err_out:
 	vol->upcase_len = 0;
 	mutex_lock(&ntfs_lock);
-	if (vol->upcase == default_upcase) {
+	if (vol->upcase && vol->upcase == default_upcase) {
 		ntfs_nr_upcase_users--;
 		vol->upcase = NULL;
 	}
@@ -1701,7 +1701,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol)
 	 * the number of upcase users if we are a user.
 	 */
 	mutex_lock(&ntfs_lock);
-	if (vol->upcase == default_upcase) {
+	if (vol->upcase && vol->upcase == default_upcase) {
 		ntfs_nr_upcase_users--;
 		vol->upcase = NULL;
 	}
@@ -2494,7 +2494,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
 	}
 	vol->upcase_len = 0;
 	mutex_lock(&ntfs_lock);
-	if (vol->upcase == default_upcase) {
+	if (vol->upcase && vol->upcase == default_upcase) {
 		ntfs_nr_upcase_users--;
 		vol->upcase = NULL;
 	}
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] ntfs: fix default_upcase refcount underflow and UAF on fs_context teardown
  2026-05-05 13:07 [PATCH] ntfs: fix default_upcase refcount underflow and UAF on fs_context teardown DaeMyung Kang
@ 2026-05-06  5:57 ` Hyunchul Lee
  2026-05-06 11:34 ` Namjae Jeon
  1 sibling, 0 replies; 3+ messages in thread
From: Hyunchul Lee @ 2026-05-06  5:57 UTC (permalink / raw)
  To: DaeMyung Kang; +Cc: linkinjeon, linux-fsdevel, linux-kernel

2026년 5월 5일 (화) 오후 10:07, DaeMyung Kang <charsyam@gmail.com>님이 작성:
>
> ntfs_init_fs_context() allocates a fresh ntfs_volume with vol->upcase
> left as NULL. ntfs_free_fs_context() unconditionally calls
> ntfs_volume_free() during fs_context teardown, even when ntfs_fill_super()
> never ran or already cleaned up. ntfs_volume_free() then executes:
>
>         mutex_lock(&ntfs_lock);
>         if (vol->upcase == default_upcase) {
>                 ntfs_nr_upcase_users--;
>                 vol->upcase = NULL;
>         }
>
> When the global default_upcase is also NULL (very first mount attempt,
> or all prior mounts have released the table), the comparison is
> NULL == NULL, and ntfs_nr_upcase_users is decremented even though this
> volume never claimed a reference. ntfs_nr_upcase_users is unsigned long,
> so the decrement wraps to ULONG_MAX.
>
> A subsequent successful mount can then free the shared table while the
> mounted volume still points at it:
>
>   1. ntfs_fill_super() does the temporary ntfs_nr_upcase_users++ at the
>      "Generate the global default upcase table if necessary" block. With
>      the prior wraparound this brings the counter back to 0.
>   2. If the volume's $UpCase matches the default, the match path does
>      ntfs_nr_upcase_users++ and sets vol->upcase = default_upcase. The
>      counter is now 1.
>   3. On the success path, !--ntfs_nr_upcase_users evaluates true and
>      default_upcase is kvfree()'d while vol->upcase still points at it.
>      Subsequent upcase comparisons through that mount touch freed
>      memory.
>
> This was reproduced with KASAN by closing a fresh fsopen("ntfs") context,
> then mounting an NTFS image whose $UpCase table matches
> generate_default_upcase(), and finally doing a case-insensitive lookup.
> KASAN reports the dangling vol->upcase access:
>
>   BUG: KASAN: use-after-free in ntfs_collate_names+0x3b4/0x420
>   Read of size 2 at addr ffff888008d40048 by task init/1
>    ntfs_collate_names+0x3b4/0x420
>    ntfs_lookup_inode_by_name+0x1921/0x3130
>    ntfs_lookup+0x193/0xc40
>    vfs_statx+0xc7/0x190
>    vfs_fstatat+0x4b/0xa0
>    __do_sys_newfstatat+0x92/0xf0
>
> The same QEMU reproducer was rerun after this change with KASAN
> enabled. It reached "reproducer finished", and the log contained no
> KASAN, use-after-free, Oops, or panic signatures.
>
> Guard each comparison with an explicit vol->upcase non-NULL check so a
> volume that never took a reference cannot decrement the global users
> counter. Apply the same guard to the other default_upcase release sites
> so all cleanup paths follow the same ownership rule: only volumes that
> actually hold a default_upcase reference may drop one.
>
> Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"")
> Signed-off-by: DaeMyung Kang <charsyam@gmail.com>

Looks good to me.

Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>


> ---
>  fs/ntfs/super.c | 6 +++---
>  1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
> index 22dc7865eca7..e9de84fb8297 100644
> --- a/fs/ntfs/super.c
> +++ b/fs/ntfs/super.c
> @@ -1671,7 +1671,7 @@ static bool load_system_files(struct ntfs_volume *vol)
>  iput_upcase_err_out:
>         vol->upcase_len = 0;
>         mutex_lock(&ntfs_lock);
> -       if (vol->upcase == default_upcase) {
> +       if (vol->upcase && vol->upcase == default_upcase) {
>                 ntfs_nr_upcase_users--;
>                 vol->upcase = NULL;
>         }
> @@ -1701,7 +1701,7 @@ static void ntfs_volume_free(struct ntfs_volume *vol)
>          * the number of upcase users if we are a user.
>          */
>         mutex_lock(&ntfs_lock);
> -       if (vol->upcase == default_upcase) {
> +       if (vol->upcase && vol->upcase == default_upcase) {
>                 ntfs_nr_upcase_users--;
>                 vol->upcase = NULL;
>         }
> @@ -2494,7 +2494,7 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
>         }
>         vol->upcase_len = 0;
>         mutex_lock(&ntfs_lock);
> -       if (vol->upcase == default_upcase) {
> +       if (vol->upcase && vol->upcase == default_upcase) {
>                 ntfs_nr_upcase_users--;
>                 vol->upcase = NULL;
>         }
> --
> 2.43.0



--
Thanks,
Hyunchul

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] ntfs: fix default_upcase refcount underflow and UAF on fs_context teardown
  2026-05-05 13:07 [PATCH] ntfs: fix default_upcase refcount underflow and UAF on fs_context teardown DaeMyung Kang
  2026-05-06  5:57 ` Hyunchul Lee
@ 2026-05-06 11:34 ` Namjae Jeon
  1 sibling, 0 replies; 3+ messages in thread
From: Namjae Jeon @ 2026-05-06 11:34 UTC (permalink / raw)
  To: DaeMyung Kang; +Cc: hyc.lee, linux-fsdevel, linux-kernel

On Tue, May 5, 2026 at 10:07 PM DaeMyung Kang <charsyam@gmail.com> wrote:
>
> ntfs_init_fs_context() allocates a fresh ntfs_volume with vol->upcase
> left as NULL. ntfs_free_fs_context() unconditionally calls
> ntfs_volume_free() during fs_context teardown, even when ntfs_fill_super()
> never ran or already cleaned up. ntfs_volume_free() then executes:
>
>         mutex_lock(&ntfs_lock);
>         if (vol->upcase == default_upcase) {
>                 ntfs_nr_upcase_users--;
>                 vol->upcase = NULL;
>         }
>
> When the global default_upcase is also NULL (very first mount attempt,
> or all prior mounts have released the table), the comparison is
> NULL == NULL, and ntfs_nr_upcase_users is decremented even though this
> volume never claimed a reference. ntfs_nr_upcase_users is unsigned long,
> so the decrement wraps to ULONG_MAX.
>
> A subsequent successful mount can then free the shared table while the
> mounted volume still points at it:
>
>   1. ntfs_fill_super() does the temporary ntfs_nr_upcase_users++ at the
>      "Generate the global default upcase table if necessary" block. With
>      the prior wraparound this brings the counter back to 0.
>   2. If the volume's $UpCase matches the default, the match path does
>      ntfs_nr_upcase_users++ and sets vol->upcase = default_upcase. The
>      counter is now 1.
>   3. On the success path, !--ntfs_nr_upcase_users evaluates true and
>      default_upcase is kvfree()'d while vol->upcase still points at it.
>      Subsequent upcase comparisons through that mount touch freed
>      memory.
>
> This was reproduced with KASAN by closing a fresh fsopen("ntfs") context,
> then mounting an NTFS image whose $UpCase table matches
> generate_default_upcase(), and finally doing a case-insensitive lookup.
> KASAN reports the dangling vol->upcase access:
>
>   BUG: KASAN: use-after-free in ntfs_collate_names+0x3b4/0x420
>   Read of size 2 at addr ffff888008d40048 by task init/1
>    ntfs_collate_names+0x3b4/0x420
>    ntfs_lookup_inode_by_name+0x1921/0x3130
>    ntfs_lookup+0x193/0xc40
>    vfs_statx+0xc7/0x190
>    vfs_fstatat+0x4b/0xa0
>    __do_sys_newfstatat+0x92/0xf0
>
> The same QEMU reproducer was rerun after this change with KASAN
> enabled. It reached "reproducer finished", and the log contained no
> KASAN, use-after-free, Oops, or panic signatures.
>
> Guard each comparison with an explicit vol->upcase non-NULL check so a
> volume that never took a reference cannot decrement the global users
> counter. Apply the same guard to the other default_upcase release sites
> so all cleanup paths follow the same ownership rule: only volumes that
> actually hold a default_upcase reference may drop one.
>
> Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"")
> Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
Applied it to #ntfs-next.
Thanks!

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-05-06 11:35 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-05 13:07 [PATCH] ntfs: fix default_upcase refcount underflow and UAF on fs_context teardown DaeMyung Kang
2026-05-06  5:57 ` Hyunchul Lee
2026-05-06 11:34 ` Namjae Jeon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox