namei: allow set_root() to produce errors
authorAleksa Sarai <cyphar@cyphar.com>
Fri, 6 Dec 2019 14:13:29 +0000 (01:13 +1100)
committerAl Viro <viro@zeniv.linux.org.uk>
Mon, 9 Dec 2019 00:09:39 +0000 (19:09 -0500)
For LOOKUP_BENEATH and LOOKUP_IN_ROOT it is necessary to ensure that
set_root() is never called, and thus (for hardening purposes) it should
return an error rather than permit a breakout from the root. In
addition, move all of the repetitive set_root() calls to nd_jump_root().

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namei.c

index fb8a3637237656a1186a11edd57c3e7e37eaefcf..7def6aa9a53dd4498b53e44e992207b00e2034e6 100644 (file)
@@ -798,7 +798,7 @@ 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;
 
@@ -814,6 +814,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 +838,11 @@ static inline void path_to_nameidata(const struct path *path,
 
 static int nd_jump_root(struct nameidata *nd)
 {
+       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;
@@ -1084,10 +1090,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 == '/'))
                        ;
        }
@@ -1700,8 +1705,13 @@ 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);
+               int error = 0;
+
+               if (!nd->root.mnt) {
+                       error = set_root(nd);
+                       if (error)
+                               return error;
+               }
                if (nd->flags & LOOKUP_RCU) {
                        return follow_dotdot_rcu(nd);
                } else
@@ -2159,6 +2169,7 @@ OK:
 /* 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)
@@ -2191,11 +2202,13 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
        nd->path.dentry = NULL;
 
        nd->m_seq = read_seqbegin(&mount_lock);
+
+       /* Figure out the starting path and root (if needed). */
        if (*s == '/') {
-               set_root(nd);
-               if (likely(!nd_jump_root(nd)))
-                       return s;
-               return ERR_PTR(-ECHILD);
+               error = nd_jump_root(nd);
+               if (unlikely(error))
+                       return ERR_PTR(error);
+               return s;
        } else if (nd->dfd == AT_FDCWD) {
                if (flags & LOOKUP_RCU) {
                        struct fs_struct *fs = current->fs;