]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - fs/namei.c
Merge tag 'arm64-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux
[linux.git] / fs / namei.c
index d2720dc71d0efc111c1c2da0f2d71acf5c39c39d..db6565c998259a12854d2317ce2f8f1d933f0277 100644 (file)
@@ -491,7 +491,7 @@ struct nameidata {
        struct path     root;
        struct inode    *inode; /* path.dentry.d_inode */
        unsigned int    flags;
-       unsigned        seq, m_seq;
+       unsigned        seq, m_seq, r_seq;
        int             last_type;
        unsigned        depth;
        int             total_link_count;
@@ -641,6 +641,14 @@ static bool legitimize_links(struct nameidata *nd)
 
 static bool legitimize_root(struct nameidata *nd)
 {
+       /*
+        * For scoped-lookups (where nd->root has been zeroed), we need to
+        * restart the whole lookup from scratch -- because set_root() is wrong
+        * for these lookups (nd->dfd is the root, not the filesystem root).
+        */
+       if (!nd->root.mnt && (nd->flags & LOOKUP_IS_SCOPED))
+               return false;
+       /* Nothing to do if nd->root is zero or is managed by the VFS user. */
        if (!nd->root.mnt || (nd->flags & LOOKUP_ROOT))
                return true;
        nd->flags |= LOOKUP_ROOT_GRABBED;
@@ -776,12 +784,37 @@ static int complete_walk(struct nameidata *nd)
        int status;
 
        if (nd->flags & LOOKUP_RCU) {
-               if (!(nd->flags & LOOKUP_ROOT))
+               /*
+                * We don't want to zero nd->root for scoped-lookups or
+                * externally-managed nd->root.
+                */
+               if (!(nd->flags & (LOOKUP_ROOT | LOOKUP_IS_SCOPED)))
                        nd->root.mnt = NULL;
                if (unlikely(unlazy_walk(nd)))
                        return -ECHILD;
        }
 
+       if (unlikely(nd->flags & LOOKUP_IS_SCOPED)) {
+               /*
+                * While the guarantee of LOOKUP_IS_SCOPED is (roughly) "don't
+                * ever step outside the root during lookup" and should already
+                * be guaranteed by the rest of namei, we want to avoid a namei
+                * BUG resulting in userspace being given a path that was not
+                * scoped within the root at some point during the lookup.
+                *
+                * So, do a final sanity-check to make sure that in the
+                * worst-case scenario (a complete bypass of LOOKUP_IS_SCOPED)
+                * we won't silently return an fd completely outside of the
+                * requested root to userspace.
+                *
+                * Userspace could move the path outside the root after this
+                * check, but as discussed elsewhere this is not a concern (the
+                * resolved file was inside the root at some point).
+                */
+               if (!path_is_under(&nd->path, &nd->root))
+                       return -EXDEV;
+       }
+
        if (likely(!(nd->flags & LOOKUP_JUMPED)))
                return 0;
 
@@ -798,10 +831,18 @@ static int complete_walk(struct nameidata *nd)
        return status;
 }
 
-static void set_root(struct nameidata *nd)
+static int set_root(struct nameidata *nd)
 {
        struct fs_struct *fs = current->fs;
 
+       /*
+        * Jumping to the real root in a scoped-lookup is a BUG in namei, but we
+        * still have to ensure it doesn't happen because it will cause a breakout
+        * from the dirfd.
+        */
+       if (WARN_ON(nd->flags & LOOKUP_IS_SCOPED))
+               return -ENOTRECOVERABLE;
+
        if (nd->flags & LOOKUP_RCU) {
                unsigned seq;
 
@@ -814,6 +855,7 @@ static void set_root(struct nameidata *nd)
                get_fs_root(fs, &nd->root);
                nd->flags |= LOOKUP_ROOT_GRABBED;
        }
+       return 0;
 }
 
 static void path_put_conditional(struct path *path, struct nameidata *nd)
@@ -837,6 +879,18 @@ static inline void path_to_nameidata(const struct path *path,
 
 static int nd_jump_root(struct nameidata *nd)
 {
+       if (unlikely(nd->flags & LOOKUP_BENEATH))
+               return -EXDEV;
+       if (unlikely(nd->flags & LOOKUP_NO_XDEV)) {
+               /* Absolute path arguments to path_init() are allowed. */
+               if (nd->path.mnt != NULL && nd->path.mnt != nd->root.mnt)
+                       return -EXDEV;
+       }
+       if (!nd->root.mnt) {
+               int error = set_root(nd);
+               if (error)
+                       return error;
+       }
        if (nd->flags & LOOKUP_RCU) {
                struct dentry *d;
                nd->path = nd->root;
@@ -859,14 +913,32 @@ static int nd_jump_root(struct nameidata *nd)
  * Helper to directly jump to a known parsed path from ->get_link,
  * caller must have taken a reference to path beforehand.
  */
-void nd_jump_link(struct path *path)
+int nd_jump_link(struct path *path)
 {
+       int error = -ELOOP;
        struct nameidata *nd = current->nameidata;
-       path_put(&nd->path);
 
+       if (unlikely(nd->flags & LOOKUP_NO_MAGICLINKS))
+               goto err;
+
+       error = -EXDEV;
+       if (unlikely(nd->flags & LOOKUP_NO_XDEV)) {
+               if (nd->path.mnt != path->mnt)
+                       goto err;
+       }
+       /* Not currently safe for scoped-lookups. */
+       if (unlikely(nd->flags & LOOKUP_IS_SCOPED))
+               goto err;
+
+       path_put(&nd->path);
        nd->path = *path;
        nd->inode = nd->path.dentry->d_inode;
        nd->flags |= LOOKUP_JUMPED;
+       return 0;
+
+err:
+       path_put(path);
+       return error;
 }
 
 static inline void put_link(struct nameidata *nd)
@@ -1001,7 +1073,8 @@ static int may_linkat(struct path *link)
  * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory
  *                       should be allowed, or not, on files that already
  *                       exist.
- * @dir: the sticky parent directory
+ * @dir_mode: mode bits of directory
+ * @dir_uid: owner of directory
  * @inode: the inode of the file to open
  *
  * Block an O_CREAT open of a FIFO (or a regular file) when:
@@ -1017,18 +1090,18 @@ static int may_linkat(struct path *link)
  *
  * Returns 0 if the open is allowed, -ve on error.
  */
-static int may_create_in_sticky(struct dentry * const dir,
+static int may_create_in_sticky(umode_t dir_mode, kuid_t dir_uid,
                                struct inode * const inode)
 {
        if ((!sysctl_protected_fifos && S_ISFIFO(inode->i_mode)) ||
            (!sysctl_protected_regular && S_ISREG(inode->i_mode)) ||
-           likely(!(dir->d_inode->i_mode & S_ISVTX)) ||
-           uid_eq(inode->i_uid, dir->d_inode->i_uid) ||
+           likely(!(dir_mode & S_ISVTX)) ||
+           uid_eq(inode->i_uid, dir_uid) ||
            uid_eq(current_fsuid(), inode->i_uid))
                return 0;
 
-       if (likely(dir->d_inode->i_mode & 0002) ||
-           (dir->d_inode->i_mode & 0020 &&
+       if (likely(dir_mode & 0002) ||
+           (dir_mode & 0020 &&
             ((sysctl_protected_fifos >= 2 && S_ISFIFO(inode->i_mode)) ||
              (sysctl_protected_regular >= 2 && S_ISREG(inode->i_mode))))) {
                const char *operation = S_ISFIFO(inode->i_mode) ?
@@ -1049,6 +1122,9 @@ const char *get_link(struct nameidata *nd)
        int error;
        const char *res;
 
+       if (unlikely(nd->flags & LOOKUP_NO_SYMLINKS))
+               return ERR_PTR(-ELOOP);
+
        if (!(nd->flags & LOOKUP_RCU)) {
                touch_atime(&last->link);
                cond_resched();
@@ -1083,10 +1159,9 @@ const char *get_link(struct nameidata *nd)
                        return res;
        }
        if (*res == '/') {
-               if (!nd->root.mnt)
-                       set_root(nd);
-               if (unlikely(nd_jump_root(nd)))
-                       return ERR_PTR(-ECHILD);
+               error = nd_jump_root(nd);
+               if (unlikely(error))
+                       return ERR_PTR(error);
                while (unlikely(*++res == '/'))
                        ;
        }
@@ -1268,10 +1343,14 @@ static int follow_managed(struct path *path, struct nameidata *nd)
                break;
        }
 
-       if (need_mntput && path->mnt == mnt)
-               mntput(path->mnt);
-       if (need_mntput)
-               nd->flags |= LOOKUP_JUMPED;
+       if (need_mntput) {
+               if (path->mnt == mnt)
+                       mntput(path->mnt);
+               if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+                       ret = -EXDEV;
+               else
+                       nd->flags |= LOOKUP_JUMPED;
+       }
        if (ret == -EISDIR || !ret)
                ret = 1;
        if (ret > 0 && unlikely(d_flags_negative(flags)))
@@ -1332,6 +1411,8 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
                mounted = __lookup_mnt(path->mnt, path->dentry);
                if (!mounted)
                        break;
+               if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+                       return false;
                path->mnt = &mounted->mnt;
                path->dentry = mounted->mnt.mnt_root;
                nd->flags |= LOOKUP_JUMPED;
@@ -1352,8 +1433,11 @@ static int follow_dotdot_rcu(struct nameidata *nd)
        struct inode *inode = nd->inode;
 
        while (1) {
-               if (path_equal(&nd->path, &nd->root))
+               if (path_equal(&nd->path, &nd->root)) {
+                       if (unlikely(nd->flags & LOOKUP_BENEATH))
+                               return -ECHILD;
                        break;
+               }
                if (nd->path.dentry != nd->path.mnt->mnt_root) {
                        struct dentry *old = nd->path.dentry;
                        struct dentry *parent = old->d_parent;
@@ -1366,7 +1450,7 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                        nd->path.dentry = parent;
                        nd->seq = seq;
                        if (unlikely(!path_connected(&nd->path)))
-                               return -ENOENT;
+                               return -ECHILD;
                        break;
                } else {
                        struct mount *mnt = real_mount(nd->path.mnt);
@@ -1378,6 +1462,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                                return -ECHILD;
                        if (&mparent->mnt == nd->path.mnt)
                                break;
+                       if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+                               return -ECHILD;
                        /* we know that mountpoint was pinned */
                        nd->path.dentry = mountpoint;
                        nd->path.mnt = &mparent->mnt;
@@ -1392,6 +1478,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                        return -ECHILD;
                if (!mounted)
                        break;
+               if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+                       return -ECHILD;
                nd->path.mnt = &mounted->mnt;
                nd->path.dentry = mounted->mnt.mnt_root;
                inode = nd->path.dentry->d_inode;
@@ -1479,9 +1567,12 @@ static int path_parent_directory(struct path *path)
 
 static int follow_dotdot(struct nameidata *nd)
 {
-       while(1) {
-               if (path_equal(&nd->path, &nd->root))
+       while (1) {
+               if (path_equal(&nd->path, &nd->root)) {
+                       if (unlikely(nd->flags & LOOKUP_BENEATH))
+                               return -EXDEV;
                        break;
+               }
                if (nd->path.dentry != nd->path.mnt->mnt_root) {
                        int ret = path_parent_directory(&nd->path);
                        if (ret)
@@ -1490,6 +1581,8 @@ static int follow_dotdot(struct nameidata *nd)
                }
                if (!follow_up(&nd->path))
                        break;
+               if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+                       return -EXDEV;
        }
        follow_mount(&nd->path);
        nd->inode = nd->path.dentry->d_inode;
@@ -1698,12 +1791,33 @@ static inline int may_lookup(struct nameidata *nd)
 static inline int handle_dots(struct nameidata *nd, int type)
 {
        if (type == LAST_DOTDOT) {
-               if (!nd->root.mnt)
-                       set_root(nd);
-               if (nd->flags & LOOKUP_RCU) {
-                       return follow_dotdot_rcu(nd);
-               } else
-                       return follow_dotdot(nd);
+               int error = 0;
+
+               if (!nd->root.mnt) {
+                       error = set_root(nd);
+                       if (error)
+                               return error;
+               }
+               if (nd->flags & LOOKUP_RCU)
+                       error = follow_dotdot_rcu(nd);
+               else
+                       error = follow_dotdot(nd);
+               if (error)
+                       return error;
+
+               if (unlikely(nd->flags & LOOKUP_IS_SCOPED)) {
+                       /*
+                        * If there was a racing rename or mount along our
+                        * path, then we can't be sure that ".." hasn't jumped
+                        * above nd->root (and so userspace should retry or use
+                        * some fallback).
+                        */
+                       smp_rmb();
+                       if (unlikely(__read_seqcount_retry(&mount_lock.seqcount, nd->m_seq)))
+                               return -EAGAIN;
+                       if (unlikely(__read_seqcount_retry(&rename_lock.seqcount, nd->r_seq)))
+                               return -EAGAIN;
+               }
        }
        return 0;
 }
@@ -2157,6 +2271,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 /* must be paired with terminate_walk() */
 static const char *path_init(struct nameidata *nd, unsigned flags)
 {
+       int error;
        const char *s = nd->name->name;
 
        if (!*s)
@@ -2167,6 +2282,11 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
        nd->last_type = LAST_ROOT; /* if there are only slashes... */
        nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
        nd->depth = 0;
+
+       nd->m_seq = __read_seqcount_begin(&mount_lock.seqcount);
+       nd->r_seq = __read_seqcount_begin(&rename_lock.seqcount);
+       smp_rmb();
+
        if (flags & LOOKUP_ROOT) {
                struct dentry *root = nd->root.dentry;
                struct inode *inode = root->d_inode;
@@ -2175,9 +2295,8 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
                nd->path = nd->root;
                nd->inode = inode;
                if (flags & LOOKUP_RCU) {
-                       nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
+                       nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
                        nd->root_seq = nd->seq;
-                       nd->m_seq = read_seqbegin(&mount_lock);
                } else {
                        path_get(&nd->path);
                }
@@ -2188,13 +2307,16 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
        nd->path.mnt = NULL;
        nd->path.dentry = NULL;
 
-       nd->m_seq = read_seqbegin(&mount_lock);
-       if (*s == '/') {
-               set_root(nd);
-               if (likely(!nd_jump_root(nd)))
-                       return s;
-               return ERR_PTR(-ECHILD);
-       } else if (nd->dfd == AT_FDCWD) {
+       /* Absolute pathname -- fetch the root (LOOKUP_IN_ROOT uses nd->dfd). */
+       if (*s == '/' && !(flags & LOOKUP_IN_ROOT)) {
+               error = nd_jump_root(nd);
+               if (unlikely(error))
+                       return ERR_PTR(error);
+               return s;
+       }
+
+       /* Relative pathname -- get the starting-point it is relative to. */
+       if (nd->dfd == AT_FDCWD) {
                if (flags & LOOKUP_RCU) {
                        struct fs_struct *fs = current->fs;
                        unsigned seq;
@@ -2209,7 +2331,6 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
                        get_fs_pwd(current->fs, &nd->path);
                        nd->inode = nd->path.dentry->d_inode;
                }
-               return s;
        } else {
                /* Caller must check execute permissions on the starting path component */
                struct fd f = fdget_raw(nd->dfd);
@@ -2234,8 +2355,19 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
                        nd->inode = nd->path.dentry->d_inode;
                }
                fdput(f);
-               return s;
        }
+
+       /* For scoped-lookups we need to set the root to the dirfd as well. */
+       if (flags & LOOKUP_IS_SCOPED) {
+               nd->root = nd->path;
+               if (flags & LOOKUP_RCU) {
+                       nd->root_seq = nd->seq;
+               } else {
+                       path_get(&nd->root);
+                       nd->flags |= LOOKUP_ROOT_GRABBED;
+               }
+       }
+       return s;
 }
 
 static const char *trailing_symlink(struct nameidata *nd)
@@ -3201,6 +3333,8 @@ static int do_last(struct nameidata *nd,
                   struct file *file, const struct open_flags *op)
 {
        struct dentry *dir = nd->path.dentry;
+       kuid_t dir_uid = nd->inode->i_uid;
+       umode_t dir_mode = nd->inode->i_mode;
        int open_flag = op->open_flag;
        bool will_truncate = (open_flag & O_TRUNC) != 0;
        bool got_write = false;
@@ -3331,7 +3465,7 @@ static int do_last(struct nameidata *nd,
                error = -EISDIR;
                if (d_is_dir(nd->path.dentry))
                        goto out;
-               error = may_create_in_sticky(dir,
+               error = may_create_in_sticky(dir_mode, dir_uid,
                                             d_backing_inode(nd->path.dentry));
                if (unlikely(error))
                        goto out;