configfs: fix a deadlock in configfs_symlink()
authorAl Viro <viro@zeniv.linux.org.uk>
Sat, 3 Aug 2019 15:51:18 +0000 (11:51 -0400)
committerChristoph Hellwig <hch@lst.de>
Wed, 11 Sep 2019 10:45:49 +0000 (12:45 +0200)
Configfs abuses symlink(2).  Unlike the normal filesystems, it
wants the target resolved at symlink(2) time, like link(2) would've
done.  The problem is that ->symlink() is called with the parent
directory locked exclusive, so resolving the target inside the
->symlink() is easily deadlocked.

Short of really ugly games in sys_symlink() itself, all we can
do is to unlock the parent before resolving the target and
relock it after.  However, that invalidates the checks done
by the caller of ->symlink(), so we have to
* check that dentry is still where it used to be
(it couldn't have been moved, but it could've been unhashed)
* recheck that it's still negative (somebody else
might've successfully created a symlink with the same name
while we were looking the target up)
* recheck the permissions on the parent directory.

Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Christoph Hellwig <hch@lst.de>
fs/configfs/symlink.c

index 91eac6c55e07dd5aa7b95ddd0c8c3b4d36e729a1..f3881e4caedd8f91085f8d5c00c9a9112ff46698 100644 (file)
@@ -143,11 +143,42 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
            !type->ct_item_ops->allow_link)
                goto out_put;
 
+       /*
+        * This is really sick.  What they wanted was a hybrid of
+        * link(2) and symlink(2) - they wanted the target resolved
+        * at syscall time (as link(2) would've done), be a directory
+        * (which link(2) would've refused to do) *AND* be a deep
+        * fucking magic, making the target busy from rmdir POV.
+        * symlink(2) is nothing of that sort, and the locking it
+        * gets matches the normal symlink(2) semantics.  Without
+        * attempts to resolve the target (which might very well
+        * not even exist yet) done prior to locking the parent
+        * directory.  This perversion, OTOH, needs to resolve
+        * the target, which would lead to obvious deadlocks if
+        * attempted with any directories locked.
+        *
+        * Unfortunately, that garbage is userland ABI and we should've
+        * said "no" back in 2005.  Too late now, so we get to
+        * play very ugly games with locking.
+        *
+        * Try *ANYTHING* of that sort in new code, and you will
+        * really regret it.  Just ask yourself - what could a BOFH
+        * do to me and do I want to find it out first-hand?
+        *
+        *  AV, a thoroughly annoyed bastard.
+        */
+       inode_unlock(dir);
        ret = get_target(symname, &path, &target_item, dentry->d_sb);
+       inode_lock(dir);
        if (ret)
                goto out_put;
 
-       ret = type->ct_item_ops->allow_link(parent_item, target_item);
+       if (dentry->d_inode || d_unhashed(dentry))
+               ret = -EEXIST;
+       else
+               ret = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+       if (!ret)
+               ret = type->ct_item_ops->allow_link(parent_item, target_item);
        if (!ret) {
                mutex_lock(&configfs_symlink_mutex);
                ret = create_link(parent_item, target_item, dentry);