| From e42ab06efafdf77b326724a7158305e8549d6a2c Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 14 Aug 2025 01:44:31 -0400 |
| Subject: use uniform permission checks for all mount propagation changes |
| |
| From: Al Viro <viro@zeniv.linux.org.uk> |
| |
| [ Upstream commit cffd0441872e7f6b1fce5e78fb1c99187a291330 ] |
| |
| do_change_type() and do_set_group() are operating on different |
| aspects of the same thing - propagation graph. The latter |
| asks for mounts involved to be mounted in namespace(s) the caller |
| has CAP_SYS_ADMIN for. The former is a mess - originally it |
| didn't even check that mount *is* mounted. That got fixed, |
| but the resulting check turns out to be too strict for userland - |
| in effect, we check that mount is in our namespace, having already |
| checked that we have CAP_SYS_ADMIN there. |
| |
| What we really need (in both cases) is |
| * only touch mounts that are mounted. That's a must-have |
| constraint - data corruption happens if it get violated. |
| * don't allow to mess with a namespace unless you already |
| have enough permissions to do so (i.e. CAP_SYS_ADMIN in its userns). |
| |
| That's an equivalent of what do_set_group() does; let's extract that |
| into a helper (may_change_propagation()) and use it in both |
| do_set_group() and do_change_type(). |
| |
| Fixes: 12f147ddd6de "do_change_type(): refuse to operate on unmounted/not ours mounts" |
| Acked-by: Andrei Vagin <avagin@gmail.com> |
| Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com> |
| Tested-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com> |
| Reviewed-by: Christian Brauner <brauner@kernel.org> |
| Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/namespace.c | 34 ++++++++++++++++++++-------------- |
| 1 file changed, 20 insertions(+), 14 deletions(-) |
| |
| diff --git a/fs/namespace.c b/fs/namespace.c |
| index f0fa2a1a6b05..2a76269f2a4e 100644 |
| --- a/fs/namespace.c |
| +++ b/fs/namespace.c |
| @@ -2340,6 +2340,19 @@ static int graft_tree(struct mount *mnt, struct mount *p, struct mountpoint *mp) |
| return attach_recursive_mnt(mnt, p, mp, false); |
| } |
| |
| +static int may_change_propagation(const struct mount *m) |
| +{ |
| + struct mnt_namespace *ns = m->mnt_ns; |
| + |
| + // it must be mounted in some namespace |
| + if (IS_ERR_OR_NULL(ns)) // is_mounted() |
| + return -EINVAL; |
| + // and the caller must be admin in userns of that namespace |
| + if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN)) |
| + return -EPERM; |
| + return 0; |
| +} |
| + |
| /* |
| * Sanity check the flags to change_mnt_propagation. |
| */ |
| @@ -2376,10 +2389,10 @@ static int do_change_type(struct path *path, int ms_flags) |
| return -EINVAL; |
| |
| namespace_lock(); |
| - if (!check_mnt(mnt)) { |
| - err = -EINVAL; |
| + err = may_change_propagation(mnt); |
| + if (err) |
| goto out_unlock; |
| - } |
| + |
| if (type == MS_SHARED) { |
| err = invent_group_ids(mnt, recurse); |
| if (err) |
| @@ -2774,18 +2787,11 @@ static int do_set_group(struct path *from_path, struct path *to_path) |
| |
| namespace_lock(); |
| |
| - err = -EINVAL; |
| - /* To and From must be mounted */ |
| - if (!is_mounted(&from->mnt)) |
| - goto out; |
| - if (!is_mounted(&to->mnt)) |
| - goto out; |
| - |
| - err = -EPERM; |
| - /* We should be allowed to modify mount namespaces of both mounts */ |
| - if (!ns_capable(from->mnt_ns->user_ns, CAP_SYS_ADMIN)) |
| + err = may_change_propagation(from); |
| + if (err) |
| goto out; |
| - if (!ns_capable(to->mnt_ns->user_ns, CAP_SYS_ADMIN)) |
| + err = may_change_propagation(to); |
| + if (err) |
| goto out; |
| |
| err = -EINVAL; |
| -- |
| 2.50.1 |
| |