All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ondrej Mosnacek <omosnace@redhat.com>
To: selinux@tycho.nsa.gov, Paul Moore <paul@paul-moore.com>
Cc: linux-fsdevel@vger.kernel.org,
	Ondrej Mosnacek <omosnace@redhat.com>,
	stable@vger.kernel.org, Stephen Smalley <sds@tycho.nsa.gov>,
	Al Viro <viro@zeniv.linux.org.uk>
Subject: [PATCH] selinux: fix race when removing selinuxfs entries
Date: Tue,  2 Oct 2018 13:18:30 +0200	[thread overview]
Message-ID: <20181002111830.26342-1-omosnace@redhat.com> (raw)

Letting the following set of commands run long enough on a multi-core
machine causes soft lockups in the kernel:

    (cd /sys/fs/selinux/; while true; do find >/dev/null 2>&1; done) &
    (cd /sys/fs/selinux/; while true; do find >/dev/null 2>&1; done) &
    (cd /sys/fs/selinux/; while true; do find >/dev/null 2>&1; done) &
    while true; do load_policy; echo -n .; sleep 0.1; done

The problem is that sel_remove_entries() calls d_genocide() to remove
the whole contents of certain subdirectories in /sys/fs/selinux/. This
function is apparently only intended for removing filesystem entries
that are no longer accessible to userspace, because it doesn't follow
the rule that any code removing entries from a directory must hold the
write lock on the directory's inode RW semaphore (formerly this was a
mutex, see 9902af79c01a ("parallel lookups: actual switch to rwsem")).

In order to fix this, I had to open-code d_genocide() again, adding the
necessary parent inode locking. I based the new implementation on
d_walk() (fs/dcache.c), the original code from before ad52184b705c
("selinuxfs: don't open-code d_genocide()"), and with a slight
inspiration from debugfs_remove() (fs/debugfs/inode.c).

The new code no longer triggers soft lockups with the above reproducer
and it also gives no warnings when run with CONFIG_PROVE_LOCKING=y.

Note that I am adding Fixes: on the commit that replaced the open-coded
version with d_genocide(), but the bug is present also in the original
code that dates back to pre-2.6 times... This patch obviously doesn't
apply to this older code so pre-4.0 kernels will need a dedicated patch
(just adding the parent inode locks should be sufficient there).

Link: https://github.com/SELinuxProject/selinux-kernel/issues/42
Fixes: ad52184b705c ("selinuxfs: don't open-code d_genocide()")
Cc: <stable@vger.kernel.org> # 4.0+
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
---
 security/selinux/selinuxfs.c | 88 ++++++++++++++++++++++++++++++++++--
 1 file changed, 85 insertions(+), 3 deletions(-)

diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index f3a5a138a096..4ac9b17e24d1 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -1316,10 +1316,92 @@ static const struct file_operations sel_commit_bools_ops = {
 	.llseek		= generic_file_llseek,
 };
 
-static void sel_remove_entries(struct dentry *de)
+/* removes all children of the given dentry (but not itself) */
+static void sel_remove_entries(struct dentry *root)
 {
-	d_genocide(de);
-	shrink_dcache_parent(de);
+	struct dentry *parent = root;
+	struct dentry *child;
+	struct list_head *next;
+
+	spin_lock(&parent->d_lock);
+
+repeat:
+	next = parent->d_subdirs.next;
+
+	while (next != &parent->d_subdirs) {
+		child = list_entry(next, struct dentry, d_child);
+		next = next->next;
+
+		spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
+
+		if (!list_empty(&child->d_subdirs)) {
+			/* Current entry has children, we need to remove those
+			 * first. We unlock the parent but keep the child locked
+			 * (it will become the new parent). */
+			spin_unlock(&parent->d_lock);
+
+			/* we need to re-lock the child (new parent) in a
+			 * special way (see d_walk() in fs/dcache.c) */
+			spin_release(&child->d_lock.dep_map, 1, _RET_IP_);
+			parent = child;
+			spin_acquire(&parent->d_lock.dep_map, 0, 1, _RET_IP_);
+
+			goto repeat;
+		}
+
+resume:
+		if (child->d_inode) {
+			/* acquire a reference to the child */
+			dget_dlock(child);
+
+			/* drop all locks (someof the callees will try to
+			 * acquire them) */
+			spin_unlock(&child->d_lock);
+			spin_unlock(&parent->d_lock);
+
+			/* IMPORTANT: we need to hold the parent's inode lock
+			 * because some functions (namely dcache_readdir) assume
+			 * holding this lock means children won't be removed */
+			inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
+
+			/* now unlink the child */
+			if (d_is_dir(child))
+				simple_rmdir(d_inode(parent), child);
+			else
+				simple_unlink(d_inode(parent), child);
+			d_delete(child);
+
+			inode_unlock(parent->d_inode);
+
+			/* drop our extra reference */
+			dput(child);
+
+			/* re-lock only the parent */
+			spin_lock(&parent->d_lock);
+		} else
+			spin_unlock(&child->d_lock);
+	}
+
+	if (parent != root) {
+		/* we processed contents of a subdirectory, ascend and continue
+		 * by removing the subdirectory itself (now empty) */
+		child = parent;
+		parent = child->d_parent;
+
+		/* we have to re-lock the child after locking the parent */
+		spin_unlock(&child->d_lock);
+		spin_lock(&parent->d_lock);
+		spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
+
+		/* set next to the next sibling */
+		next = child->d_child.next;
+		goto resume;
+	}
+
+	spin_unlock(&root->d_lock);
+
+	/* try to clean up the stale entries */
+	shrink_dcache_parent(root);
 }
 
 #define BOOL_DIR_NAME "booleans"
-- 
2.17.1

             reply	other threads:[~2018-10-02 11:18 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-10-02 11:18 Ondrej Mosnacek [this message]
2018-10-02 15:58 ` [PATCH] selinux: fix race when removing selinuxfs entries Al Viro
2018-10-03  8:18   ` Ondrej Mosnacek
2018-10-03 13:02   ` Stephen Smalley

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20181002111830.26342-1-omosnace@redhat.com \
    --to=omosnace@redhat.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=paul@paul-moore.com \
    --cc=sds@tycho.nsa.gov \
    --cc=selinux@tycho.nsa.gov \
    --cc=stable@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.