mirror of
https://github.com/torvalds/linux.git
synced 2026-09-26 18:12:03 +02:00
vfs-7.3-rc5.fixes
Please consider pulling these changes from the signed vfs-7.3-rc5.fixes tag.
Thanks!
Christian
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCarab9AAKCRCRxhvAZXjc
osTRAP90MigSgX2U/USBn8zgSTs9Key89pWuwpctsvELYKUSzwEA12UDBswbtgmq
EGT6S2HSa6bifJMXG6AB+nMCcSluuAs=
=KEgE
-----END PGP SIGNATURE-----
Merge tag 'vfs-7.3-rc5.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner:
- Revert "put_mnt_ns(): leave mounts connected". This allows the
creation of reference count cycles in a very trivial way. We can't
bring this in until we have fixed the underlying cause
- vfs: Don't create the private nullfs instance for kthreads under
namespace_sem to avoid false lockdeps complaints
- binfmt_misc:
- Copy the name into a stack buffer and look up the copy in
bpf_binprm_select_interp()
- bpf_binprm_set_interp() and bpf_binprm_set_interp_arg(): Check
the private copy instead so the string that gets staged is the
kstring that was checked
- netfs:
- Make netfs_read_gaps() use separate sink folios rather than one
reused sink folio to discard unwanted data so that cifs checksum
checking sees all the data that was fetched
- Trim reads down to i_size so afs symlinks read correctly from the
cache
- Wrap the direct mempool ->alloc() calls the GFP_KERNEL paths make
in alloc_hooks() via a new mempool_alloc_noreserve() helper
- iov_iter: Use iov_iter_alignment() for the start and length check
added to iov_iter_extract_bvecs() this cycle. It used iter_iov_addr()
and iter_iov_len() which are only valid for ITER_UBUF and ITER_IOVEC
iterators
- super: Make iterate_supers_type() deletion-safe
- inode: Stop evict_inodes() from rescanning the same inodes
- writeback: Bound the cleanup_offline_cgwb() rescans
- ntfs3: Use d_instantiate_new() in ntfs_create_inode()
- ovl: Fix a use-after-free in the ovl_do_mkdir() debug print
- dcache: Unpoison the inline name buffer in __d_alloc() for KMSAN
- autofs: Fix a pipe file reference leak in autofs_kill_sb()
- bpf: Drop the path_unlink and path_rmdir hooks from the list of hooks
for which the verifier rewrites bpf_{set,remove}_dentry_xattr() to
the _locked variants
- squashfs: Range check the xz dictionary size before shifting by it
- selftests: Add the missing eventfd, open_tree_ns, openat2 and xattr
filesystems selftests to TARGETS and drop the stale openat2 entry
left behind when those tests moved
* tag 'vfs-7.3-rc5.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
netfs: Fix missing alloc tagging of direct mempool allocations
bpf: fs/xattr: don't assume the inode is locked in path_unlink/path_rmdir
autofs: fix sbi->pipe file reference leak in autofs_kill_sb()
dcache: unpoison the inline name buffer in __d_alloc()
ovl: fix UAF in ovl_do_mkdir() debug print
super: make iterate_supers_type() deletion-safe
Revert "put_mnt_ns(): leave mounts connected"
Revert "selftests/filesystems: add mntns cleanup test"
binfmt_misc: fix racy checks in bpf set_interp kfuncs
binfmt_misc: fix OOB read in bpf_binprm_select_interp()
fs: don't create the private nullfs mount under namespace_sem
writeback: bound cleanup_offline_cgwb() rescans by rotating scanned inodes
fs: avoid repeated scans in evict_inodes()
netfs, afs: Fix symlink reading
netfs: Fix netfs_read_gaps() to use separate sink folios
squashfs: Add dictionary size range check to prevent shift-out-of-bounds
fs/ntfs3: use d_instantiate_new() in ntfs_create_inode() and murder syzbot's "WARNING in do_new_mount" saga
selftests/filesystems: fix missing and stale TARGETS entries
block: Fix start and length check added to iov_iter_extract_bvecs()
This commit is contained in:
commit
aa98230e41
|
|
@ -51,6 +51,10 @@ void autofs_kill_sb(struct super_block *sb)
|
|||
if (sbi) {
|
||||
/* Free wait queues, close pipe */
|
||||
autofs_catatonic_mode(sbi);
|
||||
if (sbi->pipe) {
|
||||
fput(sbi->pipe);
|
||||
sbi->pipe = NULL;
|
||||
}
|
||||
put_pid(sbi->oz_pgrp);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,8 +141,6 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm,
|
|||
len = strnlen(path, path__sz);
|
||||
if (len == path__sz)
|
||||
return -EINVAL;
|
||||
if (path[0] != '/')
|
||||
return -EINVAL;
|
||||
if (len >= PATH_MAX)
|
||||
return -ENAMETOOLONG;
|
||||
|
||||
|
|
@ -150,6 +148,15 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm,
|
|||
if (!interp)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* The program may pass memory that is written to while this runs,
|
||||
* so check the private copy and not the buffer it was made from.
|
||||
*/
|
||||
if (interp[0] != '/') {
|
||||
kfree(interp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bm_bpf_stage_selection(bprm, interp, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -176,6 +183,7 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm,
|
|||
const char *name, size_t name__sz)
|
||||
{
|
||||
const struct binfmt_misc_interp *interp;
|
||||
char buf[BINFMT_MISC_INTERP_NAME_MAX + 1];
|
||||
size_t len;
|
||||
char *path;
|
||||
|
||||
|
|
@ -184,8 +192,20 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm,
|
|||
len = strnlen(name, name__sz);
|
||||
if (len == name__sz || !len)
|
||||
return -EINVAL;
|
||||
/* No entry binds a longer name, so it cannot be found. */
|
||||
if (len > BINFMT_MISC_INTERP_NAME_MAX)
|
||||
return -ENOENT;
|
||||
|
||||
interp = binfmt_misc_find_interp(bprm->bpf_interps, name);
|
||||
/*
|
||||
* The program may pass memory that is written to while this runs,
|
||||
* so look the name up in a private copy and check that instead.
|
||||
*/
|
||||
memcpy(buf, name, len);
|
||||
buf[len] = '\0';
|
||||
if (!buf[0])
|
||||
return -EINVAL;
|
||||
|
||||
interp = binfmt_misc_find_interp(bprm->bpf_interps, buf);
|
||||
if (!interp)
|
||||
return -ENOENT;
|
||||
|
||||
|
|
@ -228,6 +248,15 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm,
|
|||
if (!val)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* The program may pass memory that is written to while this runs,
|
||||
* so check the private copy and not the buffer it was made from.
|
||||
*/
|
||||
if (!val[0]) {
|
||||
kfree(val);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
kfree(bprm->bpf_interp_arg);
|
||||
bprm->bpf_interp_arg = val;
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -472,10 +472,6 @@ BTF_ID(func, bpf_lsm_inode_rmdir)
|
|||
BTF_ID(func, bpf_lsm_inode_setattr)
|
||||
BTF_ID(func, bpf_lsm_inode_setxattr)
|
||||
BTF_ID(func, bpf_lsm_inode_unlink)
|
||||
#ifdef CONFIG_SECURITY_PATH
|
||||
BTF_ID(func, bpf_lsm_path_unlink)
|
||||
BTF_ID(func, bpf_lsm_path_rmdir)
|
||||
#endif /* CONFIG_SECURITY_PATH */
|
||||
BTF_SET_END(d_inode_locked_hooks)
|
||||
|
||||
bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
|
||||
|
|
|
|||
|
|
@ -1916,6 +1916,10 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
|
|||
* be overwriting an internal NUL character
|
||||
*/
|
||||
dentry->d_shortname.string[DNAME_INLINE_LEN-1] = 0;
|
||||
|
||||
/* Racy __d_lookup_rcu() walk may read past the NUL; harmless */
|
||||
kmsan_unpoison_memory(dentry->d_shortname.string, DNAME_INLINE_LEN);
|
||||
|
||||
if (unlikely(!name)) {
|
||||
name = &slash_name;
|
||||
dname = dentry->d_shortname.string;
|
||||
|
|
|
|||
|
|
@ -727,19 +727,34 @@ static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb,
|
|||
struct inode_switch_wbs_context *isw,
|
||||
struct list_head *list, int *nr)
|
||||
{
|
||||
struct inode *inode;
|
||||
struct inode *inode, *tmp;
|
||||
LIST_HEAD(scanned);
|
||||
bool full = false;
|
||||
|
||||
/*
|
||||
* Walk from the oldest end and move scanned inodes to the newest
|
||||
* end, so the next scan resumes at unscanned inodes instead of
|
||||
* re-walking an ever-growing run of prepared and skipped ones.
|
||||
* For b_dirty_time this keeps the oldest unscanned inode at the
|
||||
* end move_expired_inodes() picks from; b_attached is unordered.
|
||||
*/
|
||||
list_for_each_entry_safe_reverse(inode, tmp, list, i_io_list) {
|
||||
list_move(&inode->i_io_list, &scanned);
|
||||
|
||||
list_for_each_entry(inode, list, i_io_list) {
|
||||
if (!inode_prepare_wbs_switch(inode, new_wb))
|
||||
continue;
|
||||
|
||||
isw->inodes[*nr] = inode;
|
||||
(*nr)++;
|
||||
|
||||
if (*nr >= WB_MAX_INODES_PER_ISW - 1)
|
||||
return true;
|
||||
if (*nr >= WB_MAX_INODES_PER_ISW - 1) {
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
list_splice(&scanned, list);
|
||||
|
||||
return full;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
11
fs/inode.c
11
fs/inode.c
|
|
@ -880,7 +880,6 @@ void evict_inodes(struct super_block *sb)
|
|||
struct inode *inode;
|
||||
LIST_HEAD(dispose);
|
||||
|
||||
again:
|
||||
spin_lock(&sb->s_inode_list_lock);
|
||||
list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
|
||||
if (icount_read_once(inode))
|
||||
|
|
@ -899,19 +898,19 @@ void evict_inodes(struct super_block *sb)
|
|||
inode_state_set(inode, I_FREEING);
|
||||
inode_lru_list_del(inode);
|
||||
spin_unlock(&inode->i_lock);
|
||||
list_add(&inode->i_lru, &dispose);
|
||||
|
||||
/*
|
||||
* We can have a ton of inodes to evict at unmount time given
|
||||
* enough memory, check to see if we need to go to sleep for a
|
||||
* bit so we don't livelock.
|
||||
* Keep this inode out of dispose so it stays on s_inodes while
|
||||
* the list lock is dropped. I_FREEING prevents new references
|
||||
* and leaves eviction to us, so we can resume the walk from it.
|
||||
*/
|
||||
if (need_resched()) {
|
||||
spin_unlock(&sb->s_inode_list_lock);
|
||||
cond_resched();
|
||||
dispose_list(&dispose);
|
||||
goto again;
|
||||
spin_lock(&sb->s_inode_list_lock);
|
||||
}
|
||||
list_add(&inode->i_lru, &dispose);
|
||||
}
|
||||
spin_unlock(&sb->s_inode_list_lock);
|
||||
|
||||
|
|
|
|||
|
|
@ -434,8 +434,8 @@ void kernfs_kill_sb(struct super_block *sb)
|
|||
up_write(&root->kernfs_supers_rwsem);
|
||||
|
||||
/*
|
||||
* Remove the superblock from fs_supers/s_instances
|
||||
* so we can't find it, before freeing kernfs_super_info.
|
||||
* Mark the superblock dead so sget_fc() can't find it,
|
||||
* before freeing kernfs_super_info.
|
||||
*/
|
||||
kill_anon_super(sb);
|
||||
kfree(info);
|
||||
|
|
|
|||
|
|
@ -6184,6 +6184,21 @@ struct mnt_namespace init_mnt_ns = {
|
|||
.poll = __WAIT_QUEUE_HEAD_INITIALIZER(init_mnt_ns.poll),
|
||||
};
|
||||
|
||||
static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt,
|
||||
struct vfsmount *nullfs_mnt)
|
||||
{
|
||||
struct path root = {
|
||||
.mnt = nullfs_mnt,
|
||||
.dentry = nullfs_mnt->mnt_root,
|
||||
};
|
||||
|
||||
LOCK_MOUNT_EXACT(mp, &root);
|
||||
if (unlikely(IS_ERR(mp.parent)))
|
||||
panic("VFS: Failed to mount rootfs on nullfs");
|
||||
scoped_guard(mount_writer)
|
||||
attach_mnt(real_mount(mnt), mp.parent, mp.mp);
|
||||
}
|
||||
|
||||
static void __init init_mount_tree(void)
|
||||
{
|
||||
struct vfsmount *mnt, *nullfs_mnt;
|
||||
|
|
@ -6215,15 +6230,7 @@ static void __init init_mount_tree(void)
|
|||
mnt_root = real_mount(nullfs_mnt);
|
||||
init_mnt_ns.root = mnt_root;
|
||||
|
||||
/* Mount mutable rootfs on top of nullfs. */
|
||||
root.mnt = nullfs_mnt;
|
||||
root.dentry = nullfs_mnt->mnt_root;
|
||||
|
||||
LOCK_MOUNT_EXACT(mp, &root);
|
||||
if (unlikely(IS_ERR(mp.parent)))
|
||||
panic("VFS: Failed to mount rootfs on nullfs");
|
||||
scoped_guard(mount_writer)
|
||||
attach_mnt(real_mount(mnt), mp.parent, mp.mp);
|
||||
mount_rootfs_on_nullfs(mnt, nullfs_mnt);
|
||||
|
||||
pr_info("VFS: Finished mounting rootfs on nullfs\n");
|
||||
|
||||
|
|
@ -6294,7 +6301,7 @@ void put_mnt_ns(struct mnt_namespace *ns)
|
|||
guard(namespace_excl)();
|
||||
emptied_ns = ns;
|
||||
guard(mount_writer)();
|
||||
umount_tree(ns->root, UMOUNT_CONNECTED);
|
||||
umount_tree(ns->root, 0);
|
||||
}
|
||||
|
||||
struct vfsmount *kern_mount(struct file_system_type *type)
|
||||
|
|
|
|||
|
|
@ -482,15 +482,14 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
|
|||
struct netfs_group *group = netfs_folio_group(folio);
|
||||
struct netfs_folio *finfo = netfs_folio_info(folio);
|
||||
struct netfs_inode *ctx = netfs_inode(mapping->host);
|
||||
struct folio *sink = NULL;
|
||||
struct bio_vec *bvec;
|
||||
struct bio_vec *bvec = NULL;
|
||||
unsigned int from = finfo->dirty_offset;
|
||||
unsigned int to = from + finfo->dirty_len;
|
||||
unsigned int off = 0, i = 0;
|
||||
unsigned int off = 0;
|
||||
size_t flen = folio_size(folio);
|
||||
size_t nr_bvec = flen / PAGE_SIZE + 2;
|
||||
size_t part;
|
||||
int ret;
|
||||
int ret, i = 0, sink_from = -1, sink_to = -1;
|
||||
|
||||
_enter("%lx", folio->index);
|
||||
|
||||
|
|
@ -515,24 +514,23 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
|
|||
if (!bvec)
|
||||
goto discard;
|
||||
|
||||
sink = folio_alloc(GFP_KERNEL, 0);
|
||||
if (!sink) {
|
||||
kfree(bvec);
|
||||
goto discard;
|
||||
}
|
||||
|
||||
trace_netfs_folio(folio, netfs_folio_trace_read_gaps);
|
||||
|
||||
rreq->direct_bv = bvec;
|
||||
rreq->direct_bv_count = nr_bvec;
|
||||
if (from > 0) {
|
||||
bvec_set_folio(&bvec[i++], folio, from, 0);
|
||||
off = from;
|
||||
}
|
||||
sink_from = i;
|
||||
while (off < to) {
|
||||
struct folio *sink = folio_alloc(GFP_KERNEL, 0);
|
||||
|
||||
if (!sink)
|
||||
goto discard;
|
||||
part = min_t(size_t, to - off, PAGE_SIZE);
|
||||
bvec_set_folio(&bvec[i++], sink, part, 0);
|
||||
bvec_set_folio(&bvec[i], sink, part, 0);
|
||||
off += part;
|
||||
sink_to = i;
|
||||
i++;
|
||||
}
|
||||
if (to < flen)
|
||||
bvec_set_folio(&bvec[i++], folio, flen - to, to);
|
||||
|
|
@ -553,8 +551,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
|
|||
folio_mark_uptodate(folio);
|
||||
}
|
||||
|
||||
if (sink)
|
||||
folio_put(sink);
|
||||
if (sink_to >= 0)
|
||||
for (; sink_from <= sink_to; sink_from++)
|
||||
folio_put(bvec_folio(&bvec[sink_from]));
|
||||
kfree(bvec);
|
||||
folio_unlock(folio);
|
||||
netfs_put_request(rreq, netfs_rreq_trace_put_return);
|
||||
return ret < 0 ? ret : 0;
|
||||
|
|
@ -563,6 +563,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
|
|||
netfs_put_failed_request(rreq);
|
||||
alloc_error:
|
||||
folio_unlock(folio);
|
||||
if (sink_to >= 0)
|
||||
for (; sink_from <= sink_to; sink_from++)
|
||||
folio_put(bvec_folio(&bvec[sink_from]));
|
||||
kfree(bvec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping,
|
|||
|
||||
rreq = mempool_alloc(mempool, gfp);
|
||||
} else {
|
||||
rreq = mempool->alloc(gfp, mempool->pool_data);
|
||||
rreq = mempool_alloc_noreserve(mempool, gfp);
|
||||
if (!rreq)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
|
@ -214,7 +214,7 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq
|
|||
struct kmem_cache *cache = mempool->pool_data;
|
||||
|
||||
if (rreq->gfp == GFP_KERNEL)
|
||||
subreq = mempool->alloc(rreq->gfp, mempool->pool_data);
|
||||
subreq = mempool_alloc_noreserve(mempool, rreq->gfp);
|
||||
else
|
||||
subreq = mempool_alloc(mempool, rreq->gfp);
|
||||
if (!subreq)
|
||||
|
|
|
|||
|
|
@ -435,6 +435,11 @@ static void netfs_rreq_assess_single(struct netfs_io_request *rreq)
|
|||
netfs_single_mark_inode_dirty(rreq->inode);
|
||||
}
|
||||
|
||||
/* To do DIO, the cache has to round the size up, so we need to undo
|
||||
* the rounding.
|
||||
*/
|
||||
rreq->transferred = min(rreq->transferred, rreq->i_size);
|
||||
|
||||
if (rreq->iocb) {
|
||||
rreq->iocb->ki_pos += rreq->transferred;
|
||||
if (rreq->iocb->ki_complete) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp,
|
|||
struct folio_queue *fq;
|
||||
|
||||
if (gfp == GFP_KERNEL)
|
||||
fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data);
|
||||
fq = mempool_alloc_noreserve(&netfs_folioq_pool, gfp);
|
||||
else
|
||||
fq = mempool_alloc(&netfs_folioq_pool, gfp);
|
||||
if (fq) {
|
||||
|
|
|
|||
|
|
@ -1866,10 +1866,10 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
|
|||
goto out6;
|
||||
|
||||
/*
|
||||
* Call 'd_instantiate' after inode->i_op is set
|
||||
* Call 'd_instantiate_new' after inode->i_op is set
|
||||
* but before finish_open.
|
||||
*/
|
||||
d_instantiate(dentry, inode);
|
||||
d_instantiate_new(dentry, inode);
|
||||
|
||||
/* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */
|
||||
inode_set_atime_to_ts(inode, ni->i_crtime);
|
||||
|
|
@ -1917,9 +1917,6 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
|
|||
if (!fnd)
|
||||
ni_unlock(dir_ni);
|
||||
|
||||
if (!err)
|
||||
unlock_new_inode(inode);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -254,8 +254,10 @@ static inline struct dentry *ovl_do_mkdir(struct ovl_fs *ofs,
|
|||
{
|
||||
struct dentry *ret;
|
||||
|
||||
/* vfs_mkdir() drops @dentry on failure and may replace it on success */
|
||||
pr_debug("mkdir(%pd2, 0%o)\n", dentry, mode);
|
||||
ret = vfs_mkdir(ovl_upper_mnt_idmap(ofs), dir, dentry, mode, NULL);
|
||||
pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, PTR_ERR_OR_ZERO(ret));
|
||||
pr_debug("...mkdir = %i\n", PTR_ERR_OR_ZERO(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,10 +57,10 @@ static void *squashfs_xz_comp_opts(struct squashfs_sb_info *msblk,
|
|||
|
||||
opts->dict_size = le32_to_cpu(comp_opts->dictionary_size);
|
||||
|
||||
/* the dictionary size should be 2^n or 2^n+2^(n+1) */
|
||||
/* the dictionary size should be positive and 2^n or 2^n+2^(n+1) */
|
||||
n = ffs(opts->dict_size) - 1;
|
||||
if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) +
|
||||
(1 << (n + 1))) {
|
||||
if (opts->dict_size <= 0 || (opts->dict_size != (1 << n) &&
|
||||
opts->dict_size != (1 << n) + (1 << (n + 1)))) {
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
|
|
|||
39
fs/super.c
39
fs/super.c
|
|
@ -419,15 +419,19 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
|
|||
void put_super(struct super_block *s)
|
||||
{
|
||||
if (refcount_dec_and_test(&s->s_passive)) {
|
||||
struct file_system_type *type = s->s_type;
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
list_del_init(&s->s_list);
|
||||
hlist_del_init(&s->s_instances);
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
WARN_ON(s->s_dentry_lru.node);
|
||||
WARN_ON(s->s_inode_lru.node);
|
||||
WARN_ON(s->s_mounts);
|
||||
call_rcu(&s->rcu, destroy_super_rcu);
|
||||
/* The unlink above may touch type->fs_supers, so drop it last. */
|
||||
put_filesystem(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -544,17 +548,6 @@ static void kill_super_notify(struct super_block *sb)
|
|||
if (sb->s_flags & SB_DEAD)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Remove it from @fs_supers so it isn't found by new
|
||||
* sget_fc() walkers anymore. Any concurrent mounter still
|
||||
* managing to grab a temporary reference is guaranteed to
|
||||
* already see SB_DYING and will wait until we notify them about
|
||||
* SB_DEAD.
|
||||
*/
|
||||
spin_lock(&sb_lock);
|
||||
hlist_del_init(&sb->s_instances);
|
||||
spin_unlock(&sb_lock);
|
||||
|
||||
/* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */
|
||||
if (sb->s_super_dev->sd_dev) {
|
||||
super_dev_put(sb->s_super_dev);
|
||||
|
|
@ -563,11 +556,15 @@ static void kill_super_notify(struct super_block *sb)
|
|||
|
||||
/*
|
||||
* Let concurrent mounts know that this thing is really dead.
|
||||
* We don't need @sb->s_umount here as every concurrent caller
|
||||
* will see SB_DYING and either discard the superblock or wait
|
||||
* for SB_DEAD.
|
||||
* sget_fc() skips SB_DEAD superblocks and calls test() under
|
||||
* sb_lock, so set it under sb_lock: once we return no test()
|
||||
* runs on this superblock anymore and none will start. Everyone
|
||||
* else already saw SB_DYING and either discarded the superblock
|
||||
* or waits for SB_DEAD.
|
||||
*/
|
||||
spin_lock(&sb_lock);
|
||||
super_wake(sb, SB_DEAD);
|
||||
spin_unlock(&sb_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -594,7 +591,6 @@ void deactivate_locked_super(struct super_block *s)
|
|||
list_lru_destroy(&s->s_dentry_lru);
|
||||
list_lru_destroy(&s->s_inode_lru);
|
||||
|
||||
put_filesystem(fs);
|
||||
put_super(s);
|
||||
} else {
|
||||
super_unlock_excl(s);
|
||||
|
|
@ -781,12 +777,12 @@ void generic_shutdown_super(struct super_block *sb)
|
|||
}
|
||||
/*
|
||||
* Broadcast to everyone that grabbed a temporary reference to this
|
||||
* superblock before we removed it from @fs_supers that the superblock
|
||||
* is dying. Every walker of @fs_supers outside of sget_fc() will now
|
||||
* discard this superblock and treat it as dead.
|
||||
* superblock that it is dying. Every walker of @fs_supers outside
|
||||
* of sget_fc() will now discard this superblock and treat it as
|
||||
* dead.
|
||||
*
|
||||
* We leave the superblock on @fs_supers so it can be found by
|
||||
* sget_fc() until we passed sb->kill_sb().
|
||||
* sget_fc() keeps finding the superblock until SB_DEAD is set, so
|
||||
* a concurrent mounter waits until we passed sb->kill_sb().
|
||||
*/
|
||||
super_wake(sb, SB_DYING);
|
||||
super_unlock_excl(sb);
|
||||
|
|
@ -865,6 +861,9 @@ struct super_block *sget_fc(struct fs_context *fc,
|
|||
spin_lock(&sb_lock);
|
||||
if (test) {
|
||||
hlist_for_each_entry(old, &fc->fs_type->fs_supers, s_instances) {
|
||||
/* Only unlinked at the last passive reference. */
|
||||
if (super_flags(old, SB_DEAD))
|
||||
continue;
|
||||
if (test(old, fc))
|
||||
goto share_extant_sb;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,13 @@ int mempool_alloc_bulk_noprof(struct mempool *pool, void **elem,
|
|||
#define mempool_alloc_bulk(...) \
|
||||
alloc_hooks(mempool_alloc_bulk_noprof(__VA_ARGS__))
|
||||
|
||||
/*
|
||||
* Allocate a new element without dipping into the pool's reserves or
|
||||
* waiting. Returns NULL on failure.
|
||||
*/
|
||||
#define mempool_alloc_noreserve(_pool, _gfp) \
|
||||
alloc_hooks((_pool)->alloc(_gfp, (_pool)->pool_data))
|
||||
|
||||
void *mempool_alloc_preallocated(struct mempool *pool) __malloc;
|
||||
void mempool_free(void *element, struct mempool *pool);
|
||||
unsigned int mempool_free_bulk(struct mempool *pool, void **elem,
|
||||
|
|
|
|||
|
|
@ -35,8 +35,11 @@ TARGETS += fchmodat2
|
|||
TARGETS += filesystems
|
||||
TARGETS += filesystems/binderfs
|
||||
TARGETS += filesystems/epoll
|
||||
TARGETS += filesystems/eventfd
|
||||
TARGETS += filesystems/failfs
|
||||
TARGETS += filesystems/fat
|
||||
TARGETS += filesystems/openat2
|
||||
TARGETS += filesystems/open_tree_ns
|
||||
TARGETS += filesystems/overlayfs
|
||||
TARGETS += filesystems/statmount
|
||||
TARGETS += filesystems/mount-notify
|
||||
|
|
@ -46,7 +49,7 @@ TARGETS += filesystems/move_mount
|
|||
TARGETS += filesystems/empty_mntns
|
||||
TARGETS += filesystems/fsmount_ns
|
||||
TARGETS += filesystems/fscontext_ns
|
||||
TARGETS += filesystems/mntns_cleanup
|
||||
TARGETS += filesystems/xattr
|
||||
TARGETS += firmware
|
||||
TARGETS += fpu
|
||||
TARGETS += ftrace
|
||||
|
|
@ -103,7 +106,6 @@ TARGETS += prctl
|
|||
TARGETS += proc
|
||||
TARGETS += pstore
|
||||
TARGETS += ptrace
|
||||
TARGETS += openat2
|
||||
TARGETS += rdma
|
||||
TARGETS += resctrl
|
||||
TARGETS += riscv
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
mntns_cleanup_test
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
TEST_GEN_PROGS := mntns_cleanup_test
|
||||
|
||||
CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
|
||||
|
||||
include ../../lib.mk
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../kselftest_harness.h"
|
||||
|
||||
FIXTURE(mntns_cleanup) {
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(mntns_cleanup)
|
||||
{
|
||||
if (geteuid() != 0)
|
||||
SKIP(return, "test requires CAP_SYS_ADMIN");
|
||||
|
||||
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
|
||||
ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
|
||||
|
||||
rmdir("/mnt_dir");
|
||||
ASSERT_EQ(mkdir("/mnt_dir", 0755), 0);
|
||||
ASSERT_EQ(mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL), 0);
|
||||
ASSERT_EQ(mkdir("/mnt_dir/hidden", 0755), 0);
|
||||
ASSERT_EQ(mkdir("/mnt_dir/hidden/secret", 0755), 0);
|
||||
ASSERT_EQ(mount("tmpfs", "/mnt_dir/hidden", "tmpfs", 0, NULL), 0);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(mntns_cleanup)
|
||||
{
|
||||
}
|
||||
|
||||
/* Mounts must stay connected when a mount namespace is cleaned up. */
|
||||
TEST_F(mntns_cleanup, keeps_mounts_connected)
|
||||
{
|
||||
int fd, sfd, err;
|
||||
|
||||
fd = open("/mnt_dir", O_PATH | O_DIRECTORY | O_CLOEXEC);
|
||||
ASSERT_GE(fd, 0);
|
||||
|
||||
/* Destroy the namespace; the fd keeps /mnt_dir alive. */
|
||||
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
|
||||
|
||||
sfd = openat(fd, "hidden/secret", O_RDONLY);
|
||||
err = errno;
|
||||
if (sfd >= 0)
|
||||
close(sfd);
|
||||
close(fd);
|
||||
|
||||
ASSERT_LT(sfd, 0)
|
||||
TH_LOG("mount namespace teardown revealed what the overmount covered");
|
||||
ASSERT_EQ(err, ENOENT);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Loading…
Reference in New Issue
Block a user