configfs: fix a race in configfs_lookup()
authorSishuai Gong <sishuai@purdue.edu>
Wed, 25 Aug 2021 05:52:20 +0000 (07:52 +0200)
committerChristoph Hellwig <hch@lst.de>
Wed, 25 Aug 2021 05:58:49 +0000 (07:58 +0200)
When configfs_lookup() is executing list_for_each_entry(),
it is possible that configfs_dir_lseek() is calling list_del().
Some unfortunate interleavings of them can cause a kernel NULL
pointer dereference error

Thread 1                  Thread 2
//configfs_dir_lseek()    //configfs_lookup()
list_del(&cursor->s_sibling);
                         list_for_each_entry(sd, ...)

Fix this by grabbing configfs_dirent_lock in configfs_lookup()
while iterating ->s_children.

Signed-off-by: Sishuai Gong <sishuai@purdue.edu>
Signed-off-by: Christoph Hellwig <hch@lst.de>
fs/configfs/dir.c

index fc20bd8..1466b5d 100644 (file)
@@ -439,13 +439,13 @@ static struct dentry * configfs_lookup(struct inode *dir,
        if (!configfs_dirent_is_ready(parent_sd))
                return ERR_PTR(-ENOENT);
 
+       spin_lock(&configfs_dirent_lock);
        list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
                if ((sd->s_type & CONFIGFS_NOT_PINNED) &&
                    !strcmp(configfs_get_name(sd), dentry->d_name.name)) {
                        struct configfs_attribute *attr = sd->s_element;
                        umode_t mode = (attr->ca_mode & S_IALLUGO) | S_IFREG;
 
-                       spin_lock(&configfs_dirent_lock);
                        dentry->d_fsdata = configfs_get(sd);
                        sd->s_dentry = dentry;
                        spin_unlock(&configfs_dirent_lock);
@@ -462,10 +462,11 @@ static struct dentry * configfs_lookup(struct inode *dir,
                                inode->i_size = PAGE_SIZE;
                                inode->i_fop = &configfs_file_operations;
                        }
-                       break;
+                       goto done;
                }
        }
-
+       spin_unlock(&configfs_dirent_lock);
+done:
        d_add(dentry, inode);
        return NULL;
 }