Commit c5c2a8b4 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull mount fixes from Al Viro:
 "Several mount-related fixes"

* tag 'pull-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  userns and mnt_idmap leak in open_tree_attr(2)
  attach_recursive_mnt(): do not lock the covering tree when sliding something under it
  replace collect_mounts()/drop_collected_mounts() with a safer variant
parents c4dce0c0 0748e553
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -1249,3 +1249,12 @@ Using try_lookup_noperm() will require linux/namei.h to be included.

Calling conventions for ->d_automount() have changed; we should *not* grab
an extra reference to new mount - it should be returned with refcount 1.

---

collect_mounts()/drop_collected_mounts()/iterate_mounts() are gone now.
Replacement is collect_paths()/drop_collected_path(), with no special
iterator needed.  Instead of a cloned mount tree, the new interface returns
an array of struct path, one for each mount collect_mounts() would've
created.  These struct path point to locations in the caller's namespace
that would be roots of the cloned mounts.
+66 −49
Original line number Diff line number Diff line
@@ -2310,21 +2310,62 @@ struct mount *copy_tree(struct mount *src_root, struct dentry *dentry,
	return dst_mnt;
}

/* Caller should check returned pointer for errors */
static inline bool extend_array(struct path **res, struct path **to_free,
				unsigned n, unsigned *count, unsigned new_count)
{
	struct path *p;

	if (likely(n < *count))
		return true;
	p = kmalloc_array(new_count, sizeof(struct path), GFP_KERNEL);
	if (p && *count)
		memcpy(p, *res, *count * sizeof(struct path));
	*count = new_count;
	kfree(*to_free);
	*to_free = *res = p;
	return p;
}

struct vfsmount *collect_mounts(const struct path *path)
struct path *collect_paths(const struct path *path,
			      struct path *prealloc, unsigned count)
{
	struct mount *tree;
	namespace_lock();
	if (!check_mnt(real_mount(path->mnt)))
		tree = ERR_PTR(-EINVAL);
	else
		tree = copy_tree(real_mount(path->mnt), path->dentry,
				 CL_COPY_ALL | CL_PRIVATE);
	namespace_unlock();
	if (IS_ERR(tree))
		return ERR_CAST(tree);
	return &tree->mnt;
	struct mount *root = real_mount(path->mnt);
	struct mount *child;
	struct path *res = prealloc, *to_free = NULL;
	unsigned n = 0;

	guard(rwsem_read)(&namespace_sem);

	if (!check_mnt(root))
		return ERR_PTR(-EINVAL);
	if (!extend_array(&res, &to_free, 0, &count, 32))
		return ERR_PTR(-ENOMEM);
	res[n++] = *path;
	list_for_each_entry(child, &root->mnt_mounts, mnt_child) {
		if (!is_subdir(child->mnt_mountpoint, path->dentry))
			continue;
		for (struct mount *m = child; m; m = next_mnt(m, child)) {
			if (!extend_array(&res, &to_free, n, &count, 2 * count))
				return ERR_PTR(-ENOMEM);
			res[n].mnt = &m->mnt;
			res[n].dentry = m->mnt.mnt_root;
			n++;
		}
	}
	if (!extend_array(&res, &to_free, n, &count, count + 1))
		return ERR_PTR(-ENOMEM);
	memset(res + n, 0, (count - n) * sizeof(struct path));
	for (struct path *p = res; p->mnt; p++)
		path_get(p);
	return res;
}

void drop_collected_paths(struct path *paths, struct path *prealloc)
{
	for (struct path *p = paths; p->mnt; p++)
		path_put(p);
	if (paths != prealloc)
		kfree(paths);
}

static void free_mnt_ns(struct mnt_namespace *);
@@ -2401,15 +2442,6 @@ void dissolve_on_fput(struct vfsmount *mnt)
	free_mnt_ns(ns);
}

void drop_collected_mounts(struct vfsmount *mnt)
{
	namespace_lock();
	lock_mount_hash();
	umount_tree(real_mount(mnt), 0);
	unlock_mount_hash();
	namespace_unlock();
}

static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
{
	struct mount *child;
@@ -2511,21 +2543,6 @@ struct vfsmount *clone_private_mount(const struct path *path)
}
EXPORT_SYMBOL_GPL(clone_private_mount);

int iterate_mounts(int (*f)(struct vfsmount *, void *), void *arg,
		   struct vfsmount *root)
{
	struct mount *mnt;
	int res = f(root, arg);
	if (res)
		return res;
	list_for_each_entry(mnt, &real_mount(root)->mnt_list, mnt_list) {
		res = f(&mnt->mnt, arg);
		if (res)
			return res;
	}
	return 0;
}

static void lock_mnt_tree(struct mount *mnt)
{
	struct mount *p;
@@ -2751,14 +2768,14 @@ static int attach_recursive_mnt(struct mount *source_mnt,
	hlist_for_each_entry_safe(child, n, &tree_list, mnt_hash) {
		struct mount *q;
		hlist_del_init(&child->mnt_hash);
		q = __lookup_mnt(&child->mnt_parent->mnt,
				 child->mnt_mountpoint);
		if (q)
			mnt_change_mountpoint(child, smp, q);
		/* Notice when we are propagating across user namespaces */
		if (child->mnt_parent->mnt_ns->user_ns != user_ns)
			lock_mnt_tree(child);
		child->mnt.mnt_flags &= ~MNT_LOCKED;
		q = __lookup_mnt(&child->mnt_parent->mnt,
				 child->mnt_mountpoint);
		if (q)
			mnt_change_mountpoint(child, smp, q);
		commit_tree(child);
	}
	put_mountpoint(smp);
@@ -5290,16 +5307,12 @@ SYSCALL_DEFINE5(open_tree_attr, int, dfd, const char __user *, filename,
			kattr.kflags |= MOUNT_KATTR_RECURSE;

		ret = wants_mount_setattr(uattr, usize, &kattr);
		if (ret < 0)
			return ret;

		if (ret) {
		if (ret > 0) {
			ret = do_mount_setattr(&file->f_path, &kattr);
			if (ret)
				return ret;

			finish_mount_kattr(&kattr);
		}
		if (ret)
			return ret;
	}

	fd = get_unused_fd_flags(flags & O_CLOEXEC);
@@ -6262,7 +6275,11 @@ void put_mnt_ns(struct mnt_namespace *ns)
{
	if (!refcount_dec_and_test(&ns->ns.count))
		return;
	drop_collected_mounts(&ns->root->mnt);
	namespace_lock();
	lock_mount_hash();
	umount_tree(ns->root, 0);
	unlock_mount_hash();
	namespace_unlock();
	free_mnt_ns(ns);
}

+0 −2
Original line number Diff line number Diff line
@@ -28,8 +28,6 @@
#define CL_SHARED_TO_SLAVE	0x20
#define CL_COPY_MNT_NS_FILE	0x40

#define CL_COPY_ALL		(CL_COPY_UNBINDABLE | CL_COPY_MNT_NS_FILE)

static inline void set_mnt_shared(struct mount *mnt)
{
	mnt->mnt.mnt_flags &= ~MNT_SHARED_MASK;
+2 −4
Original line number Diff line number Diff line
@@ -116,10 +116,8 @@ extern int may_umount_tree(struct vfsmount *);
extern int may_umount(struct vfsmount *);
int do_mount(const char *, const char __user *,
		     const char *, unsigned long, void *);
extern struct vfsmount *collect_mounts(const struct path *);
extern void drop_collected_mounts(struct vfsmount *);
extern int iterate_mounts(int (*)(struct vfsmount *, void *), void *,
			  struct vfsmount *);
extern struct path *collect_paths(const struct path *, struct path *, unsigned);
extern void drop_collected_paths(struct path *, struct path *);
extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num);

extern int cifs_root_data(char **dev, char **opts);
+34 −29
Original line number Diff line number Diff line
@@ -668,12 +668,6 @@ int audit_remove_tree_rule(struct audit_krule *rule)
	return 0;
}

static int compare_root(struct vfsmount *mnt, void *arg)
{
	return inode_to_key(d_backing_inode(mnt->mnt_root)) ==
	       (unsigned long)arg;
}

void audit_trim_trees(void)
{
	struct list_head cursor;
@@ -683,8 +677,9 @@ void audit_trim_trees(void)
	while (cursor.next != &tree_list) {
		struct audit_tree *tree;
		struct path path;
		struct vfsmount *root_mnt;
		struct audit_node *node;
		struct path *paths;
		struct path array[16];
		int err;

		tree = container_of(cursor.next, struct audit_tree, list);
@@ -696,9 +691,9 @@ void audit_trim_trees(void)
		if (err)
			goto skip_it;

		root_mnt = collect_mounts(&path);
		paths = collect_paths(&path, array, 16);
		path_put(&path);
		if (IS_ERR(root_mnt))
		if (IS_ERR(paths))
			goto skip_it;

		spin_lock(&hash_lock);
@@ -706,14 +701,17 @@ void audit_trim_trees(void)
			struct audit_chunk *chunk = find_chunk(node);
			/* this could be NULL if the watch is dying else where... */
			node->index |= 1U<<31;
			if (iterate_mounts(compare_root,
					   (void *)(chunk->key),
					   root_mnt))
			for (struct path *p = paths; p->dentry; p++) {
				struct inode *inode = p->dentry->d_inode;
				if (inode_to_key(inode) == chunk->key) {
					node->index &= ~(1U<<31);
					break;
				}
			}
		}
		spin_unlock(&hash_lock);
		trim_marked(tree);
		drop_collected_mounts(root_mnt);
		drop_collected_paths(paths, array);
skip_it:
		put_tree(tree);
		mutex_lock(&audit_filter_mutex);
@@ -742,9 +740,14 @@ void audit_put_tree(struct audit_tree *tree)
	put_tree(tree);
}

static int tag_mount(struct vfsmount *mnt, void *arg)
static int tag_mounts(struct path *paths, struct audit_tree *tree)
{
	return tag_chunk(d_backing_inode(mnt->mnt_root), arg);
	for (struct path *p = paths; p->dentry; p++) {
		int err = tag_chunk(p->dentry->d_inode, tree);
		if (err)
			return err;
	}
	return 0;
}

/*
@@ -801,7 +804,8 @@ int audit_add_tree_rule(struct audit_krule *rule)
{
	struct audit_tree *seed = rule->tree, *tree;
	struct path path;
	struct vfsmount *mnt;
	struct path array[16];
	struct path *paths;
	int err;

	rule->tree = NULL;
@@ -828,16 +832,16 @@ int audit_add_tree_rule(struct audit_krule *rule)
	err = kern_path(tree->pathname, 0, &path);
	if (err)
		goto Err;
	mnt = collect_mounts(&path);
	paths = collect_paths(&path, array, 16);
	path_put(&path);
	if (IS_ERR(mnt)) {
		err = PTR_ERR(mnt);
	if (IS_ERR(paths)) {
		err = PTR_ERR(paths);
		goto Err;
	}

	get_tree(tree);
	err = iterate_mounts(tag_mount, tree, mnt);
	drop_collected_mounts(mnt);
	err = tag_mounts(paths, tree);
	drop_collected_paths(paths, array);

	if (!err) {
		struct audit_node *node;
@@ -872,20 +876,21 @@ int audit_tag_tree(char *old, char *new)
	struct list_head cursor, barrier;
	int failed = 0;
	struct path path1, path2;
	struct vfsmount *tagged;
	struct path array[16];
	struct path *paths;
	int err;

	err = kern_path(new, 0, &path2);
	if (err)
		return err;
	tagged = collect_mounts(&path2);
	paths = collect_paths(&path2, array, 16);
	path_put(&path2);
	if (IS_ERR(tagged))
		return PTR_ERR(tagged);
	if (IS_ERR(paths))
		return PTR_ERR(paths);

	err = kern_path(old, 0, &path1);
	if (err) {
		drop_collected_mounts(tagged);
		drop_collected_paths(paths, array);
		return err;
	}

@@ -914,7 +919,7 @@ int audit_tag_tree(char *old, char *new)
			continue;
		}

		failed = iterate_mounts(tag_mount, tree, tagged);
		failed = tag_mounts(paths, tree);
		if (failed) {
			put_tree(tree);
			mutex_lock(&audit_filter_mutex);
@@ -955,7 +960,7 @@ int audit_tag_tree(char *old, char *new)
	list_del(&cursor);
	mutex_unlock(&audit_filter_mutex);
	path_put(&path1);
	drop_collected_mounts(tagged);
	drop_collected_paths(paths, array);
	return failed;
}