From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>,
linux-ext4@vger.kernel.org, linux-afs@lists.infradead.org,
v9fs@lists.linux.dev, linux-trace-kernel@vger.kernel.org,
ntfs3@lists.linux.dev,
"Christian Brauner (Amutable)" <brauner@kernel.org>,
stable@vger.kernel.org
Subject: [PATCH 7/8] ntfs3: free the sbi after a grace period
Date: Fri, 09 Oct 2026 23:51:57 +0200 [thread overview]
Message-ID: <20261009-work-fixes-rcu-v1-7-7446d2d335e6@kernel.org> (raw)
In-Reply-To: <20261009-work-fixes-rcu-v1-0-7446d2d335e6@kernel.org>
If the "nocase" mount option ntfs3 uses custom hash and dentry compare
functions. Both access ntfs3 superblock private information in rcu
pathwalk. ntfs_put_super() and ntfs3_kill_sb() drop the info without any
rcu delay. A lookup that's still inside the filesystem when the last
reference to the mount is dropped causes a UAF. With a pathwalk held up
in ntfs_d_hash() while the last reference to the mount goes away:
BUG: KASAN: slab-use-after-free in ntfs_nls_to_utf16+0x752/0x940
Read of size 8 at addr ff1100010453c850 by task walker/99
Call Trace:
<TASK>
ntfs_nls_to_utf16+0x752/0x940
ntfs_d_hash+0x22b/0x500
link_path_walk+0x11ad/0x1d30
path_lookupat+0x63/0xb00
filename_lookup+0x1f1/0x5b0
vfs_statx+0xd7/0x3b0
[...]
Allocated by task 95:
__kmalloc_cache_noprof+0x249/0x560
ntfs_init_fs_context+0x2db/0x5a0
alloc_fs_context+0x52e/0xc70
[...]
Freed by task 98:
kfree+0x1db/0x5b0
deactivate_locked_super+0x9c/0xf0
cleanup_mnt+0x9f/0x130
task_work_run+0x131/0x250
The bug is currently masked by the grace period in namespace_unlock()
for umount(2). Keep the necessary information until the filesystem
private info is freed and free it with an rcu delay.
ntfs3_free_sbi() now runs from an rcu callback and drops the upcase
table via ntfs_put_shared(). So switch s_shared_lock over to
spin_lock_bh(). Otherwise the callback can deadlock against a task
holding the lock on the same cpu.
Fixes: a3a956c78efa ("fs/ntfs3: Add option "nocase"")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Reproducer: delay.c holds a task named walker in ntfs_d_hash() in rcu
pathwalk mode for two seconds and repro.c drops the last reference to a
lazily unmounted ntfs3 meanwhile. The name is not ascii, so
ntfs_d_hash() goes through the sbi.
mkntfs -F -f -Q /dev/vdb
mount -t ntfs3 -o nocase,noatime /dev/vdb /mnt
echo hello > /mnt/$(printf '\303\244')
insmod delay.ko
./repro
Without this patch that gives the splat above, with it it runs clean.
delay.c:
#include <linux/delay.h>
#include <linux/kprobes.h>
#include <linux/module.h>
/* keep a task named "walker" in ntfs_d_hash() in rcu pathwalk */
static int pre(struct kprobe *p, struct pt_regs *regs)
{
if (!strcmp(current->comm, "walker"))
mdelay(2000);
return 0;
}
static struct kprobe kp = {
.symbol_name = "ntfs_d_hash",
.pre_handler = pre,
};
static int __init delay_init(void)
{
return register_kprobe(&kp);
}
static void __exit delay_exit(void)
{
unregister_kprobe(&kp);
}
module_init(delay_init);
module_exit(delay_exit);
MODULE_LICENSE("GPL");
repro.c:
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <unistd.h>
static void pin(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
sched_setaffinity(0, sizeof(set), &set);
}
static void *walker(void *arg)
{
struct stat st;
char *p;
/*
* The delay spins. Empty this cpu's lru batches so that an
* lru_add_drain_all() on the other side has no work to wait for here.
*/
pin(1);
p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memset(p, 1, 4096);
madvise(p, 4096, MADV_COLD);
prctl(PR_SET_NAME, "walker");
stat("\xc3\xa4", &st);
return NULL;
}
int main(void)
{
struct stat st;
pthread_t t;
pin(0);
chdir("/mnt");
stat("\xc3\xa4", &st);
/* the cwd now holds the last reference to the mount */
umount2("/mnt", MNT_DETACH);
pthread_create(&t, NULL, walker, NULL);
sleep(1);
/* drop it, ->kill_sb() runs before chdir() returns */
chdir("/");
pthread_join(t, NULL);
return 0;
}
---
fs/ntfs3/ntfs_fs.h | 1 +
fs/ntfs3/super.c | 33 +++++++++++++++++++++------------
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 5811d89d67b3..bbcd2e02bf63 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -211,6 +211,7 @@ struct ntfs_index {
/* Ntfs file system in-core superblock data. */
struct ntfs_sb_info {
struct super_block *sb;
+ struct rcu_head rcu;
u32 discard_granularity;
u64 discard_granularity_mask_inv; // ~(discard_granularity_mask_inv-1)
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index f4a42a0c73a4..841f9068d82f 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -196,7 +196,7 @@ void *ntfs_set_shared(void *ptr, u32 bytes)
void *ret = NULL;
int i, j = -1;
- spin_lock(&s_shared_lock);
+ spin_lock_bh(&s_shared_lock);
for (i = 0; i < ARRAY_SIZE(s_shared); i++) {
if (!s_shared[i].cnt) {
j = i;
@@ -214,7 +214,7 @@ void *ntfs_set_shared(void *ptr, u32 bytes)
s_shared[j].cnt = 1;
ret = ptr;
}
- spin_unlock(&s_shared_lock);
+ spin_unlock_bh(&s_shared_lock);
return ret;
}
@@ -231,7 +231,7 @@ void *ntfs_put_shared(void *ptr)
void *ret = ptr;
int i;
- spin_lock(&s_shared_lock);
+ spin_lock_bh(&s_shared_lock);
for (i = 0; i < ARRAY_SIZE(s_shared); i++) {
if (s_shared[i].cnt && s_shared[i].ptr == ptr) {
if (--s_shared[i].cnt)
@@ -239,7 +239,7 @@ void *ntfs_put_shared(void *ptr)
break;
}
}
- spin_unlock(&s_shared_lock);
+ spin_unlock_bh(&s_shared_lock);
return ret;
}
@@ -708,6 +708,8 @@ static noinline void ntfs3_put_sbi(struct ntfs_sb_info *sbi)
static void ntfs3_free_sbi(struct ntfs_sb_info *sbi)
{
+ if (sbi->options)
+ put_mount_options(sbi->options);
kfree(sbi->new_rec);
kvfree(ntfs_put_shared(sbi->upcase));
kvfree(sbi->def_table);
@@ -719,6 +721,11 @@ static void ntfs3_free_sbi(struct ntfs_sb_info *sbi)
kfree(sbi);
}
+static void ntfs3_free_sbi_rcu(struct rcu_head *head)
+{
+ ntfs3_free_sbi(container_of(head, struct ntfs_sb_info, rcu));
+}
+
static void ntfs_put_super(struct super_block *sb)
{
struct ntfs_sb_info *sbi = sb->s_fs_info;
@@ -728,11 +735,11 @@ static void ntfs_put_super(struct super_block *sb)
/* Mark rw ntfs as clear, if possible. */
ntfs_set_state(sbi, NTFS_DIRTY_CLEAR);
- if (sbi->options) {
- put_mount_options(sbi->options);
- sbi->options = NULL;
- }
-
+ /*
+ * The mount options stay until the sbi is freed: ->d_hash() and
+ * ->d_compare() of a pathwalk in rcu mode read the nls table through
+ * them.
+ */
ntfs3_put_sbi(sbi);
}
@@ -1939,9 +1946,11 @@ static void ntfs3_kill_sb(struct super_block *sb)
kill_block_super(sb);
- if (sbi->options)
- put_mount_options(sbi->options);
- ntfs3_free_sbi(sbi);
+ /*
+ * A pathwalk in rcu mode may still be in ->d_hash() or ->d_compare()
+ * and read the upcase table and the nls table through the sbi.
+ */
+ call_rcu(&sbi->rcu, ntfs3_free_sbi_rcu);
}
// clang-format off
--
2.53.0
next prev parent reply other threads:[~2026-10-09 21:52 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 21:51 [PATCH 0/8] fs: fix a range of UAFs during rcu pathwalk caused by non-rcu frees Christian Brauner
2026-10-09 21:51 ` [PATCH 1/8] ext4: free the sbi after a grace period Christian Brauner
2026-10-09 22:04 ` sashiko-bot
2026-10-09 21:51 ` [PATCH 2/8] afs: free the super info " Christian Brauner
2026-10-09 22:07 ` sashiko-bot
2026-10-10 12:54 ` Christian Brauner
2026-10-09 21:51 ` [PATCH 3/8] 9p: free the session " Christian Brauner
2026-10-09 22:03 ` sashiko-bot
2026-10-09 21:51 ` [PATCH 4/8] eventfs: free the eventfs_inode after an rcu grace period too Christian Brauner
2026-10-09 21:58 ` sashiko-bot
2026-10-09 21:51 ` [PATCH 5/8] ipc: drop the mqueue mount with kern_unmount() on setup failure Christian Brauner
2026-10-09 22:00 ` sashiko-bot
2026-10-09 21:51 ` [PATCH 6/8] unicode: free the map after a grace period Christian Brauner
2026-10-09 22:04 ` sashiko-bot
2026-10-09 21:51 ` Christian Brauner [this message]
2026-10-09 22:03 ` [PATCH 7/8] ntfs3: free the sbi " sashiko-bot
2026-10-09 21:51 ` [PATCH 8/8] ntfs3: free replaced mount options " Christian Brauner
2026-10-09 22:02 ` sashiko-bot
2026-10-10 12:55 ` Christian Brauner
2026-10-12 10:36 ` [PATCH 0/8] fs: fix a range of UAFs during rcu pathwalk caused by non-rcu frees Jan Kara
2026-10-12 10:42 ` Christian Brauner
2026-10-13 10:47 ` Jan Kara
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261009-work-fixes-rcu-v1-7-7446d2d335e6@kernel.org \
--to=brauner@kernel.org \
--cc=jack@suse.cz \
--cc=linux-afs@lists.infradead.org \
--cc=linux-ext4@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=ntfs3@lists.linux.dev \
--cc=stable@vger.kernel.org \
--cc=v9fs@lists.linux.dev \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox