Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm...
[firefly-linux-kernel-4.4.55.git] / fs / debugfs / inode.c
index 8c41b52da35872461cffbbebcd0299b2cc9d4d45..1e3b99d3db0df8ff236589a0ec64ec2c73bf8013 100644 (file)
@@ -66,7 +66,7 @@ static struct inode *debugfs_get_inode(struct super_block *sb, umode_t mode, dev
                        break;
                }
        }
-       return inode; 
+       return inode;
 }
 
 /* SMP-safe */
@@ -317,7 +317,7 @@ static struct dentry *__create_file(const char *name, umode_t mode,
                goto exit;
 
        /* If the parent is not specified, we create it in the root.
-        * We need the root dentry to do this, which is in the super 
+        * We need the root dentry to do this, which is in the super
         * block. A pointer to that is in the struct vfsmount that we
         * have around.
         */
@@ -330,7 +330,7 @@ static struct dentry *__create_file(const char *name, umode_t mode,
                switch (mode & S_IFMT) {
                case S_IFDIR:
                        error = debugfs_mkdir(parent->d_inode, dentry, mode);
-                                             
+
                        break;
                case S_IFLNK:
                        error = debugfs_link(parent->d_inode, dentry, mode,
@@ -534,7 +534,7 @@ EXPORT_SYMBOL_GPL(debugfs_remove);
  */
 void debugfs_remove_recursive(struct dentry *dentry)
 {
-       struct dentry *child, *next, *parent;
+       struct dentry *child, *parent;
 
        if (IS_ERR_OR_NULL(dentry))
                return;
@@ -546,30 +546,49 @@ void debugfs_remove_recursive(struct dentry *dentry)
        parent = dentry;
  down:
        mutex_lock(&parent->d_inode->i_mutex);
-       list_for_each_entry_safe(child, next, &parent->d_subdirs, d_u.d_child) {
+ loop:
+       /*
+        * The parent->d_subdirs is protected by the d_lock. Outside that
+        * lock, the child can be unlinked and set to be freed which can
+        * use the d_u.d_child as the rcu head and corrupt this list.
+        */
+       spin_lock(&parent->d_lock);
+       list_for_each_entry(child, &parent->d_subdirs, d_u.d_child) {
                if (!debugfs_positive(child))
                        continue;
 
                /* perhaps simple_empty(child) makes more sense */
                if (!list_empty(&child->d_subdirs)) {
+                       spin_unlock(&parent->d_lock);
                        mutex_unlock(&parent->d_inode->i_mutex);
                        parent = child;
                        goto down;
                }
- up:
+
+               spin_unlock(&parent->d_lock);
+
                if (!__debugfs_remove(child, parent))
                        simple_release_fs(&debugfs_mount, &debugfs_mount_count);
+
+               /*
+                * The parent->d_lock protects agaist child from unlinking
+                * from d_subdirs. When releasing the parent->d_lock we can
+                * no longer trust that the next pointer is valid.
+                * Restart the loop. We'll skip this one with the
+                * debugfs_positive() check.
+                */
+               goto loop;
        }
+       spin_unlock(&parent->d_lock);
 
        mutex_unlock(&parent->d_inode->i_mutex);
        child = parent;
        parent = parent->d_parent;
        mutex_lock(&parent->d_inode->i_mutex);
 
-       if (child != dentry) {
-               next = list_next_entry(child, d_u.d_child);
-               goto up;
-       }
+       if (child != dentry)
+               /* go up */
+               goto loop;
 
        if (!__debugfs_remove(child, parent))
                simple_release_fs(&debugfs_mount, &debugfs_mount_count);