CVE-2013-1957: Permissions, Privileges, and Access Controls (CWE-264)
value: The clone_mnt function in fs/namespace.c in the Linux kernel before 3.8.6 does not properly restrict changes to the MNT_READONLY flag, which allows local users to bypass an intended read-only property of a filesystem by leveraging a separate mount namespace.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Published: 2013-04-24T19:55Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/namespace.c, fs/pnode.c, fs/pnode.h
Commit date: 2013-03-27 07:50:05 -0700
Commit message: vfs: Carefully propogate mounts across user namespaces

As a matter of policy MNT_READONLY should not be changable if the
original mounter had more privileges than creator of the mount
namespace.

Add the flag CL_UNPRIVILEGED to note when we are copying a mount from
a mount namespace that requires more privileges to a mount namespace
that requires fewer privileges.

When the CL_UNPRIVILEGED flag is set cause clone_mnt to set MNT_NO_REMOUNT
if any of the mnt flags that should never be changed are set.

This protects both mount propagation and the initial creation of a less
privileged mount namespace.

Cc: stable@vger.kernel.org
Acked-by: Serge Hallyn <serge.hallyn@canonical.com>
Reported-by: Andy Lutomirski <luto@amacapital.net>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>

Diff: diff --git a/fs/namespace.c b/fs/namespace.c
index 8505b5ece5de98..968d4c5eae03aa 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -796,10 +796,14 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
 		if (err)
 			goto out_free;
 	}
 
 	mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~MNT_WRITE_HOLD;
+	/* Don't allow unprivileged users to change mount flags */
+	if ((flag & CL_UNPRIVILEGED) && (mnt->mnt.mnt_flags & MNT_READONLY))
+		mnt->mnt.mnt_flags |= MNT_LOCK_READONLY;
+
 	atomic_inc(&sb->s_active);
 	mnt->mnt.mnt_sb = sb;
 	mnt->mnt.mnt_root = dget(root);
 	mnt->mnt_mountpoint = mnt->mnt.mnt_root;
 	mnt->mnt_parent = mnt;
@@ -2340,11 +2344,11 @@ static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
 
 	down_write(&namespace_sem);
 	/* First pass: copy the tree topology */
 	copy_flags = CL_COPY_ALL | CL_EXPIRE;
 	if (user_ns != mnt_ns->user_ns)
-		copy_flags |= CL_SHARED_TO_SLAVE;
+		copy_flags |= CL_SHARED_TO_SLAVE | CL_UNPRIVILEGED;
 	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
 	if (IS_ERR(new)) {
 		up_write(&namespace_sem);
 		free_mnt_ns(new_ns);
 		return ERR_CAST(new);
diff --git a/fs/pnode.c b/fs/pnode.c
index 3e000a51ac0d09..8b29d2164da6ae 100644
--- a/fs/pnode.c
+++ b/fs/pnode.c
@@ -7,10 +7,11 @@
  *
  */
 #include <linux/mnt_namespace.h>
 #include <linux/mount.h>
 #include <linux/fs.h>
+#include <linux/nsproxy.h>
 #include "internal.h"
 #include "pnode.h"
 
 /* return the next shared peer mount of @p */
 static inline struct mount *next_peer(struct mount *p)
@@ -218,10 +219,11 @@ static struct mount *get_source(struct mount *dest,
  * @tree_list : list of heads of trees to be attached.
  */
 int propagate_mnt(struct mount *dest_mnt, struct dentry *dest_dentry,
 		    struct mount *source_mnt, struct list_head *tree_list)
 {
+	struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
 	struct mount *m, *child;
 	int ret = 0;
 	struct mount *prev_dest_mnt = dest_mnt;
 	struct mount *prev_src_mnt  = source_mnt;
 	LIST_HEAD(tmp_list);
@@ -235,10 +237,14 @@ int propagate_mnt(struct mount *dest_mnt, struct dentry *dest_dentry,
 		if (IS_MNT_NEW(m))
 			continue;
 
 		source =  get_source(m, prev_dest_mnt, prev_src_mnt, &type);
 
+		/* Notice when we are propagating across user namespaces */
+		if (m->mnt_ns->user_ns != user_ns)
+			type |= CL_UNPRIVILEGED;
+
 		child = copy_tree(source, source->mnt.mnt_root, type);
 		if (IS_ERR(child)) {
 			ret = PTR_ERR(child);
 			list_splice(tree_list, tmp_list.prev);
 			goto out;
diff --git a/fs/pnode.h b/fs/pnode.h
index 19b853a3445cb9..a0493d5ebfbf52 100644
--- a/fs/pnode.h
+++ b/fs/pnode.h
@@ -21,10 +21,11 @@
 #define CL_SLAVE     		0x02
 #define CL_COPY_ALL 		0x04
 #define CL_MAKE_SHARED 		0x08
 #define CL_PRIVATE 		0x10
 #define CL_SHARED_TO_SLAVE	0x20
+#define CL_UNPRIVILEGED		0x40
 
 static inline void set_mnt_shared(struct mount *mnt)
 {
 	mnt->mnt.mnt_flags &= ~MNT_SHARED_MASK;
 	mnt->mnt.mnt_flags |= MNT_SHARED;

Vulnerable code: 		copy_flags |= CL_SHARED_TO_SLAVE;
Fixed code: 	/* Don't allow unprivileged users to change mount flags */
	if ((flag & CL_UNPRIVILEGED) && (mnt->mnt.mnt_flags & MNT_READONLY))
		mnt->mnt.mnt_flags |= MNT_LOCK_READONLY;

		copy_flags |= CL_SHARED_TO_SLAVE | CL_UNPRIVILEGED;
#include <linux/nsproxy.h>
	struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
		/* Notice when we are propagating across user namespaces */
		if (m->mnt_ns->user_ns != user_ns)
			type |= CL_UNPRIVILEGED;

#define CL_UNPRIVILEGED		0x40