vfs: Test for and handle paths that are unreachable from their mnt_root
[pandora-kernel.git] / fs / namei.c
index c8b13a9..2c22655 100644 (file)
@@ -398,6 +398,24 @@ void path_put(struct path *path)
 }
 EXPORT_SYMBOL(path_put);
 
+/**
+ * path_connected - Verify that a path->dentry is below path->mnt.mnt_root
+ * @path: nameidate to verify
+ *
+ * Rename can sometimes move a file or directory outside of a bind
+ * mount, path_connected allows those cases to be detected.
+ */
+static bool path_connected(const struct path *path)
+{
+       struct vfsmount *mnt = path->mnt;
+
+       /* Only bind mounts can have disconnected paths */
+       if (mnt->mnt_root == mnt->mnt_sb->s_root)
+               return true;
+
+       return is_subdir(path->dentry, mnt->mnt_root);
+}
+
 /*
  * Path walking has 2 modes, rcu-walk and ref-walk (see
  * Documentation/filesystems/path-lookup.txt).  In situations when we can't
@@ -933,6 +951,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                                goto failed;
                        nd->path.dentry = parent;
                        nd->seq = seq;
+                       if (unlikely(!path_connected(&nd->path)))
+                               goto failed;
                        break;
                }
                if (!follow_up_rcu(&nd->path))
@@ -1027,7 +1047,7 @@ static void follow_mount(struct path *path)
        }
 }
 
-static void follow_dotdot(struct nameidata *nd)
+static int follow_dotdot(struct nameidata *nd)
 {
        if (!nd->root.mnt)
                set_root(nd);
@@ -1043,6 +1063,10 @@ static void follow_dotdot(struct nameidata *nd)
                        /* rare case of legitimate dget_parent()... */
                        nd->path.dentry = dget_parent(nd->path.dentry);
                        dput(old);
+                       if (unlikely(!path_connected(&nd->path))) {
+                               path_put(&nd->path);
+                               return -ENOENT;
+                       }
                        break;
                }
                if (!follow_up(&nd->path))
@@ -1050,6 +1074,7 @@ static void follow_dotdot(struct nameidata *nd)
        }
        follow_mount(&nd->path);
        nd->inode = nd->path.dentry->d_inode;
+       return 0;
 }
 
 /*
@@ -1241,7 +1266,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
                        if (follow_dotdot_rcu(nd))
                                return -ECHILD;
                } else
-                       follow_dotdot(nd);
+                       return follow_dotdot(nd);
        }
        return 0;
 }