From: Miklos Szeredi <miklos@szeredi.hu>

http://bugzilla.kernel.org/show_bug.cgi?id=4857

When pivot_root is called from an init script in an initramfs environment,
it causes a circular reference in the mount tree.

The cause of this is that pivot_root() is not prepared to handle
pivoting an unattached mount.  In an initramfs environment, rootfs is
the root of the namespace, and so it is not attached.

This patch fixes this and related problems:

  - if current root is unattached AND is not the root of the
    namespace, then return -EINVAL

  - if new root is unattached, then return -EINVAL

  - if current root is the root of the namespace, then don't detach
    it, also instead of attaching new root, set namespace->root to it

Mildly tested under UML.

Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
Cc: <bigfish@asmallpond.org>
Cc: <hch@lst.de>
Cc: <viro@parcelfarce.linux.theplanet.co.uk>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 fs/namespace.c |   18 +++++++++++++++---
 1 files changed, 15 insertions(+), 3 deletions(-)

diff -puN fs/namespace.c~pivot_root-circular-reference-fix-2 fs/namespace.c
--- devel/fs/namespace.c~pivot_root-circular-reference-fix-2	2005-07-12 15:12:22.000000000 -0700
+++ devel-akpm/fs/namespace.c	2005-07-12 15:12:22.000000000 -0700
@@ -1289,6 +1289,7 @@ asmlinkage long sys_pivot_root(const cha
 	struct vfsmount *tmp;
 	struct nameidata new_nd, old_nd, parent_nd, root_parent, user_nd;
 	int error;
+	int is_nsroot = 0;
 
 	if (!capable(CAP_SYS_ADMIN))
 		return -EPERM;
@@ -1321,6 +1322,8 @@ asmlinkage long sys_pivot_root(const cha
 	error = -EINVAL;
 	if (!check_mnt(user_nd.mnt))
 		goto out2;
+	if (user_nd.mnt == current->namespace->root)
+		is_nsroot = 1;
 	error = -ENOENT;
 	if (IS_DEADDIR(new_nd.dentry->d_inode))
 		goto out2;
@@ -1334,8 +1337,12 @@ asmlinkage long sys_pivot_root(const cha
 	error = -EINVAL;
 	if (user_nd.mnt->mnt_root != user_nd.dentry)
 		goto out2; /* not a mountpoint */
+	if (user_nd.mnt->mnt_parent == user_nd.mnt && !is_nsroot)
+		goto out2; /* not attached and not namespace root */
 	if (new_nd.mnt->mnt_root != new_nd.dentry)
 		goto out2; /* not a mountpoint */
+	if (new_nd.mnt->mnt_parent == new_nd.mnt)
+		goto out2; /* not attached */
 	tmp = old_nd.mnt; /* make sure we can reach put_old from new_root */
 	spin_lock(&vfsmount_lock);
 	if (tmp != new_nd.mnt) {
@@ -1351,14 +1358,19 @@ asmlinkage long sys_pivot_root(const cha
 	} else if (!is_subdir(old_nd.dentry, new_nd.dentry))
 		goto out3;
 	detach_mnt(new_nd.mnt, &parent_nd);
-	detach_mnt(user_nd.mnt, &root_parent);
+	if (!is_nsroot)
+		detach_mnt(user_nd.mnt, &root_parent);
 	attach_mnt(user_nd.mnt, &old_nd);     /* mount old root on put_old */
-	attach_mnt(new_nd.mnt, &root_parent); /* mount new_root on / */
+	if (is_nsroot)
+		current->namespace->root = new_nd.mnt; /* set new root */
+	else
+		attach_mnt(new_nd.mnt, &root_parent); /* mount new_root on / */
 	spin_unlock(&vfsmount_lock);
 	chroot_fs_refs(&user_nd, &new_nd);
 	security_sb_post_pivotroot(&user_nd, &new_nd);
 	error = 0;
-	path_release(&root_parent);
+	if (!is_nsroot)
+		path_release(&root_parent);
 	path_release(&parent_nd);
 out2:
 	up(&old_nd.dentry->d_inode->i_sem);
_