From: Al Viro <v...@zeniv.linux.org.uk>

one potentially subtle point: may_follow_link() kludge rejecting symlink
traversal in RCU mode is handled by returning -ECHILD and repeating
everything in non-RCU mode.  Reason: audit_log_link_denied() won't work
in RCU mode.

Signed-off-by: Al Viro <v...@zeniv.linux.org.uk>
---
 fs/namei.c | 86 ++++++++++++++++++++++++++++++++++----------------------------
 1 file changed, 47 insertions(+), 39 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index cf8f2de..761d4b1 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -725,6 +725,29 @@ static int complete_walk(struct nameidata *nd)
        return status;
 }
 
+static inline void put_link(struct nameidata *nd)
+{
+       struct saved *last = nd->stack + --nd->depth;
+       struct inode *inode = last->link.dentry->d_inode;
+       if (last->cookie && inode->i_op->put_link)
+               inode->i_op->put_link(last->link.dentry, last->cookie);
+       path_put(&last->link);
+}
+
+static void terminate_walk(struct nameidata *nd)
+{
+       if (!(nd->flags & LOOKUP_RCU)) {
+               path_put(&nd->path);
+       } else {
+               nd->flags &= ~LOOKUP_RCU;
+               if (!(nd->flags & LOOKUP_ROOT))
+                       nd->root.mnt = NULL;
+               rcu_read_unlock();
+       }
+       while (unlikely(nd->depth))
+               put_link(nd);
+}
+
 static __always_inline void set_root(struct nameidata *nd)
 {
        get_fs_root(current->fs, &nd->root);
@@ -776,15 +799,6 @@ void nd_jump_link(struct path *path)
        nd->flags |= LOOKUP_JUMPED;
 }
 
-static inline void put_link(struct nameidata *nd)
-{
-       struct saved *last = nd->stack + --nd->depth;
-       struct inode *inode = last->link.dentry->d_inode;
-       if (last->cookie && inode->i_op->put_link)
-               inode->i_op->put_link(last->link.dentry, last->cookie);
-       path_put(&last->link);
-}
-
 int sysctl_protected_symlinks __read_mostly = 0;
 int sysctl_protected_hardlinks __read_mostly = 0;
 
@@ -804,21 +818,20 @@ int sysctl_protected_hardlinks __read_mostly = 0;
  *
  * Returns 0 if following the symlink is allowed, -ve on error.
  */
-static inline int may_follow_link(struct path *link, struct nameidata *nd)
+static inline int may_follow_link(struct path *link, const struct inode *inode,
+                                 struct nameidata *nd)
 {
-       const struct inode *inode;
        const struct inode *parent;
 
        if (!sysctl_protected_symlinks)
                return 0;
 
        /* Allowed if owner and follower match. */
-       inode = link->dentry->d_inode;
        if (uid_eq(current_cred()->fsuid, inode->i_uid))
                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;
 
@@ -826,9 +839,14 @@ static inline int may_follow_link(struct path *link, 
struct nameidata *nd)
        if (uid_eq(parent->i_uid, inode->i_uid))
                return 0;
 
+       if (nd->flags & LOOKUP_RCU) {
+               terminate_walk(nd);
+               return -ECHILD;
+       }
+
        audit_log_link_denied("follow_link", link);
-       path_put_conditional(link, nd);
-       path_put(&nd->path);
+       path_to_nameidata(link, nd);
+       terminate_walk(nd);
        return -EACCES;
 }
 
@@ -902,15 +920,19 @@ static int may_linkat(struct path *link)
 }
 
 static __always_inline
-const char *get_link(struct nameidata *nd)
+const char *get_link(struct nameidata *nd, struct inode *inode)
 {
        struct saved *last = nd->stack + nd->depth;
        struct dentry *dentry = nd->link.dentry;
-       struct inode *inode = dentry->d_inode;
        int error;
        const char *res;
 
-       BUG_ON(nd->flags & LOOKUP_RCU);
+       if (nd->flags & LOOKUP_RCU) {
+               if (unlikely(nd->link.mnt != nd->path.mnt ||
+                            unlazy_walk(nd, dentry))) {
+                       return ERR_PTR(-ECHILD);
+               }
+       }
 
        if (nd->link.mnt == nd->path.mnt)
                mntget(nd->link.mnt);
@@ -1553,20 +1575,6 @@ static inline int handle_dots(struct nameidata *nd, int 
type)
        return 0;
 }
 
-static void terminate_walk(struct nameidata *nd)
-{
-       if (!(nd->flags & LOOKUP_RCU)) {
-               path_put(&nd->path);
-       } else {
-               nd->flags &= ~LOOKUP_RCU;
-               if (!(nd->flags & LOOKUP_ROOT))
-                       nd->root.mnt = NULL;
-               rcu_read_unlock();
-       }
-       while (unlikely(nd->depth))
-               put_link(nd);
-}
-
 static int pick_link(struct nameidata *nd, struct path *link)
 {
        int error;
@@ -1847,16 +1855,16 @@ OK:
 
                if (err) {
                        const char *s;
+                       struct inode *inode = nd->link.dentry->d_inode;
 
                        if (nd->flags & LOOKUP_RCU) {
-                               if (unlikely(nd->link.mnt != nd->path.mnt ||
-                                            unlazy_walk(nd, nd->link.dentry))) 
{
+                               if (unlikely(is_stale(nd, nd->link.dentry))) {
                                        err = -ECHILD;
                                        break;
                                }
                        }
 
-                       s = get_link(nd);
+                       s = get_link(nd, inode);
 
                        if (unlikely(IS_ERR(s))) {
                                err = PTR_ERR(s);
@@ -2009,20 +2017,20 @@ static void path_cleanup(struct nameidata *nd)
 static int trailing_symlink(struct nameidata *nd)
 {
        const char *s;
+       struct inode *inode = nd->link.dentry->d_inode;
        int error;
 
        if (nd->flags & LOOKUP_RCU) {
-               if (unlikely(nd->link.mnt != nd->path.mnt ||
-                            unlazy_walk(nd, nd->link.dentry))) {
+               if (unlikely(is_stale(nd, nd->link.dentry))) {
                        terminate_walk(nd);
                        return -ECHILD;
                }
        }
-       error = may_follow_link(&nd->link, nd);
+       error = may_follow_link(&nd->link, inode, nd);
        if (unlikely(error))
                return error;
        nd->flags |= LOOKUP_PARENT;
-       s = get_link(nd);
+       s = get_link(nd, inode);
        if (unlikely(IS_ERR(s))) {
                terminate_walk(nd);
                return PTR_ERR(s);
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to