֎h莩m_^0X: Ay%~Dx\t*kQX0Kv_@2RI_JSm\u+>&Ш{D]T0Ϭk_}1iZ#):LHL5O9s\ɸQgM꒔vtB˦j'	H:w$5
%@a,߷H%
gw<"oO ͶVµ`@O wN!zNqhB4MyS j Odjb'" >AH(Ne4|d!eouk1W"^EakxA"ڼݡ?2Ȍ@aYVf+N쎯[>t[Sڸ/MEFP81Z(RmM/Ky$@kYpS;뮼Lh'/lL:;^oR+q$ec 8B.7(ŵw"OO1VL_?^hbRcTg=85a:f#>2<%c4vAcT j DVfB0Kt{hQ4;Cxg<_-b1ܑ mnSq@$Hf?<]15}JOQkbsGdY'hJW(37R&$rV"~,ȼ<̓C?.QxDJ҇ġ+vd1WϔFL%د50 ĨGi"j$I]׺0[%]5L̟uqPF9\`'ẂV<2ȋnT֏#:7O=]&iؠ{`'6cri~&GT"xXN0',?V0SQ
\ O+sZEg:/ߗ-P9)V mRdN/!T?ԣ#B*}N&f@eg"D58;_mȇ`;G|חb9?Q	X{MOrlBJx/W`PH:(Koѐȸ)#\,OV:E	ׅDWɜՂ '[<TU0ʓ9L*/S1bMhfZF@$OF>¦QExxr!J!F`.w(+	Z=hE	0Ϣs:HQdŠHsc8TzivAUY.4>cVIH=p
nYgcY,,uK?ؖEiw,Ш?lvh-eBLl3Yi060mEQ-\؛Sܧm-a<!;l0w1+:gQ4{ê{]N7L?޽E"'6
*ACHE_THRESHOLD	512
-#define AVC_CACHE_RECLAIM	16
+#define AVC_CACHE_SLOTS			512
+#define AVC_DEF_CACHE_THRESHOLD		512
+#define AVC_CACHE_RECLAIM		16
+
+#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
+#define avc_cache_stats_incr(field) 				\
+do {								\
+	per_cpu(avc_cache_stats, get_cpu()).field++;		\
+	put_cpu();						\
+} while (0)
+#else
+#define avc_cache_stats_incr(field)	do {} while (0)
+#endif
 
 struct avc_entry {
 	u32			ssid;
@@ -76,8 +87,14 @@ struct avc_callback_node {
 	struct avc_callback_node *next;
 };
 
+/* Exported via selinufs */
+unsigned int avc_cache_threshold = AVC_DEF_CACHE_THRESHOLD;
+
+#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
+DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 };
+#endif
+
 static struct avc_cache avc_cache;
-static unsigned avc_cache_stats[AVC_NSTATS];
 static struct avc_callback_node *avc_callbacks;
 static kmem_cache_t *avc_node_cachep;
 
@@ -86,24 +103,6 @@ static inline int avc_hash(u32 ssid, u32
 	return (ssid ^ (tsid<<2) ^ (tclass<<4)) & (AVC_CACHE_SLOTS - 1);
 }
 
-#ifdef AVC_CACHE_STATS
-static inline void avc_cache_stats_incr(int type)
-{
-	avc_cache_stats[type]++;
-}
-
-static inline void avc_cache_stats_add(int type, unsigned val)
-{
-	avc_cache_stats[type] += val;
-}
-#else
-static inline void avc_cache_stats_incr(int type)
-{ }
-
-static inline void avc_cache_stats_add(int type, unsigned val)
-{ }
-#endif
-
 /**
  * avc_dump_av - Display an access vector in human-readable form.
  * @tclass: target security class
@@ -208,8 +207,7 @@ void __init avc_init(void)
 	audit_log(current->audit_context, "AVC INITIALIZED\n");
 }
 
-#if 0
-static void avc_hash_eval(char *tag)
+int avc_get_hash_stats(char *page)
 {
 	int i, chain_len, max_chain_len, slots_used;
 	struct avc_node *node;
@@ -231,20 +229,17 @@ static void avc_hash_eval(char *tag)
 
 	rcu_read_unlock();
 
-	printk(KERN_INFO "\n");
-	printk(KERN_INFO "%s avc:  %d entries and %d/%d buckets used, longest "
-	       "chain length %d\n", tag, atomic_read(&avc_cache.active_nodes),
-		slots_used, AVC_CACHE_SLOTS, max_chain_len);
+	return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n"
+			 "longest chain: %d\n",
+			 atomic_read(&avc_cache.active_nodes),
+			 slots_used, AVC_CACHE_SLOTS, max_chain_len);
 }
-#else
-static inline void avc_hash_eval(char *tag)
-{ }
-#endif
 
 static void avc_node_free(struct rcu_head *rhead)
 {
 	struct avc_node *node = container_of(rhead, struct avc_node, rhead);
 	kmem_cache_free(avc_node_cachep, node);
+	avc_cache_stats_incr(frees);
 }
 
 static void avc_node_delete(struct avc_node *node)
@@ -277,6 +272,7 @@ static inline int avc_reclaim_node(void)
 			if (atomic_dec_and_test(&node->ae.used)) {
 				/* Recently Unused */
 				avc_node_delete(node);
+				avc_cache_stats_incr(reclaims);
 				ecx++;
 				if (ecx >= AVC_CACHE_RECLAIM) {
 					spin_unlock_irqrestore(&avc_cache.slots_lock[hvalue], flags);
@@ -302,8 +298,9 @@ static struct avc_node *avc_alloc_node(v
 	INIT_RCU_HEAD(&node->rhead);
 	INIT_LIST_HEAD(&node->list);
 	atomic_set(&node->ae.used, 1);
+	avc_cache_stats_incr(allocations);
 
-	if (atomic_inc_return(&avc_cache.active_nodes) > AVC_CACHE_THRESHOLD)
+	if (atomic_inc_return(&avc_cache.active_nodes) > avc_cache_threshold)
 		avc_reclaim_node();
 
 out:
@@ -318,12 +315,10 @@ static void avc_node_populate(struct avc
 	memcpy(&node->ae.avd, &ae->avd, sizeof(node->ae.avd));
 }
 
-static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid,
-                                               u16 tclass, int *probes)
+static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass)
 {
 	struct avc_node *node, *ret = NULL;
 	int hvalue;
-	int tprobes = 1;
 
 	hvalue = avc_hash(ssid, tsid, tclass);
 	list_for_each_entry_rcu(node, &avc_cache.slots[hvalue], list) {
@@ -333,7 +328,6 @@ static inline struct avc_node *avc_searc
 			ret = node;
 			break;
 		}
-		tprobes++;
 	}
 
 	if (ret == NULL) {
@@ -342,8 +336,6 @@ static inline struct avc_node *avc_searc
 	}
 
 	/* cache hit */
-	if (probes)
-		*probes = tprobes;
 	if (atomic_read(&ret->ae.used) != 1)
 		atomic_set(&ret->ae.used, 1);
 out:
@@ -367,19 +359,17 @@ out:
 static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass, u32 requested)
 {
 	struct avc_node *node;
-	int probes;
 
-	avc_cache_stats_incr(AVC_CAV_LOOKUPS);
-	node = avc_search_node(ssid, tsid, tclass,&probes);
+	avc_cache_stats_incr(lookups);
+	node = avc_search_node(ssid, tsid, tclass);
 
 	if (node && ((node->ae.avd.decided & requested) == requested)) {
-		avc_cache_stats_incr(AVC_CAV_HITS);
-		avc_cache_stats_add(AVC_CAV_PROBES,probes);
+		avc_cache_stats_incr(hits);
 		goto out;
 	}
 
 	node = NULL;
-	avc_cache_stats_incr(AVC_CAV_MISSES);
+	avc_cache_stats_incr(misses);
 out:
 	return node;
 }
@@ -930,8 +920,6 @@ int avc_ss_reset(u32 seqno)
 	unsigned long flag;
 	struct avc_node *node;
 
-	avc_hash_eval("reset");
-
 	for (i = 0; i < AVC_CACHE_SLOTS; i++) {
 		spin_lock_irqsave(&avc_cache.slots_lock[i], flag);
 		list_for_each_entry(node, &avc_cache.slots[i], list)
@@ -939,9 +927,6 @@ int avc_ss_reset(u32 seqno)
 		spin_unlock_irqrestore(&avc_cache.slots_lock[i], flag);
 	}
 
-	for (i = 0; i < AVC_NSTATS; i++)
-		avc_cache_stats[i] = 0;
-
 	for (c = avc_callbacks; c; c = c->next) {
 		if (c->events & AVC_CALLBACK_RESET) {
 			rc = c->callback(AVC_CALLBACK_RESET,
@@ -1025,7 +1010,6 @@ int avc_has_perm_noaudit(u32 ssid, u32 t
 	u32 denied;
 
 	rcu_read_lock();
-	avc_cache_stats_incr(AVC_ENTRY_LOOKUPS);
 
 	node = avc_lookup(ssid, tsid, tclass, requested);
 	if (!node) {
diff -puN security/selinux/include/avc.h~selinux-scalability-avc-statistics-and-tuning security/selinux/include/avc.h
--- 25/security/selinux/include/avc.h~selinux-scalability-avc-statistics-and-tuning	2004-11-17 20:46:16.799803560 -0800
+++ 25-akpm/security/selinux/include/avc.h	2004-11-17 20:46:16.810801888 -0800
@@ -82,15 +82,15 @@ struct avc_audit_data {
 /*
  * AVC statistics
  */
-#define AVC_ENTRY_LOOKUPS        0
-#define AVC_ENTRY_HITS	         1
-#define AVC_ENTRY_MISSES         2
-#define AVC_ENTRY_DISCARDS       3
-#define AVC_CAV_LOOKUPS          4
-#define AVC_CAV_HITS             5
-#define AVC_CAV_PROBES           6
-#define AVC_CAV_MISSES           7
-#define AVC_NSTATS               8
+struct avc_cache_stats
+{
+	unsigned int lookups;
+	unsigned int hits;
+	unsigned int misses;
+	unsigned int allocations;
+	unsigned int reclaims;
+	unsigned int frees;
+};
 
 /*
  * AVC display support
@@ -132,5 +132,13 @@ int avc_add_callback(int (*callback)(u32
 		     u32 events, u32 ssid, u32 tsid,
 		     u16 tclass, u32 perms);
 
+/* Exported to selinuxfs */
+int avc_get_hash_stats(char *page);
+extern unsigned int avc_cache_threshold;
+
+#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
+DECLARE_PER_CPU(struct avc_cache_stats, avc_cache_stats);
+#endif
+
 #endif /* _SELINUX_AVC_H_ */
 
diff -puN security/selinux/include/av_permissions.h~selinux-scalability-avc-statistics-and-tuning security/selinux/include/av_permissions.h
--- 25/security/selinux/include/av_permissions.h~selinux-scalability-avc-statistics-and-tuning	2004-11-17 20:46:16.800803408 -0800
+++ 25-akpm/security/selinux/include/av_permissions.h	2004-11-17 20:46:16.811801736 -0800
@@ -515,6 +515,7 @@
 #define SECURITY__COMPUTE_USER                    0x00000040UL
 #define SECURITY__SETENFORCE                      0x00000080UL
 #define SECURITY__SETBOOL                         0x00000100UL
+#define SECURITY__SETSECPARAM                     0x00000200UL
 
 #define SYSTEM__IPC_INFO                          0x00000001UL
 #define SYSTEM__SYSLOG_READ                       0x00000002UL
diff -puN security/selinux/include/av_perm_to_string.h~selinux-scalability-avc-statistics-and-tuning security/selinux/include/av_perm_to_string.h
--- 25/security/selinux/include/av_perm_to_string.h~selinux-scalability-avc-statistics-and-tuning	2004-11-17 20:46:16.802803104 -0800
+++ 25-akpm/security/selinux/include/av_perm_to_string.h	2004-11-17 20:46:16.811801736 -0800
@@ -85,6 +85,7 @@ static struct av_perm_to_string av_perm_
    { SECCLASS_SECURITY, SECURITY__COMPUTE_USER, "compute_user" },
    { SECCLASS_SECURITY, SECURITY__SETENFORCE, "setenforce" },
    { SECCLASS_SECURITY, SECURITY__SETBOOL, "setbool" },
+   { SECCLASS_SECURITY, SECURITY__SETSECPARAM, "setsecparam" },
    { SECCLASS_SYSTEM, SYSTEM__IPC_INFO, "ipc_info" },
    { SECCLASS_SYSTEM, SYSTEM__SYSLOG_READ, "syslog_read" },
    { SECCLASS_SYSTEM, SYSTEM__SYSLOG_MOD, "syslog_mod" },
diff -puN security/selinux/Kconfig~selinux-scalability-avc-statistics-and-tuning security/selinux/Kconfig
--- 25/security/selinux/Kconfig~selinux-scalability-avc-statistics-and-tuning	2004-11-17 20:46:16.803802952 -0800
+++ 25-akpm/security/selinux/Kconfig	2004-11-17 20:46:16.812801584 -0800
@@ -67,6 +67,15 @@ config SECURITY_SELINUX_DEVELOP
 	  can interactively toggle the kernel between enforcing mode and
 	  permissive mode (if permitted by the policy) via /selinux/enforce.
 
+config SECURITY_SELINUX_AVC_STATS
+	bool "NSA SELinux AVC Statistics"
+	depends on SECURITY_SELINUX
+	default y
+	help
+	  This option collects access vector cache statistics to
+	  /selinux/avc/cache_stats, which may be monitored via
+	  tools such as avcstat.
+
 config SECURITY_SELINUX_MLS
 	bool "NSA SELinux MLS policy (EXPERIMENTAL)"
 	depends on SECURITY_SELINUX && EXPERIMENTAL
diff -puN security/selinux/selinuxfs.c~selinux-scalability-avc-statistics-and-tuning security/selinux/selinuxfs.c
--- 25/security/selinux/selinuxfs.c~selinux-scalability-avc-statistics-and-tuning	2004-11-17 20:46:16.804802800 -0800
+++ 25-akpm/security/selinux/selinuxfs.c	2004-11-17 20:46:16.813801432 -0800
@@ -3,6 +3,7 @@
  * 	Added conditional policy language extensions
  *
  * Copyright (C) 2003 - 2004 Tresys Technology, LLC
+ * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com>
  *	This program is free software; you can redistribute it and/or modify
  *  	it under the terms of the GNU General Public License as published by
  *	the Free Software Foundation, version 2.
@@ -18,6 +19,8 @@
 #include <linux/string.h>
 #include <linux/security.h>
 #include <linux/major.h>
+#include <linux/seq_file.h>
+#include <linux/percpu.h>
 #include <asm/uaccess.h>
 #include <asm/semaphore.h>
 
@@ -66,7 +69,8 @@ enum sel_inos {
 	SEL_POLICYVERS,	/* return policy version for this kernel */
 	SEL_COMMIT_BOOLS, /* commit new boolean values */
 	SEL_MLS,	/* return if MLS policy is enabled */
-	SEL_DISABLE	/* disable SELinux until next reboot */
+	SEL_DISABLE,	/* disable SELinux until next reboot */
+	SEL_AVC,	/* AVC management directory */
 };
 
 #define TMPBUFLEN	12
@@ -887,6 +891,213 @@ err:
 
 struct dentry *selinux_null = NULL;
 
+static ssize_t sel_read_avc_cache_threshold(struct file *filp, char __user *buf,
+					    size_t count, loff_t *ppos)
+{
+	char tmpbuf[TMPBUFLEN];
+	ssize_t length;
+
+	length = scnprintf(tmpbuf, TMPBUFLEN, "%u", avc_cache_threshold);
+	return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
+}
+
+static ssize_t sel_write_avc_cache_threshold(struct file * file,
+					     const char __user * buf,
+					     size_t count, loff_t *ppos)
+
+{
+	char *page;
+	ssize_t ret;
+	int new_value;
+
+	if (count < 0 || count >= PAGE_SIZE) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (*ppos != 0) {
+		/* No partial writes. */
+		ret = -EINVAL;
+		goto out;
+	}
+
+	page = (char*)get_zeroed_page(GFP_KERNEL);
+	if (!page) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (copy_from_user(page, buf, count)) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	if (sscanf(page, "%u", &new_value) != 1) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (new_value != avc_cache_threshold) {
+		ret = task_has_security(current, SECURITY__SETSECPARAM);
+		if (ret)
+			goto out_free;
+		avc_cache_threshold = new_value;
+	}
+	ret = count;
+out_free:
+	free_page((unsigned long)page);
+out:
+	return ret;
+}
+
+static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf,
+				       size_t count, loff_t *ppos)
+{
+	char *page;
+	ssize_t ret = 0;
+
+	page = (char *)__get_free_page(GFP_KERNEL);
+	if (!page) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	ret = avc_get_hash_stats(page);
+	if (ret >= 0)
+		ret = simple_read_from_buffer(buf, count, ppos, page, ret);
+	free_page((unsigned long)page);
+out:
+	return ret;
+}
+
+static struct file_operations sel_avc_cache_threshold_ops = {
+	.read		= sel_read_avc_cache_threshold,
+	.write		= sel_write_avc_cache_threshold,
+};
+
+static struct file_operations sel_avc_hash_stats_ops = {
+	.read		= sel_read_avc_hash_stats,
+};
+
+#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
+static struct avc_cache_stats *sel_avc_get_stat_idx(loff_t *idx)
+{
+	int cpu;
+
+	for (cpu = *idx; cpu < NR_CPUS; ++cpu) {
+		if (!cpu_possible(cpu))
+			continue;
+		*idx = cpu + 1;
+		return &per_cpu(avc_cache_stats, cpu);
+	}
+	return NULL;
+}
+
+static void *sel_avc_stats_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	loff_t n = *pos - 1;
+
+	if (*pos == 0)
+		return SEQ_START_TOKEN;
+
+	return sel_avc_get_stat_idx(&n);
+}
+
+static void *sel_avc_stats_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	return sel_avc_get_stat_idx(pos);
+}
+
+static int sel_avc_stats_seq_show(struct seq_file *seq, void *v)
+{
+	struct avc_cache_stats *st = v;
+
+	if (v == SEQ_START_TOKEN)
+		seq_printf(seq, "lookups hits misses allocations reclaims "
+			   "frees\n");
+	else
+		seq_printf(seq, "%u %u %u %u %u %u\n", st->lookups,
+			   st->hits, st->misses, st->allocations,
+			   st->reclaims, st->frees);
+	return 0;
+}
+
+static void sel_avc_stats_seq_stop(struct seq_file *seq, void *v)
+{ }
+
+static struct seq_operations sel_avc_cache_stats_seq_ops = {
+	.start		= sel_avc_stats_seq_start,
+	.next		= sel_avc_stats_seq_next,
+	.show		= sel_avc_stats_seq_show,
+	.stop		= sel_avc_stats_seq_stop,
+};
+
+static int sel_open_avc_cache_stats(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &sel_avc_cache_stats_seq_ops);
+}
+
+static struct file_operations sel_avc_cache_stats_ops = {
+	.open		= sel_open_avc_cache_stats,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+#endif
+
+static int sel_make_avc_files(struct dentry *dir)
+{
+	int i, ret = 0;
+	static struct tree_descr files[] = {
+		{ "cache_threshold",
+		  &sel_avc_cache_threshold_ops, S_IRUGO|S_IWUSR },
+		{ "hash_stats", &sel_avc_hash_stats_ops, S_IRUGO },
+#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
+		{ "cache_stats", &sel_avc_cache_stats_ops, S_IRUGO },
+#endif
+	};
+
+	for (i = 0; i < sizeof (files) / sizeof (files[0]); i++) {
+		struct inode *inode;
+		struct dentry *dentry;
+
+		dentry = d_alloc_name(dir, files[i].name);
+		if (!dentry) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode);
+		if (!inode) {
+			ret = -ENOMEM;
+			goto err;
+		}
+		inode->i_fop = files[i].ops;
+		d_add(dentry, inode);
+	}
+out:
+	return ret;
+err:
+	d_genocide(dir);
+	goto out;
+}
+
+static int sel_make_dir(struct super_block *sb, struct dentry *dentry)
+{
+	int ret = 0;
+	struct inode *inode;
+
+	inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO);
+	if (!inode) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	inode->i_op = &simple_dir_inode_operations;
+	inode->i_fop = &simple_dir_operations;
+	d_add(dentry, inode);
+out:
+	return ret;
+}
+
 static int sel_fill_super(struct super_block * sb, void * data, int silent)
 {
 	int ret;
@@ -943,6 +1154,18 @@ static int sel_fill_super(struct super_b
 	d_add(dentry, inode);
 	selinux_null = dentry;
 
+	dentry = d_alloc_name(sb->s_root, "avc");
+	if (!dentry)
+		return -ENOMEM;
+
+	ret = sel_make_dir(sb, dentry);
+	if (ret)
+		goto out;
+
+	ret = sel_make_avc_files(dentry);
+	if (ret)
+		goto out;
+
 	return 0;
 out:
 	dput(dentry);
_
