projects/linux·commit 1dbb7422

Merge tag 'vfs-7.3-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

1dbb7422 · Linus Torvalds <[email protected]> · 2026-10-16 07:53:16 -07:00

Pull vfs fixes from Christian Brauner:

 - Fix a range of use-after-frees during rcu pathwalk caused by non-rcu
   frees.

   Whatever ->permission(), ->d_revalidate(), ->get_link(), ->d_hash()
   and ->d_compare() read in rcu mode has to be freed after a grace
   period. umount(2) hides most of it because namespace_unlock() waits
   for one, but a chdir() out of a lazily unmounted directory or the
   close() of the last file on it drops the last reference without.

    * ext4_get_link() reads the sbi and the extent status counters for a
      symlink stored in a data block. Both are freed after a grace
      period now.

    * The unicode map of casefolded ext4, f2fs and tmpfs was freed under
      generic_ci_d_hash() and generic_ci_d_compare().

    * ntfs3 with nocase freed its sbi and mount options under its dentry
      hash and compare. A remount freed the old options right away too.

    * eventfs freed an eventfs_inode after an SRCU grace period only
      while tracefs_d_revalidate() reads it under rcu_read_lock().

    * afs and 9p are only saved by the grace period their private bdi
      gets in bdi_unregister(). They stop depending on it.

    * create_ipc_ns() leaked the mqueue mount and its superblock on a
      late failure. A long-term mount needs kern_unmount().

 - Don't let idle threads pin the initramfs.

   The idle threads of the secondary cpus are forked from PID 1 with a
   private copy of its fs_struct, aren't on the task list so
   pivot_root() never moves them and never exit. So the initramfs stayed
   pinned forever on every machine with more than one cpu, 70 to 150 MB
   in the reports. They share init_fs now like init_task does.

 - A mount that copy_tree() leaves out can't take over a lock.

   copy_tree() drops mounts of mount namespace files from the copies it
   makes for unshare(CLONE_NEWNS) and propagation without looking at
   MNT_LOCKED. So root in a user namespace could bind a child's
   /proc/<pid>/ns/mnt beneath a locked cover, let it take the lock over,
   unshare(CLONE_NEWNS) and read the file. copy_tree() refuses with
   EPERM now and the move beneath a locked mount is refused up front.

 - Revert "namei: don't reveal overmounted entries in refwalk" from the
   rc7 fixes. The bug class is bigger than that patch.

 - namei: lookup_fast_for_open() checked ->d_inode while
   traverse_mounts() checks the type in d_flags, which is set second, so
   a refwalk racing the instantiation could fail an O_CREAT open with
   ENOENT. Use d_is_negative() in both places.

 - Drop the smp_mb()s in mntput_no_expire_slowpath() and do_umount()
   that mnt_get_count()'s own barrier made redundant.

 - fsnotify: a mark added while fsnotify_destroy_marks() walks the
   connector could be missed and the connector detached, so an fdinfo
   read called igrab(NULL). New marks are refused during teardown.

 - pipe: a failed second fasync enable on an O_RDWR FIFO removed the
   existing reader entry and cleared FASYNC while the writer entry
   stayed, a use-after-free in send_sigio() later.

 - watch_queue: post_one_notification() could see nr_notes before the
   note pool behind it and dereference a NULL array. An unprivileged
   user can trigger that with a key watch. Publish nr_notes last.

 - pid_namespace: zap_pid_ns_processes() waits without bound for a
   tracer outside the namespace to reap its zombies and neither wait was
   freezable, so every suspend attempt failed.

 - exit: is_child_subreaper and has_child_subreaper shared a word with
   other bitfields written without a common lock, so racing prctl()s
   lost a bit. They are separate bools now.

 - autofs: a notification write that failed on a pipe the daemon had
   already replaced put the superblock into catatonic mode again and
   closed the replacement. Only do that for the current pipe.

 - ovl: reject a lowerdir ending with an unescaped backslash with EINVAL
   instead of a confusing ENOENT later.

 - ocfs2/dlm: take dlm_reco_state_lock when the debugfs state file walks
   the recovery participant list.

 - samples/pidfd: fix the userprogs typo that kept pidfd-metadata from
   being built.

* tag 'vfs-7.3-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits)
  Revert "namei: don't reveal overmounted entries in refwalk"
  selftests/filesystems: test that a mntns file can't take over a lock
  fs: let idle threads share init_fs
  namespace: a mount that copy_tree() leaves out can't take over a lock
  namei: test for a negative dentry the way traverse_mounts() does
  fs: pass flags to copy_fs()
  ovl: reject lowerdir ending with unescaped backslash
  fsnotify: prevent mark additions during connector teardown
  pid_namespace: make zap_pid_ns_processes() freezable
  fs: drop the smp_mb()s that mnt_get_count() made redundant
  autofs: ignore notification errors from a replaced pipe
  ntfs3: free replaced mount options after a grace period
  ntfs3: free the sbi after a grace period
  unicode: free the map after a grace period
  ipc: drop the mqueue mount with kern_unmount() on setup failure
  eventfs: free the eventfs_inode after an rcu grace period too
  9p: free the session after a grace period
  watch_queue: Fix note pool publication race in watch_queue_set_size()
  afs: free the super info after a grace period
  pipe: preserve existing fasync readers on writer setup failure
  ...
Commit
1dbb7422053fa0f4fb5f9acc3931617fc0091939
Parents
519abe8d · 214897ac

Merge commit. Changes below are compared with the first parent: 519abe8d.

Changes

34 files changed · +325 −92

FileStatusAddDelContent
fs/9p/v9fs.hmodified+1−0View file
fs/9p/vfs_super.cmodified+2−2View file
fs/afs/internal.hmodified+2−0View file
fs/afs/super.cmodified+1−1View file
fs/autofs/waitq.cmodified+19−6View file
fs/ext4/ext4.hmodified+2−0View file
fs/ext4/extents-test.cmodified+2−0View file
fs/ext4/extents.cmodified+1−0View file
fs/ext4/extents_status.cmodified+6−1View file
fs/ext4/extents_status.hmodified+1−0View file
fs/ext4/super.cmodified+11−2View file
fs/namei.cmodified+1−31View file
fs/namespace.cmodified+23−10View file
fs/notify/mark.cmodified+11−0View file
fs/ntfs3/dir.cmodified+11−2View file
fs/ntfs3/ntfs_fs.hmodified+3−0View file
fs/ntfs3/super.cmodified+36−13View file
fs/ocfs2/dlm/dlmcommon.hmodified+2−0View file
fs/ocfs2/dlm/dlmdebug.cmodified+4−1View file
fs/ocfs2/dlm/dlmrecovery.cmodified+1−1View file
fs/overlayfs/params.cmodified+3−0View file
fs/pipe.cmodified+4−2View file
fs/tracefs/event_inode.cmodified+9−1View file
fs/unicode/utf8-core.cmodified+17−4View file
include/linux/fsnotify_backend.hmodified+1−0View file
include/linux/sched/signal.hmodified+2−2View file
include/linux/unicode.hmodified+1−0View file
ipc/namespace.cmodified+1−1View file
kernel/exit.cmodified+1−1View file
kernel/fork.cmodified+15−5View file
kernel/pid_namespace.cmodified+1−1View file
kernel/watch_queue.cmodified+7−4View file
samples/pidfd/Makefilemodified+1−1View file
tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.cmodified+122−0View file

Only the first 600 diff lines are shown. Download the complete patch.

fs/9p/v9fs.h

View file
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
index a462bcb..2ac863d 100644
--- a/fs/9p/v9fs.h
+++ b/fs/9p/v9fs.h
@@ -137,6 +137,7 @@ struct v9fs_session_info {
struct list_head slist; /* list of sessions registered with v9fs */
struct rw_semaphore rename_sem;
long session_lock_timeout; /* retry interval for blocking locks */
+ struct rcu_head rcu; /* v9fs_vfs_get_link_dotl() reads ->cache in rcu pathwalk */
};
#define NDENTRY_TIMEOUT_NEVER (-1U)

fs/9p/vfs_super.c

View file
diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c
index 94d6b02..7952604 100644
--- a/fs/9p/vfs_super.c
+++ b/fs/9p/vfs_super.c
@@ -173,8 +173,8 @@ static void v9fs_kill_super(struct super_block *s)
v9fs_session_cancel(v9ses);
v9fs_session_close(v9ses);
- kfree(v9ses);
- s->s_fs_info = NULL;
+ /* v9fs_vfs_get_link_dotl() may still read ->cache in rcu pathwalk */
+ kfree_rcu(v9ses, rcu);
p9_debug(P9_DEBUG_VFS, "exiting kill_super\n");
}

fs/afs/internal.h

View file
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 330654e..3282d5d 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -251,6 +251,8 @@ struct afs_super_info {
struct afs_volume *volume; /* volume record */
enum afs_flock_mode flock_mode:8; /* File locking emulation mode */
bool dyn_root; /* True if dynamic root */
+ /* afs_atcell_get_link() reads ->net_ns in rcu pathwalk */
+ struct rcu_head rcu;
};
static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)

fs/afs/super.c

View file
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 82bb713..33b1958 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -523,7 +523,7 @@ static void afs_destroy_sbi(struct afs_super_info *as)
afs_put_volume(as->volume, afs_volume_trace_put_destroy_sbi);
afs_unuse_cell(as->cell, afs_cell_trace_unuse_sbi);
put_net(as->net_ns);
- kfree(as);
+ kfree_rcu(as, rcu);
}
}

fs/autofs/waitq.c

View file
diff --git a/fs/autofs/waitq.c b/fs/autofs/waitq.c
index d462413..92c0bbb 100644
--- a/fs/autofs/waitq.c
+++ b/fs/autofs/waitq.c
@@ -12,15 +12,13 @@
*/
static autofs_wqt_t autofs_next_wait_queue = 1;
-void autofs_catatonic_mode(struct autofs_sb_info *sbi)
+static void autofs_catatonic_mode_locked(struct autofs_sb_info *sbi)
{
struct autofs_wait_queue *wq, *nwq;
- mutex_lock(&sbi->wq_mutex);
- if (sbi->flags & AUTOFS_SBI_CATATONIC) {
- mutex_unlock(&sbi->wq_mutex);
+ lockdep_assert_held(&sbi->wq_mutex);
+ if (sbi->flags & AUTOFS_SBI_CATATONIC)
return;
- }
pr_debug("entering catatonic mode\n");
@@ -40,6 +38,21 @@ void autofs_catatonic_mode(struct autofs_sb_info *sbi)
fput(sbi->pipe); /* Close the pipe */
sbi->pipe = NULL;
sbi->pipefd = -1;
+}
+
+void autofs_catatonic_mode(struct autofs_sb_info *sbi)
+{
+ mutex_lock(&sbi->wq_mutex);
+ autofs_catatonic_mode_locked(sbi);
+ mutex_unlock(&sbi->wq_mutex);
+}
+
+static void autofs_catatonic_mode_for_pipe(struct autofs_sb_info *sbi,
+ struct file *pipe)
+{
+ mutex_lock(&sbi->wq_mutex);
+ if (sbi->pipe == pipe)
+ autofs_catatonic_mode_locked(sbi);
mutex_unlock(&sbi->wq_mutex);
}
@@ -170,7 +183,7 @@ static void autofs_notify_daemon(struct autofs_sb_info *sbi,
autofs_wait_release(sbi, wq->wait_queue_token, ret);
break;
default:
- autofs_catatonic_mode(sbi);
+ autofs_catatonic_mode_for_pipe(sbi, pipe);
break;
}
fput(pipe);

fs/ext4/ext4.h

View file
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 724a27e..af70bb2 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1774,6 +1774,8 @@ struct ext4_sb_info {
struct list_head s_es_list; /* List of inodes with reclaimable extents */
long s_es_nr_inode;
struct ext4_es_stats s_es_stats;
+ /* ext4_get_link() reads the sbi in rcu pathwalk */
+ struct rcu_head s_rcu;
struct mb_cache *s_ea_block_cache;
struct mb_cache *s_ea_inode_cache;

fs/ext4/extents-test.c

View file
diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c
index c3836ec..2e997f2 100644
--- a/fs/ext4/extents-test.c
+++ b/fs/ext4/extents-test.c
@@ -151,6 +151,7 @@ static void extents_kunit_exit(struct kunit *test)
sbi = k_ctx.k_ei->vfs_inode.i_sb->s_fs_info;
ext4_es_unregister_shrinker(sbi);
+ ext4_es_destroy_stats(sbi);
deactivate_super(sbi->s_sb);
kfree(sbi);
kfree(k_ctx.k_ei);
@@ -340,6 +341,7 @@ out:
k_ctx.k_data = NULL;
ext4_es_unregister_shrinker(sbi);
+ ext4_es_destroy_stats(sbi);
out_deactivate:
deactivate_locked_super(sb);
kfree(sbi);

fs/ext4/extents.c

View file
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 76038b6..3bf0a6c 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -6409,6 +6409,7 @@ EXPORT_SYMBOL_FOR_EXT4_TEST(__ext4_ext_dirty);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_zeroout);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_es_register_shrinker);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_es_unregister_shrinker);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_es_destroy_stats);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_map_create_blocks);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_es_init_tree);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_es_lookup_extent);

fs/ext4/extents_status.c

View file
diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c
index 6e4a191..018b614 100644
--- a/fs/ext4/extents_status.c
+++ b/fs/ext4/extents_status.c
@@ -1882,11 +1882,16 @@ err1:
void ext4_es_unregister_shrinker(struct ext4_sb_info *sbi)
{
+ shrinker_free(sbi->s_es_shrinker);
+}
+
+/* ext4_get_link() bumps the hit and miss counters in rcu pathwalk */
+void ext4_es_destroy_stats(struct ext4_sb_info *sbi)
+{
percpu_counter_destroy(&sbi->s_es_stats.es_stats_cache_hits);
percpu_counter_destroy(&sbi->s_es_stats.es_stats_cache_misses);
percpu_counter_destroy(&sbi->s_es_stats.es_stats_all_cnt);
percpu_counter_destroy(&sbi->s_es_stats.es_stats_shk_cnt);
- shrinker_free(sbi->s_es_shrinker);
}
/*

fs/ext4/extents_status.h

View file
diff --git a/fs/ext4/extents_status.h b/fs/ext4/extents_status.h
index f3396cf..01cddca 100644
--- a/fs/ext4/extents_status.h
+++ b/fs/ext4/extents_status.h
@@ -238,6 +238,7 @@ static inline void ext4_es_store_pblock_status(struct extent_status *es,
extern int ext4_es_register_shrinker(struct ext4_sb_info *sbi);
extern void ext4_es_unregister_shrinker(struct ext4_sb_info *sbi);
+extern void ext4_es_destroy_stats(struct ext4_sb_info *sbi);
extern int ext4_seq_es_shrinker_info_show(struct seq_file *seq, void *v);

fs/ext4/super.c

View file
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index d636b89..3ace06b 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1279,6 +1279,14 @@ static void ext4_flex_groups_free(struct ext4_sb_info *sbi)
}
}
+static void ext4_put_sbi_rcu(struct rcu_head *head)
+{
+ struct ext4_sb_info *sbi = container_of(head, struct ext4_sb_info, s_rcu);
+
+ ext4_es_destroy_stats(sbi);
+ kfree(sbi);
+}
+
static void ext4_put_super(struct super_block *sb)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
@@ -1374,7 +1382,6 @@ static void ext4_put_super(struct super_block *sb)
ext4_stop_mmpd(sbi);
brelse(sbi->s_sbh);
- sb->s_fs_info = NULL;
/*
* Now that we are completely done shutting down the
* superblock, we need to actually destroy the kobject.
@@ -1387,7 +1394,8 @@ static void ext4_put_super(struct super_block *sb)
#if IS_ENABLED(CONFIG_UNICODE)
utf8_unload(sb->s_encoding);
#endif
- kfree(sbi);
+ /* ext4_get_link() reads the sbi and the counters in rcu pathwalk */
+ call_rcu(&sbi->s_rcu, ext4_put_sbi_rcu);
}
static struct kmem_cache *ext4_inode_cachep;
@@ -5806,6 +5814,7 @@ failed_mount3a:
/* Drain deferred EA inode iputs from journal replay */
flush_delayed_work(&sbi->s_ea_inode_work);
ext4_es_unregister_shrinker(sbi);
+ ext4_es_destroy_stats(sbi);
failed_mount3:
/* flush s_sb_upd_work before sbi destroy */
flush_work(&sbi->s_sb_upd_work);

fs/namei.c

View file
diff --git a/fs/namei.c b/fs/namei.c
index c87c14e..e45e306 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2087,33 +2087,6 @@ all_done: // pure jump
}
/*
- * Be careful in case the dentry is unlinked or renamed. Any mounts
- * stacked on top of it are going away. We need to make sure that we
- * don't reveal the underyling dentry during refwalk. In rcuwalk we
- * catch this via d_seq and another lookup for the name. Give the same
- * guarantee in refwalk.
- */
-static bool unlink_may_reveal(struct nameidata *nd, int flags,
- struct path *path, struct dentry *dentry)
-{
- /* ".." and LOOKUP_DOWN may land on an unhashed directory */
- if (flags & WALK_NOFOLLOW)
- return false;
- if (nd->flags & LOOKUP_REVAL)
- return false;
- /* We crossed onto a mount and the name led us here while it still existed */
- if (path->mnt != nd->path.mnt)
- return false;
- /* only a name on its way out is flagged */
- if (likely(!cant_mount(dentry)))
- return false;
- if (!d_unlinked(dentry))
- return false;
- dput(no_free_ptr(path->dentry));
- return true;
-}
-
-/*
* Do we need to follow links? We _really_ want to be able
* to do this check without having to look at inode->i_op,
* so we keep a cache of "no, this doesn't need follow_link"
@@ -2142,8 +2115,6 @@ static noinline const char *step_into_slowpath(struct nameidata *nd, int flags,
if (unlikely(!inode))
return ERR_PTR(-ENOENT);
} else {
- if (unlikely(unlink_may_reveal(nd, flags, &path, dentry)))
- return ERR_PTR(-ESTALE);
dput(nd->path.dentry);
if (nd->path.mnt != path.mnt)
mntput(nd->path.mnt);
@@ -4750,8 +4721,7 @@ static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)
return dentry;
if (open_flag & O_CREAT) {
- /* Discard negative dentries. Need inode_lock to do the create */
- if (!dentry->d_inode) {
+ if (d_is_negative(dentry)) {
if (!(nd->flags & LOOKUP_RCU))
dput(dentry);
dentry = NULL;

fs/namespace.c

View file
diff --git a/fs/namespace.c b/fs/namespace.c
index 3ed3766..c30f063 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -297,7 +297,11 @@ int mnt_get_count(struct mount *mnt)
/* puts first, so a put counted here has its get counted below */
for_each_possible_cpu(cpu)
puts += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_puts;
- smp_mb(); /* pairs with the smp_wmb() in mntput_no_expire() */
+ /*
+ * Pairs with the smp_wmb() in mntput_no_expire() and with the
+ * smp_mb() in __legitimize_mnt().
+ */
+ smp_mb();
for_each_possible_cpu(cpu)
gets += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_gets;
@@ -782,7 +786,7 @@ int __legitimize_mnt(struct vfsmount *bastard, unsigned seq)
return 0;
mnt = real_mount(bastard);
mnt_inc_count(mnt);
- smp_mb(); /* see mntput_no_expire_slowpath() and do_umount() */
+ smp_mb(); /* pairs with the smp_mb() in mnt_get_count() */
if (likely(!read_seqretry(&mount_lock, seq)))
return 0;
lock_mount_hash();
@@ -1419,11 +1423,6 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt)
VFS_BUG_ON(mnt->mnt_ns);
lock_mount_hash();
- /*
- * make sure that if __legitimize_mnt() has not seen us grab
- * mount_lock, we'll see their refcount increment here.
- */
- smp_mb();
mnt_dec_count(mnt);
count = mnt_get_count(mnt);
if (count != 0) {
@@ -2006,7 +2005,6 @@ static int do_umount(struct mount *mnt, int flags)
umount_tree(mnt, UMOUNT_PROPAGATE);
retval = 0;
} else {
- smp_mb(); // paired with __legitimize_mnt()
shrink_submounts(mnt);
retval = -EBUSY;
if (!propagate_mount_busy(mnt, 2)) {
@@ -2254,6 +2252,11 @@ struct mount *copy_tree(struct mount *src_root, struct dentry *dentry,
}
if (!(flag & CL_COPY_MNT_NS_FILE) &&
is_mnt_ns_file(src_mnt->mnt.mnt_root)) {
+ if (src_mnt->mnt.mnt_flags & MNT_LOCKED) {
+ /* Both a mount namespace file and locked. */
+ dst_mnt = ERR_PTR(-EPERM);
+ goto out;
+ }
src_mnt = skip_mnt_tree(src_mnt);
continue;
}
@@ -3649,8 +3652,9 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
* @mnt_to: mount under which to mount
* @mp: mountpoint of @mnt_to
*
- * - Make sure that the caller can unmount the topmost mount ensuring
- * that the caller could reveal the underlying mountpoint.
+ * - Make sure that @mnt_from can take over the lock of @mnt_to. A
+ * mount of a mount namespace file is left out of a copy of the tree
+ * and can't.
* - Ensure that nothing has been mounted on top of @mnt_from before we
* grabbed @namespace_sem to avoid creating pointless shadow mounts.
* - Prevent mounting beneath a mount if the propagation relationship
@@ -3666,6 +3670,15 @@ static int can_move_mount_beneath(const struct mount *mnt_from,
{
struct mount *parent_mnt_to = mnt_to->mnt_parent;
+ /*
+ * @mnt_from takes over the lock of @mnt_to. copy_tree() leaves a
+ * mount of a mount namespace file out of every copy made without
+ * CL_COPY_MNT_NS_FILE, together with what is mounted on it, so it
+ * can't keep anything hidden.
+ */
+ if (IS_MNT_LOCKED(mnt_to) && is_mnt_ns_file(mnt_from->mnt.mnt_root))
+ return -EINVAL;
+
/* Avoid creating shadow mounts during mount propagation. */
if (mnt_from->overmount)
return -EINVAL;

fs/notify/mark.c

View file
diff --git a/fs/notify/mark.c b/fs/notify/mark.c
index d176285..cb07f42 100644
--- a/fs/notify/mark.c
+++ b/fs/notify/mark.c
@@ -929,6 +929,11 @@ restart:
goto restart;
}
+ if (conn->flags & FSNOTIFY_CONN_FLAG_DESTROYING) {
+ err = -ENOENT;
+ goto out_err;
+ }
+
/* is mark the first mark? */
if (hlist_empty(&conn->list)) {
hlist_add_head_rcu(&mark->obj_list, &conn->list);
@@ -1117,6 +1122,12 @@ void fsnotify_destroy_marks(fsnotify_connp_t *connp)
if (!conn)
return;
/*
+ * New marks could be inserted before our cursor and survive the final
+ * detachment of the connector from the object. Prevent additions to
+ * this connector before dropping its lock.
+ */
+ conn->flags |= FSNOTIFY_CONN_FLAG_DESTROYING;
+ /*
* We have to be careful since we can race with e.g.
* fsnotify_clear_marks_by_group() and once we drop the conn->lock, the
* list can get modified. However we are holding mark reference and

fs/ntfs3/dir.c

View file
diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index eb9152e..14ca54e 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -186,11 +186,20 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
{
int ret, slen, i;
const u8 *end;
- struct nls_table *nls = sbi->options->nls;
+ struct ntfs_mount_options *opts;
+ struct nls_table *nls;
+ bool ads;
u16 *uname = uni->name;
static_assert(sizeof(wchar_t) == sizeof(u16));
+ /* ntfs_fs_reconfigure() may replace the options, but not ->nls */
+ rcu_read_lock();
+ opts = READ_ONCE(sbi->options);
+ nls = opts->nls;
+ ads = opts->ads;
+ rcu_read_unlock();
+
if (!nls) {
/* utf8 -> utf16 */
ret = _utf8s_to_utf16s(name, name_len, endian, uname, max_ulen);
@@ -230,7 +239,7 @@ int ntfs_nls_to_utf16(struct ntfs_sb_info *sbi, const u8 *name, u32 name_len,
uni->len = ret;
uni->ads_len = 0;
- if (ret > 0 && sbi->options->ads) {
+ if (ret > 0 && ads) {
uname = uni->name;
/* Find delimiter in range [1 : ret-2). */
for (i = 1; i + 1 < ret; i++) {

fs/ntfs3/ntfs_fs.h

View file
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index 5811d89..44d59b4 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -91,6 +91,8 @@ enum utf16_endian;
struct ntfs_mount_options {
char *nls_name;
struct nls_table *nls;
+ /* ->d_hash() and ->d_compare() read the options in rcu pathwalk */
+ struct rcu_head rcu;
kuid_t fs_uid;
kgid_t fs_gid;
@@ -211,6 +213,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)

fs/ntfs3/super.c

View file
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index f4a42a0..58ef6be 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;
}
@@ -251,6 +251,11 @@ static inline void put_mount_options(struct ntfs_mount_options *options)
kfree(options);
}
+static void put_mount_options_rcu(struct rcu_head *head)
+{
+ put_mount_options(container_of(head, struct ntfs_mount_options, rcu));
+}
+
enum Opt {
Opt_uid,
Opt_gid,
@@ -444,6 +449,7 @@ static int ntfs_fs_reconfigure(struct fs_context *fc)
struct super_block *sb = fc->root->d_sb;
struct ntfs_sb_info *sbi = sb->s_fs_info;
struct ntfs_mount_options *new_opts = fc->fs_private;
+ struct ntfs_mount_options *old_opts;
int ro_rw;
ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY);
@@ -473,7 +479,15 @@ static int ntfs_fs_reconfigure(struct fs_context *fc)
}
sync_filesystem(sb);
- swap(sbi->options, fc->fs_private);
+ old_opts = sbi->options;
+ /* pairs with the READ_ONCE() in ntfs_nls_to_utf16() */
+ smp_store_release(&sbi->options, new_opts);
+ fc->fs_private = NULL;
+ /*
+ * ntfs_d_hash() and ntfs_d_compare() of a pathwalk in rcu mode may
+ * still read the old options through the sbi.
+ */
+ call_rcu(&old_opts->rcu, put_mount_options_rcu);
return 0;
}
@@ -708,6 +722,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 +735,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 +749,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 +1960,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

fs/ocfs2/dlm/dlmcommon.h

View file
diff --git a/fs/ocfs2/dlm/dlmcommon.h b/fs/ocfs2/dlm/dlmcommon.h
index 20f790a..58b28df 100644
--- a/fs/ocfs2/dlm/dlmcommon.h
+++ b/fs/ocfs2/dlm/dlmcommon.h
@@ -435,6 +435,8 @@ enum {
DLM_DEREF_LOCKRES_DONE = 522,
};
+extern spinlock_t dlm_reco_state_lock;
+
struct dlm_reco_node_data

This diff is incomplete. Download the complete patch.

fs/ocfs2/dlm/dlmdebug.c

View file

This diff is incomplete. Download the complete patch.

fs/ocfs2/dlm/dlmrecovery.c

View file

This diff is incomplete. Download the complete patch.

fs/overlayfs/params.c

View file

This diff is incomplete. Download the complete patch.

fs/pipe.c

View file

This diff is incomplete. Download the complete patch.

fs/tracefs/event_inode.c

View file

This diff is incomplete. Download the complete patch.

fs/unicode/utf8-core.c

View file

This diff is incomplete. Download the complete patch.

include/linux/fsnotify_backend.h

View file

This diff is incomplete. Download the complete patch.

include/linux/sched/signal.h

View file

This diff is incomplete. Download the complete patch.

include/linux/unicode.h

View file

This diff is incomplete. Download the complete patch.

ipc/namespace.c

View file

This diff is incomplete. Download the complete patch.

kernel/exit.c

View file

This diff is incomplete. Download the complete patch.

kernel/fork.c

View file

This diff is incomplete. Download the complete patch.

kernel/pid_namespace.c

View file

This diff is incomplete. Download the complete patch.

kernel/watch_queue.c

View file

This diff is incomplete. Download the complete patch.

samples/pidfd/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c

View file

This diff is incomplete. Download the complete patch.