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

From: Al Viro
Date: Tue May 05 2015 - 01:32:39 EST


From: Al Viro <viro@xxxxxxxxxxxxxxxxxx>

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@xxxxxxxxxxxxxxxxxx>
---
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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/