summaryrefslogtreecommitdiff
path: root/fs/namei.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/namei.c')
-rw-r--r--fs/namei.c32
1 files changed, 29 insertions, 3 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 0d766d201200..6551acba2a2c 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -434,6 +434,24 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name,
return dentry;
}
+/**
+ * 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);
+}
+
/*
* Short-cut version of permission(), for calling by
* path_walk(), when dcache lock is held. Combines parts
@@ -754,7 +772,7 @@ int follow_down(struct path *path)
return 0;
}
-static __always_inline void follow_dotdot(struct nameidata *nd)
+static __always_inline int follow_dotdot(struct nameidata *nd)
{
set_root(nd);
@@ -771,6 +789,8 @@ static __always_inline void follow_dotdot(struct nameidata *nd)
nd->path.dentry = dget(nd->path.dentry->d_parent);
spin_unlock(&dcache_lock);
dput(old);
+ if (unlikely(!path_connected(&nd->path)))
+ return -ENOENT;
break;
}
spin_unlock(&dcache_lock);
@@ -788,6 +808,7 @@ static __always_inline void follow_dotdot(struct nameidata *nd)
nd->path.mnt = parent;
}
follow_mount(&nd->path);
+ return 0;
}
/*
@@ -905,7 +926,9 @@ static int __link_path_walk(const char *name, struct nameidata *nd)
case 2:
if (this.name[1] != '.')
break;
- follow_dotdot(nd);
+ err = follow_dotdot(nd);
+ if (err < 0)
+ goto out_nd_path_put;
inode = nd->path.dentry->d_inode;
/* fallthrough */
case 1:
@@ -960,7 +983,9 @@ last_component:
case 2:
if (this.name[1] != '.')
break;
- follow_dotdot(nd);
+ err = follow_dotdot(nd);
+ if (err < 0)
+ goto out_nd_path_put;
inode = nd->path.dentry->d_inode;
/* fallthrough */
case 1:
@@ -1022,6 +1047,7 @@ out_dput:
path_put_conditional(&next, nd);
break;
}
+out_nd_path_put:
path_put(&nd->path);
return_err:
return err;