diff mbox

[79/79] namei: stretch RCU mode into get_link()

Message ID 1430803373-4948-79-git-send-email-viro@ZenIV.linux.org.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Al Viro May 5, 2015, 5:22 a.m. UTC
From: Al Viro <viro@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 <viro@zeniv.linux.org.uk>
---
 fs/namei.c | 86 ++++++++++++++++++++++++++++++++++----------------------------
 1 file changed, 47 insertions(+), 39 deletions(-)
diff mbox

Patch

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);