Commit cfb2f6f6 authored by Eric W. Biederman's avatar Eric W. Biederman

Revert "mqueue: switch to on-demand creation of internal mount"

This reverts commit 36735a6a.

Aleksa Sarai <asarai@suse.de> writes:
> [REGRESSION v4.16-rc6] [PATCH] mqueue: forbid unprivileged user access to internal mount
>
> Felix reported weird behaviour on 4.16.0-rc6 with regards to mqueue[1],
> which was introduced by 36735a6a ("mqueue: switch to on-demand
> creation of internal mount").
>
> Basically, the reproducer boils down to being able to mount mqueue if
> you create a new user namespace, even if you don't unshare the IPC
> namespace.
>
> Previously this was not possible, and you would get an -EPERM. The mount
> is the *host* mqueue mount, which is being cached and just returned from
> mqueue_mount(). To be honest, I'm not sure if this is safe or not (or if
> it was intentional -- since I'm not familiar with mqueue).
>
> To me it looks like there is a missing permission check. I've included a
> patch below that I've compile-tested, and should block the above case.
> Can someone please tell me if I'm missing something? Is this actually
> safe?
>
> [1]: https://github.com/docker/docker/issues/36674

The issue is a lot deeper than a missing permission check.  sb->s_user_ns
was is improperly set as well.  So in addition to the filesystem being
mounted when it should not be mounted, so things are not allow that should
be.

We are practically to the release of 4.16 and there is no agreement between
Al Viro and myself on what the code should looks like to fix things properly.
So revert the code to what it was before so that we can take our time
and discuss this properly.

Fixes: 36735a6a ("mqueue: switch to on-demand creation of internal mount")
Reported-by: default avatarFelix Abecassis <fabecassis@nvidia.com>
Reported-by: default avatarAleksa Sarai <asarai@suse.de>
Signed-off-by: default avatar"Eric W. Biederman" <ebiederm@xmission.com>
parent 91ab883e
...@@ -325,9 +325,8 @@ static struct inode *mqueue_get_inode(struct super_block *sb, ...@@ -325,9 +325,8 @@ static struct inode *mqueue_get_inode(struct super_block *sb,
static int mqueue_fill_super(struct super_block *sb, void *data, int silent) static int mqueue_fill_super(struct super_block *sb, void *data, int silent)
{ {
struct inode *inode; struct inode *inode;
struct ipc_namespace *ns = data; struct ipc_namespace *ns = sb->s_fs_info;
sb->s_fs_info = ns;
sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
sb->s_blocksize = PAGE_SIZE; sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT; sb->s_blocksize_bits = PAGE_SHIFT;
...@@ -344,44 +343,18 @@ static int mqueue_fill_super(struct super_block *sb, void *data, int silent) ...@@ -344,44 +343,18 @@ static int mqueue_fill_super(struct super_block *sb, void *data, int silent)
return 0; return 0;
} }
static struct file_system_type mqueue_fs_type;
/*
* Return value is pinned only by reference in ->mq_mnt; it will
* live until ipcns dies. Caller does not need to drop it.
*/
static struct vfsmount *mq_internal_mount(void)
{
struct ipc_namespace *ns = current->nsproxy->ipc_ns;
struct vfsmount *m = ns->mq_mnt;
if (m)
return m;
m = kern_mount_data(&mqueue_fs_type, ns);
spin_lock(&mq_lock);
if (unlikely(ns->mq_mnt)) {
spin_unlock(&mq_lock);
if (!IS_ERR(m))
kern_unmount(m);
return ns->mq_mnt;
}
if (!IS_ERR(m))
ns->mq_mnt = m;
spin_unlock(&mq_lock);
return m;
}
static struct dentry *mqueue_mount(struct file_system_type *fs_type, static struct dentry *mqueue_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, int flags, const char *dev_name,
void *data) void *data)
{ {
struct vfsmount *m; struct ipc_namespace *ns;
if (flags & SB_KERNMOUNT) if (flags & SB_KERNMOUNT) {
return mount_nodev(fs_type, flags, data, mqueue_fill_super); ns = data;
m = mq_internal_mount(); data = NULL;
if (IS_ERR(m)) } else {
return ERR_CAST(m); ns = current->nsproxy->ipc_ns;
atomic_inc(&m->mnt_sb->s_active); }
down_write(&m->mnt_sb->s_umount); return mount_ns(fs_type, flags, data, ns, ns->user_ns, mqueue_fill_super);
return dget(m->mnt_root);
} }
static void init_once(void *foo) static void init_once(void *foo)
...@@ -771,16 +744,13 @@ static int prepare_open(struct dentry *dentry, int oflag, int ro, ...@@ -771,16 +744,13 @@ static int prepare_open(struct dentry *dentry, int oflag, int ro,
static int do_mq_open(const char __user *u_name, int oflag, umode_t mode, static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
struct mq_attr *attr) struct mq_attr *attr)
{ {
struct vfsmount *mnt = mq_internal_mount(); struct vfsmount *mnt = current->nsproxy->ipc_ns->mq_mnt;
struct dentry *root; struct dentry *root = mnt->mnt_root;
struct filename *name; struct filename *name;
struct path path; struct path path;
int fd, error; int fd, error;
int ro; int ro;
if (IS_ERR(mnt))
return PTR_ERR(mnt);
audit_mq_open(oflag, mode, attr); audit_mq_open(oflag, mode, attr);
if (IS_ERR(name = getname(u_name))) if (IS_ERR(name = getname(u_name)))
...@@ -791,7 +761,6 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode, ...@@ -791,7 +761,6 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
goto out_putname; goto out_putname;
ro = mnt_want_write(mnt); /* we'll drop it in any case */ ro = mnt_want_write(mnt); /* we'll drop it in any case */
root = mnt->mnt_root;
inode_lock(d_inode(root)); inode_lock(d_inode(root));
path.dentry = lookup_one_len(name->name, root, strlen(name->name)); path.dentry = lookup_one_len(name->name, root, strlen(name->name));
if (IS_ERR(path.dentry)) { if (IS_ERR(path.dentry)) {
...@@ -840,9 +809,6 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name) ...@@ -840,9 +809,6 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns; struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
struct vfsmount *mnt = ipc_ns->mq_mnt; struct vfsmount *mnt = ipc_ns->mq_mnt;
if (!mnt)
return -ENOENT;
name = getname(u_name); name = getname(u_name);
if (IS_ERR(name)) if (IS_ERR(name))
return PTR_ERR(name); return PTR_ERR(name);
...@@ -1569,26 +1535,28 @@ int mq_init_ns(struct ipc_namespace *ns) ...@@ -1569,26 +1535,28 @@ int mq_init_ns(struct ipc_namespace *ns)
ns->mq_msgsize_max = DFLT_MSGSIZEMAX; ns->mq_msgsize_max = DFLT_MSGSIZEMAX;
ns->mq_msg_default = DFLT_MSG; ns->mq_msg_default = DFLT_MSG;
ns->mq_msgsize_default = DFLT_MSGSIZE; ns->mq_msgsize_default = DFLT_MSGSIZE;
ns->mq_mnt = NULL;
ns->mq_mnt = kern_mount_data(&mqueue_fs_type, ns);
if (IS_ERR(ns->mq_mnt)) {
int err = PTR_ERR(ns->mq_mnt);
ns->mq_mnt = NULL;
return err;
}
return 0; return 0;
} }
void mq_clear_sbinfo(struct ipc_namespace *ns) void mq_clear_sbinfo(struct ipc_namespace *ns)
{ {
if (ns->mq_mnt) ns->mq_mnt->mnt_sb->s_fs_info = NULL;
ns->mq_mnt->mnt_sb->s_fs_info = NULL;
} }
void mq_put_mnt(struct ipc_namespace *ns) void mq_put_mnt(struct ipc_namespace *ns)
{ {
if (ns->mq_mnt) kern_unmount(ns->mq_mnt);
kern_unmount(ns->mq_mnt);
} }
static int __init init_mqueue_fs(void) static int __init init_mqueue_fs(void)
{ {
struct vfsmount *m;
int error; int error;
mqueue_inode_cachep = kmem_cache_create("mqueue_inode_cache", mqueue_inode_cachep = kmem_cache_create("mqueue_inode_cache",
...@@ -1610,10 +1578,6 @@ static int __init init_mqueue_fs(void) ...@@ -1610,10 +1578,6 @@ static int __init init_mqueue_fs(void)
if (error) if (error)
goto out_filesystem; goto out_filesystem;
m = kern_mount_data(&mqueue_fs_type, &init_ipc_ns);
if (IS_ERR(m))
goto out_filesystem;
init_ipc_ns.mq_mnt = m;
return 0; return 0;
out_filesystem: out_filesystem:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment