From 69d14d02a362247e61b894322d51a538b7aa1621 Mon Sep 17 00:00:00 2001
From: Dan Walsh <dwalsh@redhat.com>
Date: Wed, 9 Oct 2013 14:14:49 -0400
Subject: [PATCH 01/74] Richard Haines patch that allows us discover constraint
 violation information

Basically we need this information to allow audit2allow/audit2why to better
describe which constraint is being broken.
---
 libselinux/src/audit2why.c                 |   9 +-
 libsepol/include/sepol/policydb/policydb.h |   6 +-
 libsepol/include/sepol/policydb/services.h |  32 ++
 libsepol/src/expand.c                      |  11 +
 libsepol/src/policydb.c                    |  25 +
 libsepol/src/services.c                    | 727 +++++++++++++++++++++++++++--
 libsepol/src/write.c                       |   7 +-
 policycoreutils/audit2allow/audit2allow    |  13 +-
 sepolgen/src/sepolgen/audit.py             |   8 +-
 sepolgen/src/sepolgen/policygen.py         |  20 +-
 10 files changed, 788 insertions(+), 70 deletions(-)

diff --git a/libselinux/src/audit2why.c b/libselinux/src/audit2why.c
index ffe381b..d0dd277 100644
--- a/libselinux/src/audit2why.c
+++ b/libselinux/src/audit2why.c
@@ -314,6 +314,7 @@ static PyObject *init(PyObject *self __attribute__((unused)), PyObject *args) {
 	return result;						
 
 static PyObject *analyze(PyObject *self __attribute__((unused)) , PyObject *args) {
+	char *reason_buf = NULL;
 	security_context_t scon; 
 	security_context_t tcon;
 	char *tclassstr; 
@@ -384,7 +385,7 @@ static PyObject *analyze(PyObject *self __attribute__((unused)) , PyObject *args
 	}
 
 	/* Reproduce the computation. */
-	rc = sepol_compute_av_reason(ssid, tsid, tclass, av, &avd, &reason);
+	rc = sepol_compute_av_reason_buffer(ssid, tsid, tclass, av, &avd, &reason, &reason_buf, 0);
 	if (rc < 0) {
 		RETURN(BADCOMPUTE)
 	}
@@ -425,6 +426,12 @@ static PyObject *analyze(PyObject *self __attribute__((unused)) , PyObject *args
 	}
 
 	if (reason & SEPOL_COMPUTEAV_CONS) {
+		if (reason_buf) {
+			PyObject *result = NULL;
+			result = Py_BuildValue("is", CONSTRAINT, reason_buf);
+			free(reason_buf);
+			return result;
+		} 
 		RETURN(CONSTRAINT);
 	}
 
diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
index c27275e..0165eed 100644
--- a/libsepol/include/sepol/policydb/policydb.h
+++ b/libsepol/include/sepol/policydb/policydb.h
@@ -683,10 +683,11 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
 #define POLICYDB_VERSION_ROLETRANS	26
 #define POLICYDB_VERSION_NEW_OBJECT_DEFAULTS	27
 #define POLICYDB_VERSION_DEFAULT_TYPE	28
+#define POLICYDB_VERSION_CONSTRAINT_NAMES	29
 
 /* Range of policy versions we understand*/
 #define POLICYDB_VERSION_MIN	POLICYDB_VERSION_BASE
-#define POLICYDB_VERSION_MAX	POLICYDB_VERSION_DEFAULT_TYPE
+#define POLICYDB_VERSION_MAX	POLICYDB_VERSION_CONSTRAINT_NAMES
 
 /* Module versions and specific changes*/
 #define MOD_POLICYDB_VERSION_BASE		4
@@ -704,9 +705,10 @@ extern int policydb_set_target_platform(policydb_t *p, int platform);
 #define MOD_POLICYDB_VERSION_TUNABLE_SEP	14
 #define MOD_POLICYDB_VERSION_NEW_OBJECT_DEFAULTS	15
 #define MOD_POLICYDB_VERSION_DEFAULT_TYPE	16
+#define MOD_POLICYDB_VERSION_CONSTRAINT_NAMES	17
 
 #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
-#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_DEFAULT_TYPE
+#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_CONSTRAINT_NAMES
 
 #define POLICYDB_CONFIG_MLS    1
 
diff --git a/libsepol/include/sepol/policydb/services.h b/libsepol/include/sepol/policydb/services.h
index aef0c7b..1969a10 100644
--- a/libsepol/include/sepol/policydb/services.h
+++ b/libsepol/include/sepol/policydb/services.h
@@ -58,6 +58,38 @@ extern int sepol_compute_av_reason(sepol_security_id_t ssid,
 				   struct sepol_av_decision *avd,
 				   unsigned int *reason);
 
+/* 
+ * Same as above, but also returns the constraint expression calculations
+ * whether allowed or denied in a buffer. This buffer is allocated by
+ * this call and must be free'd by the caller using free(3). The contraint
+ * buffer will contain any constraints in infix notation.
+ * If the SHOW_GRANTED flag is set it will show granted and denied
+ * constraints. The default is to show only denied constraints.
+ */
+#define SHOW_GRANTED      1
+extern int sepol_compute_av_reason_buffer(sepol_security_id_t ssid,
+				   sepol_security_id_t tsid,
+				   sepol_security_class_t tclass,
+				   sepol_access_vector_t requested,
+				   struct sepol_av_decision *avd,
+				   unsigned int *reason,
+				   char **reason_buf,
+				   unsigned int flags);
+/*
+ * Return a class ID associated with the class string representation
+ * specified by `class_name'.
+ */
+extern int sepol_class_name_to_id(const char *class_name,
+					sepol_security_class_t  *tclass);
+
+/*
+ * Return a permission av bit associated with tclass and the string
+ * representation of the `perm_name'.
+ */
+extern int sepol_perm_name_to_av(sepol_security_class_t tclass,
+					const char *perm_name,
+					sepol_access_vector_t *av);
+
 /*
  * Compute a SID to use for labeling a new object in the 
  * class `tclass' based on a SID pair.  
diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
index f0555bb..6fd992f 100644
--- a/libsepol/src/expand.c
+++ b/libsepol/src/expand.c
@@ -384,6 +384,17 @@ static int constraint_node_clone(constraint_node_t ** dst,
 			new_expr->op = expr->op;
 			if (new_expr->expr_type == CEXPR_NAMES) {
 				if (new_expr->attr & CEXPR_TYPE) {
+					/*
+					 * Copy over constraint policy source types and/or
+					 * attributes for sepol_compute_av_reason_buffer(3) so that
+					 * utilities can analyse constraint errors.
+					 */
+					if (map_ebitmap(&expr->type_names->types,
+							&new_expr->type_names->types,
+							state->typemap)) {
+						ERR(NULL, "Failed to map type_names->types");
+						goto out_of_mem;
+					}
 					/* Type sets require expansion and conversion. */
 					if (expand_convert_type_set(state->out,
 								    state->
diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
index 1f49261..8c7efbc 100644
--- a/libsepol/src/policydb.c
+++ b/libsepol/src/policydb.c
@@ -165,6 +165,13 @@ static struct policydb_compat_info policydb_compat[] = {
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
 	{
+	 .type = POLICY_KERN,
+	 .version = POLICYDB_VERSION_CONSTRAINT_NAMES,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = OCON_NODE6 + 1,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
+	{
 	 .type = POLICY_BASE,
 	 .version = MOD_POLICYDB_VERSION_BASE,
 	 .sym_num = SYM_NUM,
@@ -256,6 +263,13 @@ static struct policydb_compat_info policydb_compat[] = {
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
 	{
+	 .type = POLICY_BASE,
+	 .version = MOD_POLICYDB_VERSION_CONSTRAINT_NAMES,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = OCON_NODE6 + 1,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
+	{
 	 .type = POLICY_MOD,
 	 .version = MOD_POLICYDB_VERSION_BASE,
 	 .sym_num = SYM_NUM,
@@ -346,6 +360,13 @@ static struct policydb_compat_info policydb_compat[] = {
 	 .ocon_num = 0,
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
+	{
+	 .type = POLICY_MOD,
+	 .version = MOD_POLICYDB_VERSION_CONSTRAINT_NAMES,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = 0,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
 };
 
 #if 0
@@ -2019,6 +2040,10 @@ static int read_cons_helper(policydb_t * p, constraint_node_t ** nodep,
 				if (p->policy_type != POLICY_KERN &&
 				    type_set_read(e->type_names, fp))
 					return -1;
+				else if (p->policy_type == POLICY_KERN &&
+					 p->policyvers >= POLICYDB_VERSION_CONSTRAINT_NAMES &&
+					 type_set_read(e->type_names, fp))
+					return -1;
 				break;
 			default:
 				return -1;
diff --git a/libsepol/src/services.c b/libsepol/src/services.c
index 7fac4a0..43ec07e 100644
--- a/libsepol/src/services.c
+++ b/libsepol/src/services.c
@@ -43,6 +43,11 @@
  * Implementation of the security services.
  */
 
+/* The initial sizes malloc'd for sepol_compute_av_reason_buffer() support */
+#define REASON_BUF_SIZE 2048
+#define EXPR_BUF_SIZE 1024
+#define STACK_LEN 32
+
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -54,6 +59,7 @@
 #include <sepol/policydb/services.h>
 #include <sepol/policydb/conditional.h>
 #include <sepol/policydb/flask.h>
+#include <sepol/policydb/util.h>
 
 #include "debug.h"
 #include "private.h"
@@ -70,6 +76,50 @@ static int selinux_enforcing = 1;
 static sidtab_t mysidtab, *sidtab = &mysidtab;
 static policydb_t mypolicydb, *policydb = &mypolicydb;
 
+/* Used by sepol_compute_av_reason_buffer() to keep track of entries */
+static int reason_buf_used;
+static int reason_buf_len;
+
+/* Stack services for RPN to infix conversion. */
+static char **stack;
+static int stack_len;
+static int next_stack_entry;
+
+static void push(char * expr_ptr)
+{
+	if (next_stack_entry >= stack_len) {
+		char **new_stack = stack;
+		int new_stack_len;
+
+		if (stack_len == 0)
+			new_stack_len = STACK_LEN;
+		else
+			new_stack_len = stack_len * 2;
+
+		new_stack = realloc(stack, new_stack_len * sizeof(*stack));
+		if (!new_stack) {
+			ERR(NULL, "unable to allocate stack space");
+			return;
+		}
+		stack_len = new_stack_len;
+		stack = new_stack;
+	}
+	stack[next_stack_entry] = expr_ptr;
+	next_stack_entry++;
+}
+
+static char *pop(void)
+{
+	next_stack_entry--;
+	if (next_stack_entry < 0) {
+		next_stack_entry = 0;
+		ERR(NULL, "pop called with no stack entries");
+		return NULL;
+	}
+	return stack[next_stack_entry];
+}
+/* End Stack services */
+
 int hidden sepol_set_sidtab(sidtab_t * s)
 {
 	sidtab = s;
@@ -113,20 +163,195 @@ int sepol_set_policydb_from_file(FILE * fp)
 static uint32_t latest_granting = 0;
 
 /*
- * Return the boolean value of a constraint expression 
- * when it is applied to the specified source and target 
+ * cat_expr_buf adds a string to an expression buffer and handles realloc's if
+ * buffer is too small. The array of expression text buffer pointers and its
+ * counter are globally defined here as constraint_expr_eval_reason() sets
+ * them up and cat_expr_buf updates the e_buf pointer if the buffer is realloc'ed.
+ */
+static int expr_counter;
+static char **expr_list;
+static int expr_buf_used;
+static int expr_buf_len;
+
+static void cat_expr_buf(char *e_buf, char *string)
+{
+	int len, new_buf_len;
+	char *p, *new_buf = e_buf;
+
+	while (1) {
+		p = e_buf + expr_buf_used;
+		len = snprintf(p, expr_buf_len - expr_buf_used, "%s", string);
+		if (len < 0 || len >= expr_buf_len - expr_buf_used) {
+			new_buf_len = expr_buf_len + EXPR_BUF_SIZE;
+			new_buf = realloc(e_buf, new_buf_len);
+			if (!new_buf) {
+				ERR(NULL, "failed to realloc expr buffer");
+				return;
+			}
+			/* Update the new ptr in the expr list and locally + new len */
+			expr_list[expr_counter] = new_buf;
+			e_buf = new_buf;
+			expr_buf_len = new_buf_len;
+		} else {
+			expr_buf_used += len;
+			return;
+		}
+	}
+}
+
+/*
+ * If the POLICY_KERN version is < POLICYDB_VERSION_CONSTRAINT_NAMES,
+ * then just return.
+ *
+ * If the POLICY_KERN version is >= POLICYDB_VERSION_CONSTRAINT_NAMES,
+ * then for 'types' only, read the types_names->types list as it will
+ * contain a list of types and attributes that were defined in the
+ * policy source.
+ */
+static void get_names_list(constraint_expr_t *e, int type)
+{
+	ebitmap_t *types;
+	types = &e->type_names->types;
+	int rc = 0;
+	unsigned int i;
+	char tmp_buf[128];
+	/* if -type_names->types is 0, then output string <empty_set> */
+	int empty_set = 0;
+
+	if (policydb->policy_type == POLICY_KERN &&
+			policydb->policyvers >= POLICYDB_VERSION_CONSTRAINT_NAMES &&
+			type == CEXPR_TYPE) {
+		/*
+		 * Process >= POLICYDB_VERSION_CONSTRAINT_NAMES with CEXPR_TYPE, then
+		 * obtain the list of names defined in the policy source.
+		 */
+		cat_expr_buf(expr_list[expr_counter], "{ POLICY_SOURCE: ");
+		for (i = ebitmap_startbit(types); i < ebitmap_length(types); i++) {
+			if ((rc = ebitmap_get_bit(types, i)) == 0)
+				continue;
+			/* Collect entries */
+			snprintf(tmp_buf, sizeof(tmp_buf), "%s ", policydb->p_type_val_to_name[i]);
+			cat_expr_buf(expr_list[expr_counter], tmp_buf);
+			empty_set++;
+		}
+		if (empty_set == 0)
+			cat_expr_buf(expr_list[expr_counter], "<empty_set> ");
+		cat_expr_buf(expr_list[expr_counter], "} ");
+	}
+	return;
+}
+
+static void msgcat(char *src, char *tgt, char *rel, int failed)
+{
+	char tmp_buf[1024];
+	if (failed)
+		snprintf(tmp_buf, sizeof(tmp_buf), "(%s %s %s -Fail-) ",
+				src, rel, tgt);
+	else
+		snprintf(tmp_buf, sizeof(tmp_buf), "(%s %s %s -Pass-) ",
+				src, rel, tgt);
+	cat_expr_buf(expr_list[expr_counter], tmp_buf);
+}
+
+/* Returns a buffer with class, statement type and permissions */
+static char *get_class_info(sepol_security_class_t tclass,
+							constraint_node_t *constraint,
+							context_struct_t * xcontext)
+{
+	constraint_expr_t *e;
+	int mls, state_num;
+
+	/* Find if MLS statement or not */
+	mls = 0;
+	for (e = constraint->expr; e; e = e->next) {
+		if (e->attr >= CEXPR_L1L2) {
+			mls = 1;
+			break;
+		}
+	}
+
+	/* Determine statement type */
+	char *statements[] = {
+        "constrain ",			/* 0 */
+		"mlsconstrain ",		/* 1 */
+        "validatetrans ",		/* 2 */
+		"mlsvalidatetrans ",	/* 3 */
+        0 };
+
+	if (xcontext == NULL)
+		state_num = mls + 0;
+	else
+		state_num = mls + 2;
+
+	int class_buf_len = 0;
+	int new_class_buf_len;
+	int len, buf_used;
+	char *class_buf = NULL, *p;
+	char *new_class_buf = NULL;
+
+	while (1) {
+		new_class_buf_len = class_buf_len + EXPR_BUF_SIZE;
+		new_class_buf = realloc(class_buf, new_class_buf_len);
+			if (!new_class_buf)
+				return NULL;
+		class_buf_len = new_class_buf_len;
+		class_buf = new_class_buf;
+		buf_used = 0;
+		p = class_buf;
+
+		/* Add statement type */
+		len = snprintf(p, class_buf_len - buf_used, "%s", statements[state_num]);
+		if (len < 0 || len >= class_buf_len - buf_used)
+			continue;
+
+		/* Add class entry */
+		p += len;
+		buf_used += len;
+		len = snprintf(p, class_buf_len - buf_used, "%s ",
+				policydb->p_class_val_to_name[tclass - 1]);
+		if (len < 0 || len >= class_buf_len - buf_used)
+			continue;
+
+		/* Add permission entries */
+		p += len;
+		buf_used += len;
+		len = snprintf(p, class_buf_len - buf_used, "{%s } (",
+				sepol_av_to_string(policydb, tclass, constraint->permissions));
+		if (len < 0 || len >= class_buf_len - buf_used)
+			continue;
+		break;
+	}
+	return class_buf;
+}
+
+/*
+ * Modified version of constraint_expr_eval that will process each
+ * constraint as before but adds the information to text buffers that
+ * will hold various components. The expression will be in RPN format,
+ * therefore there is a stack based RPN to infix converter to produce
+ * the final readable constraint.
+ *
+ * Return the boolean value of a constraint expression
+ * when it is applied to the specified source and target
  * security contexts.
  *
  * xcontext is a special beast...  It is used by the validatetrans rules
  * only.  For these rules, scontext is the context before the transition,
  * tcontext is the context after the transition, and xcontext is the context
  * of the process performing the transition.  All other callers of
- * constraint_expr_eval should pass in NULL for xcontext.
+ * constraint_expr_eval_reason should pass in NULL for xcontext.
+ * 
+ * This function will also build a buffer as the constraint is processed
+ * for analysis. If this option is not required, then:
+ *      'tclass' should be '0' and r_buf MUST be NULL.
  */
-static int constraint_expr_eval(context_struct_t * scontext,
+static int constraint_expr_eval_reason(context_struct_t * scontext,
 				context_struct_t * tcontext,
 				context_struct_t * xcontext,
-				constraint_expr_t * cexpr)
+				sepol_security_class_t tclass,
+				constraint_node_t *constraint,
+				char **r_buf,
+				unsigned int flags)
 {
 	uint32_t val1, val2;
 	context_struct_t *c;
@@ -136,56 +361,137 @@ static int constraint_expr_eval(context_struct_t * scontext,
 	int s[CEXPR_MAXDEPTH];
 	int sp = -1;
 
-	for (e = cexpr; e; e = e->next) {
+	char tmp_buf[128];
+
+/*
+ * Define the s_t_x_num values that make up r1, t2 etc. in text strings
+ * Set 1 = source, 2 = target, 3 = xcontext for validatetrans
+ */
+#define SOURCE  1
+#define TARGET  2
+#define XTARGET 3
+
+	int s_t_x_num = SOURCE;
+
+	/* Set 0 = fail, u = CEXPR_USER, r = CEXPR_ROLE, t = CEXPR_TYPE */
+	int u_r_t = 0;
+
+	char *name1, *name2;
+	char *src = NULL;
+	char *tgt = NULL;
+
+	int rc = 0, x;
+
+	char *class_buf = NULL;
+
+	class_buf = get_class_info(tclass, constraint, xcontext);
+	if (!class_buf) {
+		ERR(NULL, "failed to allocate class buffer");
+		return -ENOMEM;
+	}
+
+	/* Original function but with buffer support */
+	int expr_list_len = 0;
+	expr_counter = 0;
+	expr_list = NULL;
+	for (e = constraint->expr; e; e = e->next) {
+		/* Allocate a stack to hold expression buffer entries */
+		if (expr_counter >= expr_list_len) {
+			char **new_expr_list = expr_list;
+			int new_expr_list_len;
+
+			if (expr_list_len == 0)
+				new_expr_list_len = STACK_LEN;
+			else
+				new_expr_list_len = expr_list_len * 2;
+
+			new_expr_list = realloc(expr_list, new_expr_list_len * sizeof(*expr_list));
+			if (!new_expr_list) {
+				ERR(NULL, "failed to allocate expr buffer stack");
+				rc = -ENOMEM;
+				goto out;
+			}
+			expr_list_len = new_expr_list_len;
+			expr_list = new_expr_list;
+		}
+
+		/*
+		 * malloc a buffer to store each expression text component. If the
+		 * buffer is too small cat_expr_buf() will realloc extra space.
+		 */
+		expr_buf_len = EXPR_BUF_SIZE;
+		expr_list[expr_counter] = malloc(expr_buf_len);
+		if (!expr_list[expr_counter]) {
+			ERR(NULL, "failed to allocate expr buffer");
+			rc = -ENOMEM;
+			goto out;
+		}
+		expr_buf_used = 0;
+
+		/* Now process each expression of the constraint */
 		switch (e->expr_type) {
 		case CEXPR_NOT:
 			BUG_ON(sp < 0);
 			s[sp] = !s[sp];
+			cat_expr_buf(expr_list[expr_counter], "not");
 			break;
 		case CEXPR_AND:
 			BUG_ON(sp < 1);
 			sp--;
 			s[sp] &= s[sp + 1];
+			cat_expr_buf(expr_list[expr_counter], "and");
 			break;
 		case CEXPR_OR:
 			BUG_ON(sp < 1);
 			sp--;
 			s[sp] |= s[sp + 1];
+			cat_expr_buf(expr_list[expr_counter], "or");
 			break;
 		case CEXPR_ATTR:
 			if (sp == (CEXPR_MAXDEPTH - 1))
-				return 0;
+				goto out;
+
 			switch (e->attr) {
 			case CEXPR_USER:
 				val1 = scontext->user;
 				val2 = tcontext->user;
+				free(src); src = strdup("u1");
+				free(tgt); tgt = strdup("u2");
 				break;
 			case CEXPR_TYPE:
 				val1 = scontext->type;
 				val2 = tcontext->type;
+				free(src); src = strdup("t1");
+				free(tgt); tgt = strdup("t2");
 				break;
 			case CEXPR_ROLE:
 				val1 = scontext->role;
 				val2 = tcontext->role;
 				r1 = policydb->role_val_to_struct[val1 - 1];
 				r2 = policydb->role_val_to_struct[val2 - 1];
+				name1 = policydb->p_role_val_to_name[r1->s.value - 1];
+				name2 = policydb->p_role_val_to_name[r2->s.value - 1];
+				snprintf(tmp_buf, sizeof(tmp_buf), "r1=%s", name1);
+				free(src); src = strdup(tmp_buf);
+				snprintf(tmp_buf, sizeof(tmp_buf), "r2=%s ", name2);
+				free(tgt); tgt = strdup(tmp_buf);
+
 				switch (e->op) {
 				case CEXPR_DOM:
-					s[++sp] =
-					    ebitmap_get_bit(&r1->dominates,
-							    val2 - 1);
+					s[++sp] = ebitmap_get_bit(&r1->dominates, val2 - 1);
+					msgcat(src, tgt, "dom", s[sp] == 0);
+					expr_counter++;
 					continue;
 				case CEXPR_DOMBY:
-					s[++sp] =
-					    ebitmap_get_bit(&r2->dominates,
-							    val1 - 1);
+					s[++sp] = ebitmap_get_bit(&r2->dominates, val1 - 1);
+					msgcat(src, tgt, "domby", s[sp] == 0);
+					expr_counter++;
 					continue;
 				case CEXPR_INCOMP:
-					s[++sp] =
-					    (!ebitmap_get_bit
-					     (&r1->dominates, val2 - 1)
-					     && !ebitmap_get_bit(&r2->dominates,
-								 val1 - 1));
+					s[++sp] = (!ebitmap_get_bit(&r1->dominates, val2 - 1)
+						 && !ebitmap_get_bit(&r2->dominates, val1 - 1));
+					msgcat(src, tgt, "incomp", s[sp] == 0);
+					expr_counter++;
 					continue;
 				default:
 					break;
@@ -194,110 +500,327 @@ static int constraint_expr_eval(context_struct_t * scontext,
 			case CEXPR_L1L2:
 				l1 = &(scontext->range.level[0]);
 				l2 = &(tcontext->range.level[0]);
+				free(src); src = strdup("l1");
+				free(tgt); tgt = strdup("l2");
 				goto mls_ops;
 			case CEXPR_L1H2:
 				l1 = &(scontext->range.level[0]);
 				l2 = &(tcontext->range.level[1]);
+				free(src); src = strdup("l1");
+				free(tgt); tgt = strdup("h2");
 				goto mls_ops;
 			case CEXPR_H1L2:
 				l1 = &(scontext->range.level[1]);
 				l2 = &(tcontext->range.level[0]);
+				free(src); src = strdup("h1");
+				free(tgt); tgt = strdup("L2");
 				goto mls_ops;
 			case CEXPR_H1H2:
 				l1 = &(scontext->range.level[1]);
 				l2 = &(tcontext->range.level[1]);
+				free(src); src = strdup("h1");
+				free(tgt); tgt = strdup("h2");
 				goto mls_ops;
 			case CEXPR_L1H1:
 				l1 = &(scontext->range.level[0]);
 				l2 = &(scontext->range.level[1]);
+				free(src); src = strdup("l1");
+				free(tgt); tgt = strdup("h1");
 				goto mls_ops;
 			case CEXPR_L2H2:
 				l1 = &(tcontext->range.level[0]);
 				l2 = &(tcontext->range.level[1]);
-				goto mls_ops;
-			      mls_ops:
+				free(src); src = strdup("l2");
+				free(tgt); tgt = strdup("h2");
+			mls_ops:
 				switch (e->op) {
 				case CEXPR_EQ:
 					s[++sp] = mls_level_eq(l1, l2);
+					msgcat(src, tgt, "eq", s[sp] == 0);
+					expr_counter++;
 					continue;
 				case CEXPR_NEQ:
 					s[++sp] = !mls_level_eq(l1, l2);
+					msgcat(src, tgt, "neq", s[sp] == 0);
+					expr_counter++;
 					continue;
 				case CEXPR_DOM:
 					s[++sp] = mls_level_dom(l1, l2);
+					msgcat(src, tgt, "dom", s[sp] == 0);
+					expr_counter++;
 					continue;
 				case CEXPR_DOMBY:
 					s[++sp] = mls_level_dom(l2, l1);
+					msgcat(src, tgt, "domby", s[sp] == 0);
+					expr_counter++;
 					continue;
 				case CEXPR_INCOMP:
 					s[++sp] = mls_level_incomp(l2, l1);
+					msgcat(src, tgt, "incomp", s[sp] == 0);
+					expr_counter++;
 					continue;
 				default:
 					BUG();
-					return 0;
+					goto out;
 				}
 				break;
 			default:
 				BUG();
-				return 0;
+				goto out;
 			}
 
 			switch (e->op) {
 			case CEXPR_EQ:
 				s[++sp] = (val1 == val2);
+				msgcat(src, tgt, "eq", s[sp] == 0);
 				break;
 			case CEXPR_NEQ:
 				s[++sp] = (val1 != val2);
+				msgcat(src, tgt, "neq", s[sp] == 0);
 				break;
 			default:
 				BUG();
-				return 0;
+				goto out;
 			}
 			break;
 		case CEXPR_NAMES:
 			if (sp == (CEXPR_MAXDEPTH - 1))
-				return 0;
+				goto out;
+			s_t_x_num = SOURCE;
 			c = scontext;
-			if (e->attr & CEXPR_TARGET)
+			if (e->attr & CEXPR_TARGET) {
+				s_t_x_num = TARGET;
 				c = tcontext;
-			else if (e->attr & CEXPR_XTARGET) {
+			} else if (e->attr & CEXPR_XTARGET) {
+				s_t_x_num = XTARGET;
 				c = xcontext;
-				if (!c) {
-					BUG();
-					return 0;
-				}
 			}
-			if (e->attr & CEXPR_USER)
+			if (!c) {
+				BUG();
+				goto out;
+			}
+			if (e->attr & CEXPR_USER) {
+				u_r_t = CEXPR_USER;
 				val1 = c->user;
-			else if (e->attr & CEXPR_ROLE)
+				name1 = policydb->p_user_val_to_name[val1 - 1];
+				snprintf(tmp_buf, sizeof(tmp_buf), "u%d=%s ",
+						s_t_x_num, name1);
+				free(src); src = strdup(tmp_buf);
+			}
+			else if (e->attr & CEXPR_ROLE) {
+				u_r_t = CEXPR_ROLE;
 				val1 = c->role;
-			else if (e->attr & CEXPR_TYPE)
+				name1 = policydb->p_role_val_to_name[val1 - 1];
+				snprintf(tmp_buf, sizeof(tmp_buf), "r%d=%s ", s_t_x_num, name1);
+				free(src); src = strdup(tmp_buf);
+			}
+			else if (e->attr & CEXPR_TYPE) {
+				u_r_t = CEXPR_TYPE;
 				val1 = c->type;
+				name1 = policydb->p_type_val_to_name[val1 - 1];
+				snprintf(tmp_buf, sizeof(tmp_buf),
+						"t%d=%s ", s_t_x_num, name1);
+				free(src); src = strdup(tmp_buf);
+			}
 			else {
 				BUG();
-				return 0;
+				goto out;
 			}
 
 			switch (e->op) {
 			case CEXPR_EQ:
+				switch (u_r_t) {
+				case CEXPR_USER:
+					free(tgt); tgt=strdup("USER_ENTRY");
+					break;
+				case CEXPR_ROLE:
+					free(tgt); tgt=strdup("ROLE_ENTRY");
+					break;
+				case CEXPR_TYPE:
+					free(tgt); tgt=strdup("TYPE_ENTRY");
+					break;
+				default:
+					ERR(NULL, "unrecognized u_r_t Value: %d", u_r_t);
+					break;
+				}
+
 				s[++sp] = ebitmap_get_bit(&e->names, val1 - 1);
+				msgcat(src, tgt, "eq", s[sp] == 0);
+				if (s[sp] == 0) {
+					get_names_list(e, u_r_t);
+				}
 				break;
+
 			case CEXPR_NEQ:
+				switch (u_r_t) {
+				case CEXPR_USER:
+					free(tgt); tgt=strdup("USER_ENTRY");
+					break;
+				case CEXPR_ROLE:
+					free(tgt); tgt=strdup("ROLE_ENTRY");
+					break;
+				case CEXPR_TYPE:
+					free(tgt); tgt=strdup("TYPE_ENTRY");
+					break;
+				default:
+					ERR(NULL, "unrecognized u_r_t Value: %d", u_r_t);
+					break;
+				}
+
 				s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1);
+				msgcat(src, tgt, "neq", s[sp] == 0);
+				if (s[sp] == 0) {
+					get_names_list(e, u_r_t);
+				}
 				break;
 			default:
 				BUG();
-				return 0;
+				goto out;
 			}
 			break;
 		default:
 			BUG();
-			return 0;
+			goto out;
 		}
+		expr_counter++;
+	}
+
+	/*
+	 * At this point each expression of the constraint is in
+	 * expr_list[n+1] and in RPN format. Now convert to 'infix'
+	 */
+
+	/*
+	 * Save expr count but zero expr_counter to detect if 'BUG(); goto out;'
+	 * was called as we need to release any used expr_list malloc's. Normally
+	 * they are released by the RPN to infix code.
+	 */
+	int expr_count = expr_counter;
+	expr_counter = 0;
+
+	/*
+	 * The array of expression answer buffer pointers and counter. Generate
+	 * the same number of answer buffer entries as expression buffers (as
+	 * there will never be more required).
+	 */
+	char **answer_list;
+	int answer_counter = 0;
+
+	answer_list = malloc(expr_count * sizeof(*answer_list));
+	if (!answer_list) {
+		ERR(NULL, "failed to allocate answer stack");
+		rc = -ENOMEM;
+		goto out;
 	}
 
-	BUG_ON(sp != 0);
-	return s[0];
+	/* The pop operands */
+	char *a;
+	char *b;
+	int a_len, b_len;
+
+	/* Convert constraint from RPN to infix notation. */
+	for (x = 0; x != expr_count; x++) {
+		if (strncmp(expr_list[x], "and", 3) == 0 || strncmp(expr_list[x],
+					"or", 2) == 0) {
+			b = pop();
+			b_len = strlen(b);
+			a = pop();
+			a_len = strlen(a);
+
+			/* get a buffer to hold the answer */
+			answer_list[answer_counter] = malloc(a_len + b_len + 8);
+			if (!answer_list[answer_counter]) {
+				ERR(NULL, "failed to allocate answer buffer");
+				rc = -ENOMEM;
+				goto out;
+			}
+			memset(answer_list[answer_counter], '\0', a_len + b_len + 8);
+
+			sprintf(answer_list[answer_counter], "%s %s %s", a, expr_list[x], b);
+			push(answer_list[answer_counter++]);
+			free(a);
+			free(b);
+		} else if (strncmp(expr_list[x], "not", 3) == 0) {
+			b = pop();
+			b_len = strlen(b);
+
+			answer_list[answer_counter] = malloc(b_len + 8);
+			if (!answer_list[answer_counter]) {
+				ERR(NULL, "failed to allocate answer buffer");
+				rc = -ENOMEM;
+				goto out;
+			}
+			memset(answer_list[answer_counter], '\0', b_len + 8);
+
+			if (strncmp(b, "not", 3) == 0)
+				sprintf(answer_list[answer_counter], "%s (%s)", expr_list[x], b);
+			else
+				sprintf(answer_list[answer_counter], "%s%s", expr_list[x], b);
+			push(answer_list[answer_counter++]);
+			free(b);
+		} else {
+			push(expr_list[x]);
+		}
+	}
+	/* Get the final answer from tos and build constraint text */
+	a = pop();
+
+	/* Constraint calculation: rc = 0 is denied, rc = 1 is granted */
+	sprintf(tmp_buf,"Constraint %s\n", s[0] ? "GRANTED" : "DENIED");
+
+	int len, new_buf_len;
+	char *p, **new_buf = r_buf;
+	/*
+	 * These contain the constraint components that are added to the
+	 * callers reason buffer.
+	 */
+	char *buffers[] = { class_buf, a, "); ", tmp_buf, 0 };
+
+	/*
+	 * This will add the constraints to the callers reason buffer (who is
+	 * responsible for freeing the memory). It will handle any realloc's
+	 * should the buffer be too short.
+	 * The reason_buf_used and reason_buf_len counters are defined globally
+	 * as multiple constraints can be in the buffer.
+	 */
+	if (r_buf && ((s[0] == 0) || ((s[0] == 1 &&
+				(flags & SHOW_GRANTED) == SHOW_GRANTED)))) {
+		for (x = 0; buffers[x] != NULL; x++) {
+			while (1) {
+				p = *r_buf + reason_buf_used;
+				len = snprintf(p, reason_buf_len - reason_buf_used, "%s", buffers[x]);
+				if (len < 0 || len >= reason_buf_len - reason_buf_used) {
+					new_buf_len = reason_buf_len + REASON_BUF_SIZE;
+					*new_buf = realloc(*r_buf, new_buf_len);
+					if (!new_buf) {
+						ERR(NULL, "failed to realloc reason buffer");
+						goto out1;
+					}
+					**r_buf = **new_buf;
+					reason_buf_len = new_buf_len;
+					continue;
+				} else {
+					reason_buf_used += len;
+					break;
+				}
+			}
+		}
+	}
+
+out1:
+	rc = s[0];
+	free(a);
+
+out:
+	free(class_buf);
+	free(src);
+	free(tgt);
+
+	if (expr_counter) {
+		for (x = 0; expr_list[x] != NULL; x++)
+			free(expr_list[x]);
+	}
+	return rc;
 }
 
 /*
@@ -309,7 +832,9 @@ static int context_struct_compute_av(context_struct_t * scontext,
 				     sepol_security_class_t tclass,
 				     sepol_access_vector_t requested,
 				     struct sepol_av_decision *avd,
-				     unsigned int *reason)
+				     unsigned int *reason,
+				     char **r_buf,
+					 unsigned int flags)
 {
 	constraint_node_t *constraint;
 	struct role_allow *ra;
@@ -384,8 +909,8 @@ static int context_struct_compute_av(context_struct_t * scontext,
 	constraint = tclass_datum->constraints;
 	while (constraint) {
 		if ((constraint->permissions & (avd->allowed)) &&
-		    !constraint_expr_eval(scontext, tcontext, NULL,
-					  constraint->expr)) {
+		    !constraint_expr_eval_reason(scontext, tcontext, NULL,
+					  tclass, constraint, r_buf, flags)) {
 			avd->allowed =
 			    (avd->allowed) & ~(constraint->permissions);
 		}
@@ -460,8 +985,8 @@ int hidden sepol_validate_transition(sepol_security_id_t oldsid,
 
 	constraint = tclass_datum->validatetrans;
 	while (constraint) {
-		if (!constraint_expr_eval(ocontext, ncontext, tcontext,
-					  constraint->expr)) {
+		if (!constraint_expr_eval_reason(ocontext, ncontext, tcontext,
+					  0, constraint, NULL, 0)) {
 			return -EPERM;
 		}
 		constraint = constraint->next;
@@ -494,11 +1019,59 @@ int hidden sepol_compute_av_reason(sepol_security_id_t ssid,
 	}
 
 	rc = context_struct_compute_av(scontext, tcontext, tclass,
-				       requested, avd, reason);
+					requested, avd, reason, NULL, 0);
       out:
 	return rc;
 }
 
+/* 
+ * sepol_compute_av_reason_buffer - the reason buffer is malloc'd to
+ * REASON_BUF_SIZE. If the buffer size is exceeded, then it is realloc'd
+ * in the constraint_expr_eval_reason() function.
+ */
+int hidden sepol_compute_av_reason_buffer(sepol_security_id_t ssid,
+				   sepol_security_id_t tsid,
+				   sepol_security_class_t tclass,
+				   sepol_access_vector_t requested,
+				   struct sepol_av_decision *avd,
+				   unsigned int *reason,
+				   char **reason_buf,
+				   unsigned int flags)
+{
+	*reason_buf = malloc(REASON_BUF_SIZE);
+	if (!*reason_buf) {
+		ERR(NULL, "failed to allocate reason buffer");
+		return -ENOMEM;
+	}
+	/*
+	 * These are defined globally as the buffer can contain multiple
+	 * constraint statements so need to keep track
+	 */
+	reason_buf_used = 0;
+	reason_buf_len = REASON_BUF_SIZE;
+
+	context_struct_t *scontext = 0, *tcontext = 0;
+	int rc = 0;
+
+	scontext = sepol_sidtab_search(sidtab, ssid);
+	if (!scontext) {
+		ERR(NULL, "unrecognized SID %d", ssid);
+		rc = -EINVAL;
+		goto out;
+	}
+	tcontext = sepol_sidtab_search(sidtab, tsid);
+	if (!tcontext) {
+		ERR(NULL, "unrecognized SID %d", tsid);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	rc = context_struct_compute_av(scontext, tcontext, tclass,
+					   requested, avd, reason, reason_buf, flags);
+out:
+	return rc;
+}
+
 int hidden sepol_compute_av(sepol_security_id_t ssid,
 			    sepol_security_id_t tsid,
 			    sepol_security_class_t tclass,
@@ -511,6 +1084,70 @@ int hidden sepol_compute_av(sepol_security_id_t ssid,
 }
 
 /*
+ * Return a class ID associated with the class string specified by
+ * class_name.
+ */
+int hidden sepol_class_name_to_id(const char *class_name,
+			sepol_security_class_t *tclass)
+{
+	char *class = NULL;
+	sepol_security_class_t id;
+
+	for (id = 1; ; id++) {
+		if ((class = policydb->p_class_val_to_name[id - 1]) == NULL) {
+			ERR(NULL, "could not convert %s to class id", class_name);
+			return STATUS_ERR;
+		}
+		if ((strcmp(class, class_name)) == 0) {
+			*tclass = id;
+			return STATUS_SUCCESS;
+		}
+	}
+}
+
+/*
+ * Return access vector bit associated with the class ID and permission
+ * string.
+ */
+int hidden sepol_perm_name_to_av(sepol_security_class_t tclass,
+					const char *perm_name,
+					sepol_access_vector_t *av)
+{
+	class_datum_t *tclass_datum;
+	perm_datum_t *perm_datum;
+
+	if (!tclass || tclass > policydb->p_classes.nprim) {
+		ERR(NULL, "unrecognized class %d", tclass);
+		return -EINVAL;
+	}
+	tclass_datum = policydb->class_val_to_struct[tclass - 1];
+
+	/* Check for unique perms then the common ones (if any) */
+	perm_datum = (perm_datum_t *)
+			hashtab_search(tclass_datum->permissions.table,
+			(hashtab_key_t)perm_name);
+	if (perm_datum != NULL) {
+		*av = 0x1 << (perm_datum->s.value - 1);
+		return STATUS_SUCCESS;
+	}
+
+	if (tclass_datum->comdatum == NULL)
+	   	goto out;
+
+	perm_datum = (perm_datum_t *)
+			hashtab_search(tclass_datum->comdatum->permissions.table,
+			(hashtab_key_t)perm_name);
+
+	if (perm_datum != NULL) {
+		*av = 0x1 << (perm_datum->s.value - 1);
+		return STATUS_SUCCESS;
+	}
+out:	
+	ERR(NULL, "could not convert %s to av bit", perm_name);
+   	return STATUS_ERR;
+}
+
+/*
  * Write the security context string representation of 
  * the context associated with `sid' into a dynamically
  * allocated string of the correct size.  Set `*scontext'
@@ -1339,7 +1976,7 @@ int hidden sepol_get_user_sids(sepol_security_id_t fromsid,
 			rc = context_struct_compute_av(fromcon, &usercon,
 						       SECCLASS_PROCESS,
 						       PROCESS__TRANSITION,
-						       &avd, &reason);
+						       &avd, &reason, NULL, 0);
 			if (rc || !(avd.allowed & PROCESS__TRANSITION))
 				continue;
 			rc = sepol_sidtab_context_to_sid(sidtab, &usercon,
diff --git a/libsepol/src/write.c b/libsepol/src/write.c
index 55992f8..6fe73e6 100644
--- a/libsepol/src/write.c
+++ b/libsepol/src/write.c
@@ -893,8 +893,11 @@ static int write_cons_helper(policydb_t * p,
 				if (ebitmap_write(&e->names, fp)) {
 					return POLICYDB_ERROR;
 				}
-				if (p->policy_type != POLICY_KERN &&
-				    type_set_write(e->type_names, fp)) {
+				if ((p->policy_type != POLICY_KERN &&
+						type_set_write(e->type_names, fp)) ||
+						(p->policy_type == POLICY_KERN &&
+						(p->policyvers >= POLICYDB_VERSION_CONSTRAINT_NAMES) &&
+						type_set_write(e->type_names, fp))) {
 					return POLICYDB_ERROR;
 				}
 				break;
diff --git a/policycoreutils/audit2allow/audit2allow b/policycoreutils/audit2allow/audit2allow
index 8e0c396..b18cef7 100644
--- a/policycoreutils/audit2allow/audit2allow
+++ b/policycoreutils/audit2allow/audit2allow
@@ -267,12 +267,10 @@ class AuditToPolicy:
                     continue
 
                 if rc == audit2why.CONSTRAINT:
-                    print "\t\tPolicy constraint violation.\n"
-                    print "\t\tMay require adding a type attribute to the domain or type to satisfy the constraint.\n"
-                    print "\t\tConstraints are defined in the policy sources in policy/constraints (general), policy/mcs (MCS), and policy/mls (MLS).\n"
-                    for reason in data:
-                        print "\t\tNote: Possible cause is the source and target %s differ\n" % reason
-                    continue
+                    print #!!!! This avc is a constraint violation.  You would need to modify the attributes of either the source or target types to allow this access.\n"
+                    print "#Constraint rule: \n\t" + data[0]
+                    for reason in data[1:]:
+                        print "#\tPossible cause is the source %s and target %s are different.\n\b" % reason
 
                 if rc == audit2why.RBAC:
                     print "\t\tMissing role allow rule.\n"
@@ -350,6 +348,9 @@ class AuditToPolicy:
         except ValueError, e:
             print e
             sys.exit(1)
+        except IOError, e:
+            print e
+            sys.exit(1)
 
 if __name__ == "__main__":
     app = AuditToPolicy()
diff --git a/sepolgen/src/sepolgen/audit.py b/sepolgen/src/sepolgen/audit.py
index d636091..56919be 100644
--- a/sepolgen/src/sepolgen/audit.py
+++ b/sepolgen/src/sepolgen/audit.py
@@ -259,13 +259,13 @@ class AVCMessage(AuditMessage):
                 raise ValueError("Error during access vector computation")
 
             if self.type == audit2why.CONSTRAINT:
-                self.data = []
+                self.data = [ self.data ]
                 if self.scontext.user != self.tcontext.user:
-                    self.data.append("user")
+                    self.data.append(("user (%s)" % self.scontext.user, 'user (%s)' % self.tcontext.user))
                 if self.scontext.role != self.tcontext.role and self.tcontext.role != "object_r":
-                    self.data.append("role")
+                    self.data.append(("role (%s)" % self.scontext.role, 'role (%s)' % self.tcontext.role))
                 if self.scontext.level != self.tcontext.level:
-                    self.data.append("level")
+                    self.data.append(("level (%s)" % self.scontext.level, 'level (%s)' % self.tcontext.level))
 
             avcdict[(scontext, tcontext, self.tclass, access_tuple)] = (self.type, self.data)
 
diff --git a/sepolgen/src/sepolgen/policygen.py b/sepolgen/src/sepolgen/policygen.py
index cc9f8ea..ce643e5 100644
--- a/sepolgen/src/sepolgen/policygen.py
+++ b/sepolgen/src/sepolgen/policygen.py
@@ -161,21 +161,21 @@ class PolicyGenerator:
             if self.explain:
                 rule.comment = str(refpolicy.Comment(explain_access(av, verbosity=self.explain)))
             if av.type == audit2why.ALLOW:
-                rule.comment += "#!!!! This avc is allowed in the current policy\n"
+                rule.comment += "\n#!!!! This avc is allowed in the current policy"
             if av.type == audit2why.DONTAUDIT:
-                rule.comment += "#!!!! This avc has a dontaudit rule in the current policy\n"
+                rule.comment += "\n#!!!! This avc has a dontaudit rule in the current policy"
 
             if av.type == audit2why.BOOLEAN:
                 if len(av.data) > 1:
-                    rule.comment += "#!!!! This avc can be allowed using one of the these booleans:\n#     %s\n" % ", ".join(map(lambda x: x[0], av.data))
+                    rule.comment += "\n#!!!! This avc can be allowed using one of the these booleans:\n#     %s" % ", ".join(map(lambda x: x[0], av.data))
                 else:
-                    rule.comment += "#!!!! This avc can be allowed using the boolean '%s'\n" % av.data[0][0]
+                    rule.comment += "\n#!!!! This avc can be allowed using the boolean '%s'" % av.data[0][0]
 
             if av.type == audit2why.CONSTRAINT:
-                rule.comment += "#!!!! This avc is a constraint violation.  You will need to add an attribute to either the source or target type to make it work.\n"
-                rule.comment += "#Constraint rule: "
-                for reason in av.data:
-                    rule.comment += "\n#\tPossible cause source context and target context '%s' differ\b" % reason
+                rule.comment += "\n#!!!! This avc is a constraint violation.  You would need to modify the attributes of either the source or target types to allow this access.\n"
+                rule.comment += "#Constraint rule: \n\t" + av.data[0]
+                for reason in av.data[1:]:
+                    rule.comment += "#\tPossible cause is the source %s and target %s are different." % reason
 
             try:
                 if ( av.type == audit2why.TERULE and
@@ -189,9 +189,9 @@ class PolicyGenerator:
                         if i not in self.domains:
                             types.append(i)
                     if len(types) == 1:
-                        rule.comment += "#!!!! The source type '%s' can write to a '%s' of the following type:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types))
+                        rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following type:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types))
                     elif len(types) >= 1:
-                        rule.comment += "#!!!! The source type '%s' can write to a '%s' of the following types:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types))
+                        rule.comment += "\n#!!!! The source type '%s' can write to a '%s' of the following types:\n# %s\n" % ( av.src_type, av.obj_class, ", ".join(types))
             except:
                 pass
             self.module.children.append(rule)
-- 
1.8.3.1

