@@ -554,6 +554,50 @@ static inline int nd_alloc_stack(struct nameidata *nd)
return __nd_alloc_stack(nd);
}
+static void drop_links(struct nameidata *nd)
+{
+ int i = nd->depth;
+ while (i--) {
+ struct saved *last = nd->stack + i;
+ struct inode *inode = last->inode;
+ if (last->cookie && inode->i_op->put_link) {
+ inode->i_op->put_link(inode, last->cookie);
+ last->cookie = NULL;
+ }
+ }
+}
+
+static bool legitimize_path(struct nameidata *nd,
+ struct path *path, unsigned seq)
+{
+ int res = __legitimize_mnt(path->mnt, nd->m_seq);
+ if (unlikely(res)) {
+ if (res > 0)
+ path->mnt = NULL;
+ path->dentry = NULL;
+ return false;
+ }
+ if (unlikely(!lockref_get_not_dead(&path->dentry->d_lockref))) {
+ path->dentry = NULL;
+ return false;
+ }
+ return !read_seqcount_retry(&path->dentry->d_seq, seq);
+}
+
+static bool legitimize_links(struct nameidata *nd)
+{
+ int i;
+ for (i = 0; i < nd->depth; i++) {
+ struct saved *last = nd->stack + i;
+ if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
+ drop_links(nd);
+ nd->depth = i;
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* Path walking has 2 modes, rcu-walk and ref-walk (see
* Documentation/filesystems/path-lookup.txt). In situations when we can't
@@ -575,25 +619,33 @@ static inline int nd_alloc_stack(struct nameidata *nd)
* unlazy_walk attempts to legitimize the current nd->path, nd->root and dentry
* for ref-walk mode. @dentry must be a path found by a do_lookup call on
* @nd or NULL. Must be called from rcu-walk context.
+ * Nothing should touch nameidata between unlazy_walk() failure and
+ * terminate_walk().
*/
static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
{
struct fs_struct *fs = current->fs;
struct dentry *parent = nd->path.dentry;
+ int res;
BUG_ON(!(nd->flags & LOOKUP_RCU));
- /*
- * After legitimizing the bastards, terminate_walk()
- * will do the right thing for non-RCU mode, and all our
- * subsequent exit cases should rcu_read_unlock()
- * before returning. Do vfsmount first; if dentry
- * can't be legitimized, just set nd->path.dentry to NULL
- * and rely on dput(NULL) being a no-op.
- */
- if (!legitimize_mnt(nd->path.mnt, nd->m_seq))
- return -ECHILD;
nd->flags &= ~LOOKUP_RCU;
+ if (unlikely(!legitimize_links(nd))) {
+ rcu_read_unlock();
+ nd->path.mnt = NULL;
+ nd->path.dentry = NULL;
+ goto drop_root_mnt;
+ }
+ res = __legitimize_mnt(nd->path.mnt, nd->m_seq);
+ if (unlikely(res)) {
+ rcu_read_unlock();
+ if (res < 0)
+ mntput(nd->path.mnt);
+ nd->path.mnt = NULL;
+ nd->path.dentry = NULL;
+ goto drop_root_mnt;
+ }
if (!lockref_get_not_dead(&parent->d_lockref)) {
nd->path.dentry = NULL;
@@ -651,6 +703,23 @@ drop_root_mnt:
return -ECHILD;
}
+static int unlazy_link(struct nameidata *nd, struct path *link, unsigned seq)
+{
+ if (unlikely(!legitimize_path(nd, link, seq))) {
+ drop_links(nd);
+ rcu_read_unlock();
+ nd->flags &= ~LOOKUP_RCU;
+ nd->path.mnt = NULL;
+ nd->path.dentry = NULL;
+ if (!(nd->flags & LOOKUP_ROOT))
+ nd->root.mnt = NULL;
+ } else if (likely(unlazy_walk(nd, NULL, 0)) == 0) {
+ return 0;
+ }
+ path_put(link);
+ return -ECHILD;
+}
+
static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
{
return dentry->d_op->d_revalidate(dentry, flags);
@@ -1539,16 +1608,19 @@ static inline int handle_dots(struct nameidata *nd, int type)
static void terminate_walk(struct nameidata *nd)
{
+ drop_links(nd);
if (!(nd->flags & LOOKUP_RCU)) {
+ int i;
path_put(&nd->path);
+ for (i = 0; i < nd->depth; i++)
+ path_put(&nd->stack[i].link);
} else {
nd->flags &= ~LOOKUP_RCU;
if (!(nd->flags & LOOKUP_ROOT))
nd->root.mnt = NULL;
rcu_read_unlock();
}
- while (unlikely(nd->depth))
- put_link(nd);
+ nd->depth = 0;
}
static int pick_link(struct nameidata *nd, struct path *link,
@@ -1561,13 +1633,12 @@ static int pick_link(struct nameidata *nd, struct path *link,
return -ELOOP;
}
if (nd->flags & LOOKUP_RCU) {
- if (unlikely(nd->path.mnt != link->mnt ||
- unlazy_walk(nd, link->dentry, seq))) {
+ if (unlikely(unlazy_link(nd, link, seq)))
return -ECHILD;
- }
+ } else {
+ if (link->mnt == nd->path.mnt)
+ mntget(link->mnt);
}
- if (link->mnt == nd->path.mnt)
- mntget(link->mnt);
error = nd_alloc_stack(nd);
if (unlikely(error)) {
path_put(link);