Commits

Stephen Smalley  committed 6617731 Merge

Merge branch 'upstream'

Comments (0)

Files changed (19)

+2.1.4 2011-10-03
+	* regenerate .pc on VERSION change
+	* Move ebitmap_* functions from mcstrans to libsepol
+	* expand: do filename_trans type comparison on mapped representation
+
+2.1.3 2011-09-15
+	* Skip writing role attributes for policy.X and
+	* Indicate when boolean is indeed a tunable.
+	* Separate tunable from boolean during compile.
+	* Write and read TUNABLE flags in related
+	* Copy and check the cond_bool_datum_t.flags during link.
+	* Permanently discard disabled branches of tunables in
+	* Skip tunable identifier and cond_node_t in expansion.
+	* Create a new preserve_tunables flag
+	* Preserve tunables when required by semodule program.
+	* setools expects expand_module_avrules to be an exported
+	* tree: default make target to all not
+
+2.1.2 2011-08-03
+	* Only call role_fix_callback for base.p_roles during expansion.
+	* use mapped role number instead of module role number
+
+2.1.1 2011-08-01
+	* Minor fix to reading policy with filename transition rules
+
 2.1.0 2011-07-27
 	* Release, minor version bump
 
-2.1.0
+2.1.4

File include/Makefile

 PREFIX ?= $(DESTDIR)/usr
 INCDIR ?= $(PREFIX)/include/sepol
 
-install:
+all:
+
+install: all
 	test -d $(INCDIR) || install -m 755 -d $(INCDIR)
 	test -d $(INCDIR)/policydb || install -m 755 -d $(INCDIR)/policydb
 	install -m 644 $(wildcard sepol/*.h) $(INCDIR)

File include/sepol/handle.h

 /* Destroy a sepol handle. */
 void sepol_handle_destroy(sepol_handle_t *);
 
+/* Get whether or not needless unused branch of tunables would be preserved */
+int sepol_get_preserve_tunables(sepol_handle_t * sh);
+
+/* Set whether or not to preserve the needless unused branch of tunables,
+ * 0 is default and discard such branch, 1 preserves them */
+void sepol_set_preserve_tunables(sepol_handle_t * sh, int preserve_tunables);
+
 #endif

File include/sepol/policydb/conditional.h

 	/* these true/false lists point into te_avtab when that is used */
 	cond_av_list_t *true_list;
 	cond_av_list_t *false_list;
-	/* and these are using during parsing and for modules */
+	/* and these are used during parsing and for modules */
 	avrule_t *avtrue_list;
 	avrule_t *avfalse_list;
 	/* these fields are not written to binary policy */
 	unsigned int nbools;
 	uint32_t bool_ids[COND_MAX_BOOLS];
 	uint32_t expr_pre_comp;
-	/*                                               */
 	struct cond_node *next;
+	/* a tunable conditional, calculated and used at expansion */
+#define	COND_NODE_FLAGS_TUNABLE	0x01
+	uint32_t flags;
 } cond_node_t;
 
 extern int cond_evaluate_expr(policydb_t * p, cond_expr_t * expr);

File include/sepol/policydb/ebitmap.h

 extern int ebitmap_cmp(const ebitmap_t * e1, const ebitmap_t * e2);
 extern int ebitmap_or(ebitmap_t * dst, const ebitmap_t * e1, const ebitmap_t * e2);
 extern int ebitmap_union(ebitmap_t * dst, const ebitmap_t * e1);
+extern int ebitmap_and(ebitmap_t *dst, ebitmap_t *e1, ebitmap_t *e2);
+extern int ebitmap_xor(ebitmap_t *dst, ebitmap_t *e1, ebitmap_t *e2);
+extern int ebitmap_not(ebitmap_t *dst, ebitmap_t *e1, unsigned int maxbit);
+extern int ebitmap_andnot(ebitmap_t *dst, ebitmap_t *e1, ebitmap_t *e2, unsigned int maxbit);
+extern unsigned int ebitmap_cardinality(ebitmap_t *e1);
+extern int ebitmap_hamming_distance(ebitmap_t * e1, ebitmap_t * e2);
 extern int ebitmap_cpy(ebitmap_t * dst, const ebitmap_t * src);
 extern int ebitmap_contains(const ebitmap_t * e1, const ebitmap_t * e2);
 extern int ebitmap_get_bit(const ebitmap_t * e, unsigned int bit);

File include/sepol/policydb/policydb.h

 typedef struct cond_bool_datum {
 	symtab_datum_t s;
 	int state;
+#define COND_BOOL_FLAGS_TUNABLE	0x01	/* is this a tunable? */
+	uint32_t flags;
 } cond_bool_datum_t;
 
 struct cond_node;
 #define MOD_POLICYDB_VERSION_FILENAME_TRANS	11
 #define MOD_POLICYDB_VERSION_ROLETRANS		12
 #define MOD_POLICYDB_VERSION_ROLEATTRIB		13
+#define MOD_POLICYDB_VERSION_TUNABLE_SEP	14
 
 #define MOD_POLICYDB_VERSION_MIN MOD_POLICYDB_VERSION_BASE
-#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_ROLEATTRIB
+#define MOD_POLICYDB_VERSION_MAX MOD_POLICYDB_VERSION_TUNABLE_SEP
 
 #define POLICYDB_CONFIG_MLS    1
 

File man/Makefile

 MAN8DIR ?= $(DESTDIR)/usr/share/man/man8
 MAN3DIR ?= $(DESTDIR)/usr/share/man/man3
 
-install:
+all:
+
+install: all
 	mkdir -p $(MAN3DIR)
 	mkdir -p $(MAN8DIR)
 	install -m 644 man3/*.3 $(MAN3DIR)

File src/Makefile

 	$(CC) $(CFLAGS) $(LDFLAGS) -shared -o $@ $^ -Wl,-soname,$(LIBSO),--version-script=libsepol.map,-z,defs
 	ln -sf $@ $(TARGET) 
 
-$(LIBPC): $(LIBPC).in
+$(LIBPC): $(LIBPC).in ../VERSION
 	sed -e 's/@VERSION@/$(VERSION)/; s:@prefix@:$(PREFIX):; s:@libdir@:$(LIBBASE):; s:@includedir@:$(INCLUDEDIR):' < $< > $@
 
 %.o:  %.c 

File src/conditional.c

 		for (i = 0; i < min(node->nbools, COND_MAX_BOOLS); i++)
 			new_node->bool_ids[i] = node->bool_ids[i];
 		new_node->expr_pre_comp = node->expr_pre_comp;
+		new_node->flags = node->flags;
 	}
 
 	return new_node;
 	return 1;
 }
 
-int cond_read_bool(policydb_t * p
-		   __attribute__ ((unused)), hashtab_t h,
+int cond_read_bool(policydb_t * p,
+		   hashtab_t h,
 		   struct policy_file *fp)
 {
 	char *key = 0;
 	if (rc < 0)
 		goto err;
 	key[len] = 0;
+
+	if (p->policy_type != POLICY_KERN &&
+	    p->policyvers >= MOD_POLICYDB_VERSION_TUNABLE_SEP) {
+		rc = next_entry(buf, fp, sizeof(uint32_t));
+		if (rc < 0)
+			goto err;
+		booldatum->flags = le32_to_cpu(buf[0]);
+	}
+
 	if (hashtab_insert(h, key, booldatum))
 		goto err;
 
 			goto err;
 	}
 
+	if (p->policy_type != POLICY_KERN &&
+	    p->policyvers >= MOD_POLICYDB_VERSION_TUNABLE_SEP) {
+		rc = next_entry(buf, fp, sizeof(uint32_t));
+		if (rc < 0)
+			goto err;
+		node->flags = le32_to_cpu(buf[0]);
+	}
+
 	return 0;
       err:
 	cond_node_destroy(node);

File src/ebitmap.c

 	return 0;
 }
 
+int ebitmap_and(ebitmap_t *dst, ebitmap_t *e1, ebitmap_t *e2)
+{
+	unsigned int i, length = min(ebitmap_length(e1), ebitmap_length(e2));
+	ebitmap_init(dst);
+	for (i=0; i < length; i++) {
+		if (ebitmap_get_bit(e1, i) && ebitmap_get_bit(e2, i)) {
+			int rc = ebitmap_set_bit(dst, i, 1);
+			if (rc < 0)
+				return rc;
+		}
+	}
+	return 0;
+}
+
+int ebitmap_xor(ebitmap_t *dst, ebitmap_t *e1, ebitmap_t *e2)
+{
+	unsigned int i, length = max(ebitmap_length(e1), ebitmap_length(e2));
+	ebitmap_init(dst);
+	for (i=0; i < length; i++) {
+		int val = ebitmap_get_bit(e1, i) ^ ebitmap_get_bit(e2, i);
+		int rc = ebitmap_set_bit(dst, i, val);
+		if (rc < 0)
+			return rc;
+	}
+	return 0;
+}
+
+int ebitmap_not(ebitmap_t *dst, ebitmap_t *e1, unsigned int maxbit)
+{
+	unsigned int i;
+	ebitmap_init(dst);
+	for (i=0; i < maxbit; i++) {
+		int val = ebitmap_get_bit(e1, i);
+		int rc = ebitmap_set_bit(dst, i, !val);
+		if (rc < 0)
+			return rc;
+	}
+	return 0;
+}
+
+int ebitmap_andnot(ebitmap_t *dst, ebitmap_t *e1, ebitmap_t *e2, unsigned int maxbit)
+{
+	ebitmap_t e3;
+	ebitmap_init(dst);
+	int rc = ebitmap_not(&e3, e2, maxbit);
+	if (rc < 0)
+		return rc;
+	rc = ebitmap_and(dst, e1, &e3);
+	ebitmap_destroy(&e3);
+	if (rc < 0)
+		return rc;
+	return 0;
+}
+
+unsigned int ebitmap_cardinality(ebitmap_t *e1)
+{
+	unsigned int i, count = 0;
+	for (i=ebitmap_startbit(e1); i < ebitmap_length(e1); i++)
+		if (ebitmap_get_bit(e1, i))
+			count++;
+	return count;
+}
+
+int ebitmap_hamming_distance(ebitmap_t * e1, ebitmap_t * e2)
+{
+	if (ebitmap_cmp(e1, e2))
+		return 0;
+	ebitmap_t tmp;
+	int rc = ebitmap_xor(&tmp, e1, e2);
+	if (rc < 0)
+		return -1;
+	int distance = ebitmap_cardinality(&tmp);
+	ebitmap_destroy(&tmp);
+	return distance;
+}
+
 int ebitmap_cmp(const ebitmap_t * e1, const ebitmap_t * e2)
 {
 	ebitmap_node_t *n1, *n2;

File src/expand.c

 		return 0;
 	}
 
+	if (bool->flags & COND_BOOL_FLAGS_TUNABLE) {
+		/* Skip tunables */
+		return 0;
+	}
+
 	if (state->verbose)
 		INFO(state->handle, "copying boolean %s", id);
 
 	state->boolmap[bool->s.value - 1] = new_bool->s.value;
 
 	new_bool->state = bool->state;
+	new_bool->flags = bool->flags;
 
 	return 0;
 }
 
 					cur_trans = state->out->role_tr;
 					while (cur_trans) {
+						unsigned int mapped_role;
+
+						mapped_role = state->rolemap[cur->new_role - 1];
+
 						if ((cur_trans->role ==
 								i + 1) &&
 						    (cur_trans->type ==
 								j + 1) &&
 						    (cur_trans->tclass ==
 								k + 1)) {
-							if (cur_trans->
-							    new_role ==
-								cur->new_role) {
+							if (cur_trans->new_role == mapped_role) {
 								break;
 							} else {
 								ERR(state->handle,
-									"Conflicting role trans rule %s %s : %s %s",
+									"Conflicting role trans rule %s %s : %s { %s vs %s }",
 									state->out->p_role_val_to_name[i],
 									state->out->p_type_val_to_name[j],
 									state->out->p_class_val_to_name[k],
-									state->out->p_role_val_to_name[cur->new_role - 1]);
+									state->out->p_role_val_to_name[mapped_role - 1],
+									state->out->p_role_val_to_name[cur_trans->new_role - 1]);
 								return -1;
 							}
 						}
 
 	cur_rule = rules;
 	while (cur_rule) {
+		uint32_t mapped_otype;
+
 		ebitmap_init(&stypes);
 		ebitmap_init(&ttypes);
 
 			return -1;
 		}
 
+		mapped_otype = state->typemap[cur_rule->otype - 1];
+
 		ebitmap_for_each_bit(&stypes, snode, i) {
 			if (!ebitmap_node_get_bit(snode, i))
 				continue;
 					    (cur_trans->tclass == cur_rule->tclass) &&
 					    (!strcmp(cur_trans->name, cur_rule->name))) {
 						/* duplicate rule, who cares */
-						if (cur_trans->otype == cur_rule->otype)
+						if (cur_trans->otype == mapped_otype)
 							break;
 
 						ERR(state->handle, "Conflicting filename trans rules %s %s %s : %s otype1:%s otype2:%s",
 						    state->out->p_type_val_to_name[j],
 						    state->out->p_class_val_to_name[cur_trans->tclass - 1],
 						    state->out->p_type_val_to_name[cur_trans->otype - 1],
-						    state->out->p_type_val_to_name[state->typemap[cur_rule->otype - 1] - 1]);
+						    state->out->p_type_val_to_name[mapped_otype - 1]);
 						    
 						return -1;
 					}
 				new_trans->stype = i + 1;
 				new_trans->ttype = j + 1;
 				new_trans->tclass = cur_rule->tclass;
-				new_trans->otype = state->typemap[cur_rule->otype - 1];
+				new_trans->otype = mapped_otype;
 			}
 		}
 
 	if (cond_node_copy(state, cn->next)) {
 		return -1;
 	}
+
+	/* If current cond_node_t is of tunable, its effective branch
+	 * has been appended to its home decl->avrules list during link
+	 * and now we should just skip it. */
+	if (cn->flags & COND_NODE_FLAGS_TUNABLE)
+		return 0;
+
 	if (cond_normalize_expr(state->base, cn)) {
 		ERR(state->handle, "Error while normalizing conditional");
 		return -1;
 	return copy_and_expand_avrule_block(&state);
 }
 
+static void discard_tunables(sepol_handle_t *sh, policydb_t *pol)
+{
+	avrule_block_t *block;
+	avrule_decl_t *decl;
+	cond_node_t *cur_node;
+	cond_expr_t *cur_expr;
+	int cur_state, preserve_tunables = 0;
+	avrule_t *tail, *to_be_appended;
+
+	if (sh && sh->preserve_tunables)
+		preserve_tunables = 1;
+
+	/* Iterate through all cond_node of all enabled decls, if a cond_node
+	 * is about tunable, calculate its state value and concatenate one of
+	 * its avrule list to the current decl->avrules list. On the other
+	 * hand, the disabled unused branch of a tunable would be discarded.
+	 *
+	 * Note, such tunable cond_node would be skipped over in expansion,
+	 * so we won't have to worry about removing it from decl->cond_list
+	 * here :-)
+	 *
+	 * If tunables are requested to be preserved then they would be
+	 * "transformed" as booleans by having their TUNABLE flag cleared.
+	 */
+	for (block = pol->global; block != NULL; block = block->next) {
+		decl = block->enabled;
+		if (decl == NULL || decl->enabled == 0)
+			continue;
+
+		tail = decl->avrules;
+		while (tail && tail->next)
+			tail = tail->next;
+
+		for (cur_node = decl->cond_list; cur_node != NULL;
+		     cur_node = cur_node->next) {
+			int booleans, tunables, i;
+			cond_bool_datum_t *booldatum;
+			cond_bool_datum_t *tmp[COND_EXPR_MAXDEPTH];
+
+			booleans = tunables = 0;
+			memset(tmp, 0, sizeof(cond_bool_datum_t *) * COND_EXPR_MAXDEPTH);
+
+			for (cur_expr = cur_node->expr; cur_expr != NULL;
+			     cur_expr = cur_expr->next) {
+				if (cur_expr->expr_type != COND_BOOL)
+					continue;
+				booldatum = pol->bool_val_to_struct[cur_expr->bool - 1];
+				if (booldatum->flags & COND_BOOL_FLAGS_TUNABLE)
+					tmp[tunables++] = booldatum;
+				else
+					booleans++;
+			}
+
+			/* bool_copy_callback() at link phase has ensured
+			 * that no mixture of tunables and booleans in one
+			 * expression. However, this would be broken by the
+			 * request to preserve tunables */
+			if (!preserve_tunables)
+				assert(!(booleans && tunables));
+
+			if (booleans || preserve_tunables) {
+				cur_node->flags &= ~COND_NODE_FLAGS_TUNABLE;
+				if (tunables) {
+					for (i = 0; i < tunables; i++)
+						tmp[i]->flags &= ~COND_BOOL_FLAGS_TUNABLE;
+				}
+			} else {
+				cur_node->flags |= COND_NODE_FLAGS_TUNABLE;
+				cur_state = cond_evaluate_expr(pol, cur_node->expr);
+				if (cur_state == -1) {
+					printf("Expression result was "
+					       "undefined, skipping all"
+					       "rules\n");
+					continue;
+				}
+
+				to_be_appended = (cur_state == 1) ?
+					cur_node->avtrue_list : cur_node->avfalse_list;
+
+				if (tail)
+					tail->next = to_be_appended;
+				else
+					tail = decl->avrules = to_be_appended;
+
+				/* Now that the effective branch has been
+				 * appended, neutralize its original pointer */
+				if (cur_state == 1)
+					cur_node->avtrue_list = NULL;
+				else
+					cur_node->avfalse_list = NULL;
+
+				/* Update the tail of decl->avrules for
+				 * further concatenation */
+				while (tail && tail->next)
+					tail = tail->next;
+			}
+		}
+	}
+}
+
 /* Linking should always be done before calling expand, even if
  * there is only a base since all optionals are dealt with at link time
  * the base passed in should be indexed and avrule blocks should be 
 	expand_state_t state;
 	avrule_block_t *curblock;
 
+	/* Append tunable's avtrue_list or avfalse_list to the avrules list
+	 * of its home decl depending on its state value, so that the effect
+	 * rules of a tunable would be added to te_avtab permanently. Whereas
+	 * the disabled unused branch would be discarded.
+	 *
+	 * Originally this function is called at the very end of link phase,
+	 * however, we need to keep the linked policy intact for analysis
+	 * purpose. */
+	discard_tunables(handle, base);
+
 	expand_state_init(&state);
 
 	state.verbose = verbose;
 		if (hashtab_map
 		    (decl->p_roles.table, role_copy_callback, &state))
 			goto cleanup;
-		if (hashtab_map
-		    (decl->p_roles.table, role_fix_callback, &state))
-			goto cleanup;
 
 		/* copy users */
 		if (hashtab_map

File src/handle.c

 	sh->disable_dontaudit = 0;
 	sh->expand_consume_base = 0;
 
+	/* by default needless unused branch of tunables would be discarded  */
+	sh->preserve_tunables = 0;
+
 	return sh;
 }
 
+int sepol_get_preserve_tunables(sepol_handle_t *sh)
+{
+	assert(sh != NULL);
+	return sh->preserve_tunables;
+}
+
+void sepol_set_preserve_tunables(sepol_handle_t * sh, int preserve_tunables)
+{
+	assert(sh !=NULL);
+	sh->preserve_tunables = preserve_tunables;
+}
+
 int sepol_get_disable_dontaudit(sepol_handle_t *sh)
 {
 	assert(sh !=NULL);

File src/handle.h

 
 	int disable_dontaudit;
 	int expand_consume_base;
-
+	int preserve_tunables;
 };
 
 #endif

File src/libsepol.map

 {
   global: 
+	expand_module_avrules;
 	sepol_module_package_*; sepol_link_modules; sepol_expand_module; sepol_link_packages;
 	sepol_bool_*; sepol_genbools*; 
 	sepol_context_*; sepol_mls_*; sepol_check_context;
 	sepol_get_disable_dontaudit;
 	sepol_set_disable_dontaudit;
 	sepol_set_expand_consume_base;
+	sepol_get_preserve_tunables; sepol_set_preserve_tunables;
   local: *;
 };
 		}
 		state->base->p_bools.nprim++;
 		base_bool = new_bool;
-
+		base_bool->flags = booldatum->flags;
+	} else if ((booldatum->flags & COND_BOOL_FLAGS_TUNABLE) !=
+		   (base_bool->flags & COND_BOOL_FLAGS_TUNABLE)) {
+			/* A mismatch between boolean/tunable declaration
+			 * and usage(for example a boolean used in the
+			 * tunable_policy() or vice versa).
+			 *
+			 * This is not allowed and bail out with errors */
+			ERR(state->handle,
+			    "%s: Mismatch between boolean/tunable definition "
+			    "and usage for %s", state->cur_mod_name, id);
+			return -1;
 	}
 
 	/* Get the scope info for this boolean to see if this is the declaration, 
 	scope = hashtab_search(state->cur->policy->p_bools_scope.table, id);
 	if (!scope)
 		return SEPOL_ERR;
-	if (scope->scope == SCOPE_DECL)  
+	if (scope->scope == SCOPE_DECL) {
 		base_bool->state = booldatum->state;
-
+		/* Only the declaration rather than requirement
+		 * decides if it is a boolean or tunable. */
+		base_bool->flags = booldatum->flags;
+	}
 	state->cur->map[SYM_BOOLS][booldatum->s.value - 1] = base_bool->s.value;
 	return 0;
 

File src/policydb.c

 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
 	{
+	 .type = POLICY_BASE,
+	 .version = MOD_POLICYDB_VERSION_TUNABLE_SEP,
+	 .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,
 	 .ocon_num = 0,
 	 .target_platform = SEPOL_TARGET_SELINUX,
 	},
+	{
+	 .type = POLICY_MOD,
+	 .version = MOD_POLICYDB_VERSION_TUNABLE_SEP,
+	 .sym_num = SYM_NUM,
+	 .ocon_num = 0,
+	 .target_platform = SEPOL_TARGET_SELINUX,
+	},
 };
 
 #if 0
 			lft->next = ft;
 		else
 			*t = ft;
+		lft = ft;
 		rc = next_entry(buf, fp, sizeof(uint32_t));
 		if (rc < 0)
 			return -1;

File src/private.h

 #undef min
 #define min(a,b) (((a) < (b)) ? (a) : (b))
 
+#undef max
+#define max(a,b) ((a) >= (b) ? (a) : (b))
+
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
 
 /* Policy compatibility information. */
 	unsigned int items, items2;
 	struct policy_data *pd = ptr;
 	struct policy_file *fp = pd->fp;
+	struct policydb *p = pd->p;
 
 	booldatum = (cond_bool_datum_t *) datum;
 
 	items = put_entry(key, 1, len, fp);
 	if (items != len)
 		return POLICYDB_ERROR;
+
+	if (p->policy_type != POLICY_KERN &&
+	    p->policyvers >= MOD_POLICYDB_VERSION_TUNABLE_SEP) {
+		buf[0] = cpu_to_le32(booldatum->flags);
+		items = put_entry(buf, sizeof(uint32_t), 1, fp);
+		if (items != 1)
+			return POLICYDB_ERROR;
+	}
+
 	return POLICYDB_SUCCESS;
 }
 
 			return POLICYDB_ERROR;
 	}
 
+	if (p->policy_type != POLICY_KERN &&
+	    p->policyvers >= MOD_POLICYDB_VERSION_TUNABLE_SEP) {
+		buf[0] = cpu_to_le32(node->flags);
+		items = put_entry(buf, sizeof(uint32_t), 1, fp);
+		if (items != 1)
+			return POLICYDB_ERROR;
+	}
+
 	return POLICYDB_SUCCESS;
 }
 
 
 	role = (role_datum_t *) datum;
 
+	/*
+	 * Role attributes are redundant for policy.X, skip them
+	 * when writing the roles symbol table. They are also skipped
+	 * when pp is downgraded.
+	 *
+	 * Their numbers would be deducted in policydb_write().
+	 */
+	if ((role->flavor == ROLE_ATTRIB) &&
+	    ((p->policy_type == POLICY_KERN) ||
+	     (p->policy_type != POLICY_KERN &&
+	      p->policyvers < MOD_POLICYDB_VERSION_ROLEATTRIB)))
+		return POLICYDB_SUCCESS;
+
 	len = strlen(key);
 	items = 0;
 	buf[items++] = cpu_to_le32(len);
 	return 0;
 }
 
+static int role_attr_uncount(hashtab_key_t key __attribute__ ((unused)),
+			     hashtab_datum_t datum, void *args)
+{
+	role_datum_t *role = datum;
+	uint32_t *p_nel = args;
+
+	if (role->flavor == ROLE_ATTRIB) {
+		/* uncount attribute from total number of roles */
+		(*p_nel)--;
+	}
+	return 0;
+}
+
 /*
  * Write the configuration data in a policy database
  * structure to a policy database binary representation
 	num_syms = info->sym_num;
 	for (i = 0; i < num_syms; i++) {
 		buf[0] = cpu_to_le32(p->symtab[i].nprim);
-		buf[1] = cpu_to_le32(p->symtab[i].table->nel);
+		buf[1] = p->symtab[i].table->nel;
 
 		/*
 		 * A special case when writing type/attribute symbol table.
 		    p->policy_type == POLICY_KERN) {
 			hashtab_map(p->symtab[i].table, type_attr_uncount, &buf[1]);
 		}
+
+		/*
+		 * Another special case when writing role/attribute symbol
+		 * table, role attributes are redundant for policy.X, or
+		 * when the pp's version is not big enough. So deduct
+		 * their numbers from p_roles.table->nel.
+		 */
+		if ((i == SYM_ROLES) &&
+		    ((p->policy_type == POLICY_KERN) ||
+		     (p->policy_type != POLICY_KERN &&
+		      p->policyvers < MOD_POLICYDB_VERSION_ROLEATTRIB)))
+			hashtab_map(p->symtab[i].table, role_attr_uncount, &buf[1]);
+
+		buf[1] = cpu_to_le32(buf[1]);
 		items = put_entry(buf, sizeof(uint32_t), 2, fp);
 		if (items != 2)
 			return POLICYDB_ERROR;