[PATCH 2/2] libsepol: Validate scope index class_perm_map array

James Carter <[email protected]> Fri, 24 Jul 2026 14:29:08 -0400
Newsgroups org.kernel.vger.selinux
Message-ID <[email protected]>
In addtion to the array of ebitmaps that shows which declarations
are in scope for a given block, the scope index also contains an
array of ebitmaps, class_perms_map, that shows which permissions
are in scope for each class and it contains the length of that
array.

If the class_perms_map is NULL, then verify that class_perms_len
is 0. If the class_perms_map is not NULL, then verify that the
class_perms_len is not 0 and that the array is long enough to
contain all of the classes that are in scope for the block and
that for each class in the array either the permission ebitmap
is empty or it contains at least one valid permission. [Older
compilers could set all the bits if "*" was used.]

Signed-off-by: James Carter <[email protected]>
---
v3: - Stop differentiating between required and declared blocks and
      instead differentiate between NULL and non-NULL class_perms_map
    - Remove the added bool argument "required"

 libsepol/src/policydb_validate.c | 45 +++++++++++++++++++++++++++-----
 1 file changed, 38 insertions(+), 7 deletions(-)

diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
index 1d8e0870..fc66a4c7 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -1713,10 +1713,8 @@ bad:
 
 static int validate_scope_index(sepol_handle_t *handle,
 				const scope_index_t *scope_index,
-				validate_t flavors[])
+				const policydb_t *p, validate_t flavors[])
 {
-	uint32_t i;
-
 	if (!ebitmap_is_empty(&scope_index->scope[SYM_COMMONS]))
 		goto bad;
 	if (validate_ebitmap(&scope_index->p_classes_scope,
@@ -1735,9 +1733,42 @@ static int validate_scope_index(sepol_handle_t *handle,
 	if (validate_ebitmap(&scope_index->p_cat_scope, &flavors[SYM_CATS]))
 		goto bad;
 
-	for (i = 0; i < scope_index->class_perms_len; i++)
-		if (validate_value(i + 1, &flavors[SYM_CLASSES]))
+	if (scope_index->class_perms_map != NULL) {
+		uint32_t i;
+		if (scope_index->class_perms_len == 0)
+			goto bad;
+		if (ebitmap_highest_set_bit(&scope_index->p_classes_scope) >=
+		    scope_index->class_perms_len)
 			goto bad;
+		for (i = 0; i < scope_index->class_perms_len; i++) {
+			const ebitmap_t *map;
+			class_datum_t *class;
+			ebitmap_node_t *node;
+			unsigned int bit = 0;
+			if (validate_value(i + 1, &flavors[SYM_CLASSES]))
+				goto bad;
+			map = &scope_index->class_perms_map[i];
+			class = p->class_val_to_struct[i];
+			/* Either there are no perms */
+			if (ebitmap_is_empty(map))
+				continue;
+			/* Or at least one valid perm */
+			ebitmap_for_each_positive_bit(map, node, bit) {
+				if (bit < class->permissions.nprim)
+					break;
+			}
+			if (bit >= class->permissions.nprim)
+				goto bad;
+		}
+	} else {
+		/* This is the normal branch for declared blocks.
+		 * Base module required blocks should never be in this branch.
+		 * Module required blocks can be if neither its nor the module's
+		 * require block has a class in it.
+		 */
+		if (scope_index->class_perms_len != 0)
+			goto bad;
+	}
 
 	return 0;
 
@@ -1811,10 +1842,10 @@ static int validate_avrule_blocks(sepol_handle_t *handle,
 			if (validate_range_trans_rules(
 				    handle, decl->range_tr_rules, flavors))
 				goto bad;
-			if (validate_scope_index(handle, &decl->required,
+			if (validate_scope_index(handle, &decl->required, p,
 						 flavors))
 				goto bad;
-			if (validate_scope_index(handle, &decl->declared,
+			if (validate_scope_index(handle, &decl->declared, p,
 						 flavors))
 				goto bad;
 			if (validate_filename_trans_rules(
-- 
2.55.0