From b572e501b901b5c1a6a91c6f1851cadcf4526861 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Tue, 28 Aug 2018 04:50:03 +1000 Subject: [PATCH] fs: disallow --rbind if mount has an MS_UNBINDABLE child MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Container runtimes have to mask over certain areas in /proc/ and /sys/ in order to avoid a variety of information disclosures and other vulnerabilities being exposed to containers. This masking is done by bind-mounting tmpfs over the relevant parts of procfs and sysfs -- and mnt_is_visible() stops containers from being able to mount a clean procfs. However, it turns out that MS_UNBINDABLE allows userspace to strip away these masked paths. For instance: % mount -t tmpfs tmpfs /proc/scsi % unshare -rm % mount --make-runbindable /proc % mount --make-private /proc % mount --rbind /proc /tmp/proc And now /tmp/proc will have an unmasked /proc/scsi (which would not be possible to get otherwise -- both bind and a new procfs would fail to mount because there is a child mount with MNT_LOCKED). We also cannot just block MNT_LOCKED|MNT_UNBINDABLE because you can create an intermediate directory above the MNT_LOCKED which is MNT_UNBINDABLE, and then rbind it to remove the MNT_LOCKED mount. Cc: "Eric W. Biederman" Reported-by: Jonathan Calmels Suggested-by: Stéphane Graber Suggested-by: Christian Brauner Signed-off-by: Aleksa Sarai --- fs/namespace.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 99186556f8d3..f8b40206d261 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2124,14 +2124,14 @@ static int do_change_type(struct path *path, int ms_flags) return err; } -static bool has_locked_children(struct mount *mnt, struct dentry *dentry) +static bool has_children_with(struct mount *mnt, struct dentry *dentry, + int flag) { struct mount *child; list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) { if (!is_subdir(child->mnt_mountpoint, dentry)) continue; - - if (child->mnt.mnt_flags & MNT_LOCKED) + if (child->mnt.mnt_flags & flag) return true; } return false; @@ -2175,7 +2175,9 @@ static int do_loopback(struct path *path, const char *old_name, if (!check_mnt(old) && old_path.dentry->d_op != &ns_dentry_operations) goto out2; - if (!recurse && has_locked_children(old, old_path.dentry)) + if (!recurse && has_children_with(old, old_path.dentry, MNT_LOCKED)) + goto out2; + if (recurse && has_children_with(old, old_path.dentry, MNT_UNBINDABLE)) goto out2; if (recurse) -- 2.19.0