diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index a462bcbfc7da..2ac863dc8446 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) diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c index 94d6b02c221b..79526044685c 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"); } diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 330654ed16ec..3282d5d6e1c0 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) diff --git a/fs/afs/super.c b/fs/afs/super.c index 82bb713825a0..33b195821e36 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); } } diff --git a/fs/autofs/waitq.c b/fs/autofs/waitq.c index d46241342dfc..92c0bbbf452b 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); diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 724a27e8be61..af70bb26bb54 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; diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index c3836ecb89f9..2e997f228330 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); diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 76038b6c3655..3bf0a6cdbec2 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); diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index 6e4a191e8219..018b6146b9d8 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c @@ -1881,12 +1881,17 @@ 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); } /* diff --git a/fs/ext4/extents_status.h b/fs/ext4/extents_status.h index f3396cf32b44..01cddcaaa68e 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); diff --git a/fs/ext4/super.c b/fs/ext4/super.c index d636b89090a2..3ace06b4f9e3 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); diff --git a/fs/namei.c b/fs/namei.c index c87c14ee1a25..e45e30603215 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2086,33 +2086,6 @@ all_done: // pure jump return NULL; } -/* - * 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, @@ -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; diff --git a/fs/namespace.c b/fs/namespace.c index 3ed3766f3a18..c30f063742f4 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; diff --git a/fs/notify/mark.c b/fs/notify/mark.c index d17628580a57..cb07f42d3422 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); @@ -1116,6 +1121,12 @@ void fsnotify_destroy_marks(fsnotify_connp_t *connp) conn = fsnotify_grab_connector(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 diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c index eb9152e9fa22..14ca54e54578 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++) { diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 5811d89d67b3..44d59b444648 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) diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c index f4a42a0c73a4..58ef6be3286b 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 diff --git a/fs/ocfs2/dlm/dlmcommon.h b/fs/ocfs2/dlm/dlmcommon.h index 20f790a47484..58b28dfa8d34 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 { int state; diff --git a/fs/ocfs2/dlm/dlmdebug.c b/fs/ocfs2/dlm/dlmdebug.c index ce6d301cb942..a91f167ed683 100644 --- a/fs/ocfs2/dlm/dlmdebug.c +++ b/fs/ocfs2/dlm/dlmdebug.c @@ -736,8 +736,11 @@ static int debug_state_print(struct dlm_ctxt *dlm, char *buf, int len) buf + out, len - out); out += scnprintf(buf + out, len - out, "\n"); + spin_unlock(&dlm->spinlock); + /* Recovery Node State: */ out += scnprintf(buf + out, len - out, "Recovery Node State:\n"); + spin_lock(&dlm_reco_state_lock); list_for_each_entry(node, &dlm->reco.node_data, list) { switch (node->state) { case DLM_RECO_NODE_DATA_INIT: @@ -769,7 +772,7 @@ static int debug_state_print(struct dlm_ctxt *dlm, char *buf, int len) node->node_num, state); } - spin_unlock(&dlm->spinlock); + spin_unlock(&dlm_reco_state_lock); return out; } diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c index 9d4a2695b959..7d519e31fcf1 100644 --- a/fs/ocfs2/dlm/dlmrecovery.c +++ b/fs/ocfs2/dlm/dlmrecovery.c @@ -80,7 +80,7 @@ static int dlm_lockres_master_requery(struct dlm_ctxt *dlm, static u64 dlm_get_next_mig_cookie(void); -static DEFINE_SPINLOCK(dlm_reco_state_lock); +DEFINE_SPINLOCK(dlm_reco_state_lock); static DEFINE_SPINLOCK(dlm_mig_cookie_lock); static u64 dlm_mig_cookie = 1; diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c index c93fcaa45d4a..5a15e5478a6b 100644 --- a/fs/overlayfs/params.c +++ b/fs/overlayfs/params.c @@ -216,6 +216,9 @@ static ssize_t ovl_parse_param_split_lowerdirs(char *str) if (*s == '\\') { /* keep esc chars in split lowerdir */ *d++ = *s++; + /* A trailing backslash escapes nothing. */ + if (!*s) + return -EINVAL; } else if (*s == ':') { bool next_colon = (*(s + 1) == ':'); diff --git a/fs/pipe.c b/fs/pipe.c index 292425a834dd..61806a013fde 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -838,9 +838,11 @@ pipe_fasync(int fd, struct file *filp, int on) if (filp->f_mode & FMODE_READ) retval = fasync_helper(fd, filp, on, &pipe->fasync_readers); if ((filp->f_mode & FMODE_WRITE) && retval >= 0) { + int read_ret = retval; + retval = fasync_helper(fd, filp, on, &pipe->fasync_writers); - if (retval < 0 && (filp->f_mode & FMODE_READ)) - /* this can happen only if on == T */ + /* Undo only a reader entry added by this call. */ + if (retval < 0 && read_ret > 0) fasync_helper(-1, filp, 0, &pipe->fasync_readers); } mutex_unlock(&pipe->mutex); diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 6e3513b13cfa..090aca946717 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -91,6 +91,14 @@ static void free_ei_rcu(struct rcu_head *rcu) } } +/* read under eventfs_srcu and, by tracefs_d_revalidate(), in rcu pathwalk */ +static void free_ei_srcu(struct rcu_head *rcu) +{ + struct eventfs_inode *ei = container_of(rcu, struct eventfs_inode, rcu); + + call_rcu(&ei->rcu, free_ei_rcu); +} + /* * eventfs_inode reference count management. * @@ -112,7 +120,7 @@ static void release_ei(struct kref *ref) entry->release(entry->name, ei->data); } - call_srcu(&eventfs_srcu, &ei->rcu, free_ei_rcu); + call_srcu(&eventfs_srcu, &ei->rcu, free_ei_srcu); } static inline void put_ei(struct eventfs_inode *ei) diff --git a/fs/unicode/utf8-core.c b/fs/unicode/utf8-core.c index f313532f5e80..74a74a48dd8b 100644 --- a/fs/unicode/utf8-core.c +++ b/fs/unicode/utf8-core.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -183,12 +184,24 @@ out_free_um: } EXPORT_SYMBOL(utf8_load); +static void utf8_unload_rcu(struct rcu_head *head) +{ + struct unicode_map *um = container_of(head, struct unicode_map, rcu); + + symbol_put(utf8_data_table); + kfree(um); +} + +/* + * A pathwalk in rcu mode may still be in ->d_hash() or ->d_compare() of a + * filesystem that is being torn down and read the map through its + * superblock, so the map and the tables it points to have to outlive the + * grace period. + */ void utf8_unload(struct unicode_map *um) { - if (um) { - symbol_put(utf8_data_table); - kfree(um); - } + if (um) + call_rcu(&um->rcu, utf8_unload_rcu); } EXPORT_SYMBOL(utf8_unload); diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h index 618eed4d6d72..a6d5c1a89cff 100644 --- a/include/linux/fsnotify_backend.h +++ b/include/linux/fsnotify_backend.h @@ -566,6 +566,7 @@ struct fsnotify_mark_connector { unsigned char prio; /* Highest priority group */ #define FSNOTIFY_CONN_FLAG_IS_WATCHED 0x01 #define FSNOTIFY_CONN_FLAG_HAS_IREF 0x02 +#define FSNOTIFY_CONN_FLAG_DESTROYING 0x04 unsigned short flags; /* flags [lock] */ union { /* Object pointer [lock] */ diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index d45a5476b97d..efa1e2d6c7a6 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -130,8 +130,8 @@ struct signal_struct { * process will inherit a flag if they should look for a * child_subreaper process at exit. */ - unsigned int is_child_subreaper:1; - unsigned int has_child_subreaper:1; + bool is_child_subreaper; + bool has_child_subreaper; unsigned int autoreap:1; #ifdef CONFIG_POSIX_TIMERS diff --git a/include/linux/unicode.h b/include/linux/unicode.h index 64fa44fe180c..3a8cb603eb93 100644 --- a/include/linux/unicode.h +++ b/include/linux/unicode.h @@ -53,6 +53,7 @@ struct unicode_map { unsigned int version; const struct utf8data *ntab[UTF8_NMAX]; const struct utf8data_table *tables; + struct rcu_head rcu; }; int utf8_validate(const struct unicode_map *um, const struct qstr *str); diff --git a/ipc/namespace.c b/ipc/namespace.c index 1e71353bdb4a..75aa7b469348 100644 --- a/ipc/namespace.c +++ b/ipc/namespace.c @@ -96,7 +96,7 @@ fail_ipc: fail_mq_sysctls: retire_mq_sysctls(ns); fail_mq_mount: - mntput(ns->mq_mnt); + kern_unmount(ns->mq_mnt); fail_put: put_user_ns(ns->user_ns); ns_common_free(ns); diff --git a/kernel/exit.c b/kernel/exit.c index 282328d2b4cf..ba1469bea8fb 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1729,7 +1729,7 @@ static long do_wait(struct wait_opts *wo) add_wait_queue(¤t->signal->wait_chldexit, &wo->child_wait); do { - set_current_state(TASK_INTERRUPTIBLE); + set_current_state(TASK_INTERRUPTIBLE | TASK_FREEZABLE); retval = __do_wait(wo); if (retval != -ERESTARTSYS) break; diff --git a/kernel/fork.c b/kernel/fork.c index eb198c8dfdc7..33760ece3bb3 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1619,7 +1619,13 @@ static int copy_exec_state(u64 clone_flags, struct task_struct *tsk) return task_exec_state_copy(tsk); } -static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh) +enum copy_fs_flags_t { + COPY_FS_UMH = (1 << 0), + COPY_FS_IDLE = (1 << 1), +}; + +static int copy_fs(u64 clone_flags, struct task_struct *tsk, + enum copy_fs_flags_t flags) { struct fs_struct *fs; @@ -1629,19 +1635,20 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh) * filesystem state, or be started from a non-initial mount * namespace. */ - if (umh) { + if (flags & COPY_FS_UMH) { if (clone_flags & (CLONE_NEWNS | CLONE_FS)) return -EINVAL; if (current->nsproxy->mnt_ns != &init_mnt_ns) return -EINVAL; fs = userspace_init_fs; + } else if (flags & COPY_FS_IDLE) { + fs = &init_fs; } else { fs = current->fs; VFS_WARN_ON_ONCE(current->fs != current->real_fs); } if (clone_flags & CLONE_FS) { - /* tsk->fs is already what we want */ read_seqlock_excl(&fs->seq); /* "users" and "in_exec" locked for check_unsafe_exec() */ if (fs->in_exec) { @@ -1650,6 +1657,7 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh) } fs->users++; read_sequnlock_excl(&fs->seq); + tsk->real_fs = tsk->fs = fs; return 0; } tsk->real_fs = tsk->fs = copy_fs_struct(fs); @@ -2299,7 +2307,9 @@ __latent_entropy struct task_struct *copy_process( retval = copy_files(clone_flags, p, args->no_files); if (retval) goto bad_fork_cleanup_semundo; - retval = copy_fs(clone_flags, p, args->umh); + retval = copy_fs(clone_flags, p, + (args->umh ? COPY_FS_UMH : 0) | + (args->idle ? COPY_FS_IDLE : 0)); if (retval) goto bad_fork_cleanup_files; retval = copy_sighand(clone_flags, p); @@ -2671,7 +2681,7 @@ struct task_struct * __init fork_idle(int cpu) { struct task_struct *task; struct kernel_clone_args args = { - .flags = CLONE_VM, + .flags = CLONE_VM | CLONE_FS, .fn = &idle_dummy, .fn_arg = NULL, .kthread = 1, diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index d36afc58ee1d..4ade0f37ed54 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -268,7 +268,7 @@ void zap_pid_ns_processes(struct pid_namespace *pid_ns) * free_pid() will awaken this task. */ for (;;) { - set_current_state(TASK_INTERRUPTIBLE); + set_current_state(TASK_IDLE | TASK_FREEZABLE); if (pid_ns->pid_allocated == init_pids) break; schedule(); diff --git a/kernel/watch_queue.c b/kernel/watch_queue.c index 538520861e8b..932a88ac1f39 100644 --- a/kernel/watch_queue.c +++ b/kernel/watch_queue.c @@ -101,7 +101,7 @@ static bool post_one_notification(struct watch_queue *wqueue, struct pipe_inode_info *pipe = wqueue->pipe; struct pipe_buffer *buf; struct page *page; - unsigned int head, tail, note, offset, len; + unsigned int head, tail, note, offset, len, nr_notes; bool done = false; spin_lock_irq(&pipe->rd_wait.lock); @@ -111,8 +111,10 @@ static bool post_one_notification(struct watch_queue *wqueue, if (pipe_full(head, tail, pipe->ring_size)) goto lost; - note = find_first_bit(wqueue->notes_bitmap, wqueue->nr_notes); - if (note >= wqueue->nr_notes) + /* Pairs with the smp_store_release() in watch_queue_set_size(). */ + nr_notes = smp_load_acquire(&wqueue->nr_notes); + note = find_first_bit(wqueue->notes_bitmap, nr_notes); + if (note >= nr_notes) goto lost; page = wqueue->notes[note / WATCH_QUEUE_NOTES_PER_PAGE]; @@ -297,7 +299,8 @@ long watch_queue_set_size(struct pipe_inode_info *pipe, unsigned int nr_notes) wqueue->notes = pages; wqueue->notes_bitmap = bitmap; wqueue->nr_pages = nr_pages; - wqueue->nr_notes = nr_notes; + /* Pairs with the smp_load_acquire() in post_one_notification(). */ + smp_store_release(&wqueue->nr_notes, nr_notes); return 0; error_p: diff --git a/samples/pidfd/Makefile b/samples/pidfd/Makefile index 9754e2d81f70..d82409f7061f 100644 --- a/samples/pidfd/Makefile +++ b/samples/pidfd/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -usertprogs-always-y += pidfd-metadata +userprogs-always-y += pidfd-metadata userccflags += -I usr/include diff --git a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c index 0799b2ba545d..e9b2b874dd16 100644 --- a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c +++ b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -27,6 +29,12 @@ #ifndef MOVE_MOUNT_BENEATH #define MOVE_MOUNT_BENEATH 0x00000200 #endif +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif #define DIR_LEN 64 #define PATH_LEN 192 @@ -120,6 +128,35 @@ static bool covered(const char *path) return fd < 0; } +/* What @path reads, "" if it can't be read. */ +static int read_word(const char *path, char *buf, size_t len) +{ + ssize_t n = 0; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + n = read(fd, buf, len - 1); + close(fd); + } + buf[n > 0 ? n : 0] = 0; + return fd < 0 ? -1 : 0; +} + +/* Put a clone of the mount at @src beneath the top mount at @where. */ +static int move_beneath(const char *src, const char *where) +{ + int fd, ret; + + fd = syscall(__NR_open_tree, AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd < 0) + return -1; + ret = syscall(__NR_move_mount, fd, "", AT_FDCWD, where, + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH); + close(fd); + return ret; +} + FIXTURE(locked_mount) { char base[DIR_LEN]; bool tracing; /* debugfs with the tracefs automount is there */ @@ -429,4 +466,89 @@ TEST_F(locked_mount, propagated_copy_keeps_the_cover_locked) EXPECT_EQ(ret, CHILD_OK); } +/* + * A mount of a mount namespace file is left out of the copy that + * unshare(CLONE_NEWNS) makes, together with everything mounted on it, so + * it can't take over the lock of a cover it is moved beneath. The child + * tries just that with the namespace file of a grandchild and gets EINVAL. + * A bind of an ordinary file takes the lock over, and the file is still + * covered in the next copy of the mount namespace. + */ +static int ns_file_child(const char *base) +{ + char secret[PATH_LEN], mine[PATH_LEN], nsfile[PATH_LEN], buf[32]; + int ret = CHILD_OK, pfd[2]; + pid_t pid; + + snprintf(secret, sizeof(secret), "%s/P/secret", base); + snprintf(mine, sizeof(mine), "%s/mine", base); + + if (enter_userns() || pipe(pfd)) + return CHILD_SETUP; + /* the cover is locked */ + if (!umount2(secret, 0) || errno != EINVAL) + return CHILD_STEP1; + + /* a mount namespace newer than our own, so that a bind of it is allowed */ + pid = fork(); + if (pid < 0) + return CHILD_SETUP; + if (pid == 0) { + if (unshare(CLONE_NEWNS) || send_byte(pfd[1])) + _exit(1); + pause(); + _exit(0); + } + if (wait_byte(pfd[0])) + return CHILD_SETUP; + snprintf(nsfile, sizeof(nsfile), "/proc/%d/ns/mnt", pid); + if (!move_beneath(nsfile, secret) || errno != EINVAL) + ret = CHILD_STEP2; + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + if (ret) + return ret; + + /* an ordinary file beneath the cover takes the lock over */ + if (move_beneath(mine, secret)) + return CHILD_STEP3; + if (umount2(secret, 0)) + return CHILD_STEP3; + if (!umount2(secret, 0) || errno != EINVAL) + return CHILD_STEP3; + /* and it is in the next copy of the namespace */ + if (unshare(CLONE_NEWNS)) + return CHILD_SETUP; + if (read_word(secret, buf, sizeof(buf)) || strcmp(buf, "mine")) + return CHILD_STEP4; + return CHILD_OK; +} + +TEST_F(locked_mount, mount_namespace_file_cant_take_the_lock) +{ + char p[PATH_LEN], q[PATH_LEN]; + pid_t pid; + int ret; + + snprintf(p, sizeof(p), "%s/P", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + snprintf(p, sizeof(p), "%s/P/secret", self->base); + ASSERT_EQ(create_file(p, "covered-by-root\n"), 0); + snprintf(q, sizeof(q), "%s/P/empty", self->base); + ASSERT_EQ(create_file(q, ""), 0); + /* the cover: an empty file over the secret */ + ASSERT_EQ(mount(q, p, NULL, MS_BIND, NULL), 0); + snprintf(q, sizeof(q), "%s/mine", self->base); + ASSERT_EQ(create_file(q, "mine"), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(ns_file_child(self->base)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + TEST_HARNESS_MAIN