diff options
Diffstat (limited to 'fs/namei.c')
-rw-r--r-- | fs/namei.c | 32 |
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; |