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

James Carter <[email protected]> Thu, 23 Jul 2026 15:18:05 -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.

An optional block has a required scope. For a required scope validate
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 (the class is not required
in the block) or contains at least one valid permission. [Older
compilers could set all the bits if "*" was used.]

For declared scopes, verify that the length of the class permissions
ebitmap array is 0 and the array itself is NULL.

Signed-off-by: James Carter <[email protected]>
---
v2: Move assignment of class and map to after the validate_value() check
    to make sanitizers happy.

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

diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
index 166e20a5..2d7c3e71 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -1690,10 +1690,9 @@ 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[],
+				bool required)
 {
-	uint32_t i;
-
 	if (!ebitmap_is_empty(&scope_index->scope[SYM_COMMONS]))
 		goto bad;
 	if (validate_ebitmap(&scope_index->p_classes_scope,
@@ -1712,9 +1711,37 @@ 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 (required) {
+		uint32_t i;
+		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 {
+		if (scope_index->class_perms_len != 0)
+			goto bad;
+		if (scope_index->class_perms_map != NULL)
 			goto bad;
+	}
 
 	return 0;
 
@@ -1786,11 +1813,11 @@ 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,
-						 flavors))
+			if (validate_scope_index(handle, &decl->required, p,
+						 flavors, true))
 				goto bad;
-			if (validate_scope_index(handle, &decl->declared,
-						 flavors))
+			if (validate_scope_index(handle, &decl->declared, p,
+						 flavors, false))
 				goto bad;
 			if (validate_filename_trans_rules(
 				    handle, decl->filename_trans_rules, p,
-- 
2.55.0