
From: James Morris <jmorris@redhat.com>

The patch below fixes a bug in avc_update_node(), reported by Alan Cox. 
This can only happen on a UP kernel (where spin_trylock() always succeeds)
and when SELinux is running in non-enforcing mode.  Under these
circumstances, the code can cause an avc node to be reclaimed as it is
updating it, leading to an oops.

The patch below fixes this by ensuring that avc node allocation and
possible reclamation happens before the cache lookup.

Signed-off-by: James Morris <jmorris@redhat.com>
Signed-off-by: Stephen Smalley <sds@epoch.ncsc.mil>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/security/selinux/avc.c |   25 +++++++++++++++++--------
 1 files changed, 17 insertions(+), 8 deletions(-)

diff -puN security/selinux/avc.c~selinux-scalability-convert-avc-to-rcu-fix security/selinux/avc.c
--- 25/security/selinux/avc.c~selinux-scalability-convert-avc-to-rcu-fix	2004-12-11 01:51:32.032502296 -0800
+++ 25-akpm/security/selinux/avc.c	2004-12-11 01:51:32.036501688 -0800
@@ -254,6 +254,13 @@ static void avc_node_delete(struct avc_n
 	atomic_dec(&avc_cache.active_nodes);
 }
 
+static void avc_node_kill(struct avc_node *node)
+{
+	kmem_cache_free(avc_node_cachep, node);
+	avc_cache_stats_incr(frees);
+	atomic_dec(&avc_cache.active_nodes);
+}
+
 static void avc_node_replace(struct avc_node *new, struct avc_node *old)
 {
 	list_replace_rcu(&old->list, &new->list);
@@ -732,6 +739,12 @@ static int avc_update_node(u32 event, u3
 	unsigned long flag;
 	struct avc_node *pos, *node, *orig = NULL;
 
+	node = avc_alloc_node();
+	if (!node) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
 	/* Lock the target slot */
 	hvalue = avc_hash(ssid, tsid, tclass);
 	spin_lock_irqsave(&avc_cache.slots_lock[hvalue], flag);
@@ -747,17 +760,13 @@ static int avc_update_node(u32 event, u3
 
 	if (!orig) {
 		rc = -ENOENT;
-		goto out;
+		avc_node_kill(node);
+		goto out_unlock;
 	}
 
 	/*
 	 * Copy and replace original node.
 	 */
-	node = avc_alloc_node();
-	if (!node) {
-		rc = -ENOMEM;
-		goto out;
-	}
 
 	avc_node_populate(node, ssid, tsid, tclass, &orig->ae);
 
@@ -783,9 +792,9 @@ static int avc_update_node(u32 event, u3
 		break;
 	}
 	avc_node_replace(node, orig);
-out:
+out_unlock:
 	spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flag);
-
+out:
 	return rc;
 }
 
_
