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:
Linus Torvalds 2026-09-25 11:12:06 -07:00
commit aa98230e41
21 changed files with 149 additions and 145 deletions

View File

@ -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);
}

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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;
}
/**

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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;
}

View File

@ -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)

View File

@ -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) {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -1,2 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
mntns_cleanup_test

View File

@ -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

View File

@ -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