]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - fs/namei.c
Merge remote-tracking branch 'trivial/for-next'
[karo-tx-linux.git] / fs / namei.c
index d3f84069a9aa82d25dae0f7b07b2c696a1d91d07..e5daa6f65c6e894aaeba36a2533599f4ee3fb3b7 100644 (file)
@@ -560,6 +560,24 @@ static int __nd_alloc_stack(struct nameidata *nd)
        return 0;
 }
 
+/**
+ * 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);
+}
+
 static inline int nd_alloc_stack(struct nameidata *nd)
 {
        if (likely(nd->depth != EMBEDDED_LEVELS))
@@ -879,7 +897,7 @@ static inline int may_follow_link(struct nameidata *nd)
                return 0;
 
        /* Allowed if parent directory not sticky and world-writable. */
-       parent = nd->path.dentry->d_inode;
+       parent = nd->inode;
        if ((parent->i_mode & (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH))
                return 0;
 
@@ -1296,6 +1314,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                                return -ECHILD;
                        nd->path.dentry = parent;
                        nd->seq = seq;
+                       if (unlikely(!path_connected(&nd->path)))
+                               return -ENOENT;
                        break;
                } else {
                        struct mount *mnt = real_mount(nd->path.mnt);
@@ -1396,7 +1416,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);
@@ -1412,6 +1432,8 @@ 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)))
+                               return -ENOENT;
                        break;
                }
                if (!follow_up(&nd->path))
@@ -1419,6 +1441,7 @@ static void follow_dotdot(struct nameidata *nd)
        }
        follow_mount(&nd->path);
        nd->inode = nd->path.dentry->d_inode;
+       return 0;
 }
 
 /*
@@ -1535,8 +1558,6 @@ static int lookup_fast(struct nameidata *nd,
                negative = d_is_negative(dentry);
                if (read_seqcount_retry(&dentry->d_seq, seq))
                        return -ECHILD;
-               if (negative)
-                       return -ENOENT;
 
                /*
                 * This sequence count validates that the parent had no
@@ -1557,6 +1578,12 @@ static int lookup_fast(struct nameidata *nd,
                                goto unlazy;
                        }
                }
+               /*
+                * Note: do negative dentry check after revalidation in
+                * case that drops it.
+                */
+               if (negative)
+                       return -ENOENT;
                path->mnt = mnt;
                path->dentry = dentry;
                if (likely(__follow_mount_rcu(nd, path, inode, seqp)))
@@ -1634,7 +1661,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
                if (nd->flags & LOOKUP_RCU) {
                        return follow_dotdot_rcu(nd);
                } else
-                       follow_dotdot(nd);
+                       return follow_dotdot(nd);
        }
        return 0;
 }
@@ -2255,6 +2282,8 @@ EXPORT_SYMBOL(vfs_path_lookup);
  *
  * Note that this routine is purely a helper for filesystem usage and should
  * not be called by generic code.
+ *
+ * The caller must hold base->i_mutex.
  */
 struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
 {
@@ -2298,6 +2327,78 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
 }
 EXPORT_SYMBOL(lookup_one_len);
 
+/**
+ * lookup_one_len_unlocked - filesystem helper to lookup single pathname component
+ * @name:      pathname component to lookup
+ * @base:      base directory to lookup from
+ * @len:       maximum length @len should be interpreted to
+ *
+ * Note that this routine is purely a helper for filesystem usage and should
+ * not be called by generic code.
+ *
+ * Unlike lookup_one_len, it should be called without the parent
+ * i_mutex held, and will take the i_mutex itself if necessary.
+ */
+struct dentry *lookup_one_len_unlocked(const char *name,
+                                      struct dentry *base, int len)
+{
+       struct qstr this;
+       unsigned int c;
+       int err;
+       struct dentry *ret;
+
+       this.name = name;
+       this.len = len;
+       this.hash = full_name_hash(name, len);
+       if (!len)
+               return ERR_PTR(-EACCES);
+
+       if (unlikely(name[0] == '.')) {
+               if (len < 2 || (len == 2 && name[1] == '.'))
+                       return ERR_PTR(-EACCES);
+       }
+
+       while (len--) {
+               c = *(const unsigned char *)name++;
+               if (c == '/' || c == '\0')
+                       return ERR_PTR(-EACCES);
+       }
+       /*
+        * See if the low-level filesystem might want
+        * to use its own hash..
+        */
+       if (base->d_flags & DCACHE_OP_HASH) {
+               int err = base->d_op->d_hash(base, &this);
+               if (err < 0)
+                       return ERR_PTR(err);
+       }
+
+       err = inode_permission(base->d_inode, MAY_EXEC);
+       if (err)
+               return ERR_PTR(err);
+
+       ret = __d_lookup(base, &this);
+       if (ret)
+               return ret;
+       /*
+        * __d_lookup() is used to try to get a quick answer and avoid the
+        * mutex.  A false-negative does no harm.
+        */
+       ret = __d_lookup(base, &this);
+       if (ret && ret->d_flags & DCACHE_OP_REVALIDATE) {
+               dput(ret);
+               ret = NULL;
+       }
+       if (ret)
+               return ret;
+
+       mutex_lock(&base->d_inode->i_mutex);
+       ret =  __lookup_hash(&this, base, 0);
+       mutex_unlock(&base->d_inode->i_mutex);
+       return ret;
+}
+EXPORT_SYMBOL(lookup_one_len_unlocked);
+
 int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
                 struct path *path, int *empty)
 {
@@ -2415,7 +2516,7 @@ done:
 
 /**
  * path_mountpoint - look up a path to be umounted
- * @nameidata: lookup context
+ * @nd:                lookup context
  * @flags:     lookup flags
  * @path:      pointer to container for result
  *