mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 01/14] selinux: avoid nontransitive comparison
@ 2025-05-11 17:30 Christian Göttsche
  2025-05-11 17:30 ` [PATCH v3 02/14] selinux: use u16 for security classes Christian Göttsche
                   ` (14 more replies)
  0 siblings, 15 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel

From: Christian Göttsche <cgzones@googlemail.com>

Avoid using nontransitive comparison to prevent unexpected sorting
results due to (well-defined) overflows.
See https://www.qualys.com/2024/01/30/qsort.txt for a related issue in
glibc's qsort(3).

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3: rename macro to cmp_int()
---
 security/selinux/ss/policydb.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 9ea971943713..dc701a7f8652 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -37,6 +37,8 @@
 #include "mls.h"
 #include "services.h"
 
+#define cmp_int(a, b) (((a) > (b)) - ((a) < (b)))
+
 #ifdef CONFIG_SECURITY_SELINUX_DEBUG
 /* clang-format off */
 static const char *const symtab_name[SYM_NUM] = {
@@ -424,11 +426,11 @@ static int filenametr_cmp(const void *k1, const void *k2)
 	const struct filename_trans_key *ft2 = k2;
 	int v;
 
-	v = ft1->ttype - ft2->ttype;
+	v = cmp_int(ft1->ttype, ft2->ttype);
 	if (v)
 		return v;
 
-	v = ft1->tclass - ft2->tclass;
+	v = cmp_int(ft1->tclass, ft2->tclass);
 	if (v)
 		return v;
 
@@ -459,15 +461,15 @@ static int rangetr_cmp(const void *k1, const void *k2)
 	const struct range_trans *key1 = k1, *key2 = k2;
 	int v;
 
-	v = key1->source_type - key2->source_type;
+	v = cmp_int(key1->source_type, key2->source_type);
 	if (v)
 		return v;
 
-	v = key1->target_type - key2->target_type;
+	v = cmp_int(key1->target_type, key2->target_type);
 	if (v)
 		return v;
 
-	v = key1->target_class - key2->target_class;
+	v = cmp_int(key1->target_class, key2->target_class);
 
 	return v;
 }
@@ -496,15 +498,15 @@ static int role_trans_cmp(const void *k1, const void *k2)
 	const struct role_trans_key *key1 = k1, *key2 = k2;
 	int v;
 
-	v = key1->role - key2->role;
+	v = cmp_int(key1->role, key2->role);
 	if (v)
 		return v;
 
-	v = key1->type - key2->type;
+	v = cmp_int(key1->type, key2->type);
 	if (v)
 		return v;
 
-	return key1->tclass - key2->tclass;
+	return cmp_int(key1->tclass, key2->tclass);
 }
 
 static const struct hashtab_key_params roletr_key_params = {
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 02/14] selinux: use u16 for security classes
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-13 19:44   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 03/14] selinux: more strict policy parsing Christian Göttsche
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Thiébaud Weksteen,
	Casey Schaufler, Canfeng Guo, Takaya Saeki

From: Christian Göttsche <cgzones@googlemail.com>

Security class identifiers are limited to 2^16, thus use the appropriate
type u16 consistently.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3: only change type, move the validation (> U16_MAX) to the subsequent
    patch
---
 security/selinux/ss/policydb.c |  5 +++--
 security/selinux/ss/policydb.h | 10 +++++-----
 security/selinux/ss/services.c |  2 +-
 3 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index dc701a7f8652..f490556ddb5c 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -927,7 +927,7 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s)
 	return 0;
 }
 
-int policydb_class_isvalid(struct policydb *p, unsigned int class)
+int policydb_class_isvalid(struct policydb *p, u16 class)
 {
 	if (!class || class > p->p_classes.nprim)
 		return 0;
@@ -2003,7 +2003,8 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 	struct filename_trans_key *ft = NULL;
 	struct filename_trans_datum **dst, *datum, *first = NULL;
 	char *name = NULL;
-	u32 len, ttype, tclass, ndatum, i;
+	u32 len, ttype, ndatum, i;
+	u16 tclass;
 	__le32 buf[3];
 	int rc;
 
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 25650224b6e7..0c423ad77fd9 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -48,7 +48,7 @@ struct common_datum {
 
 /* Class attributes */
 struct class_datum {
-	u32 value; /* class value */
+	u16 value; /* class value */
 	char *comkey; /* common name */
 	struct common_datum *comdatum; /* common datum */
 	struct symtab permissions; /* class-specific permission symbol table */
@@ -82,7 +82,7 @@ struct role_datum {
 struct role_trans_key {
 	u32 role; /* current role */
 	u32 type; /* program executable type, or new object type */
-	u32 tclass; /* process class, or new object class */
+	u16 tclass; /* process class, or new object class */
 };
 
 struct role_trans_datum {
@@ -139,7 +139,7 @@ struct cat_datum {
 struct range_trans {
 	u32 source_type;
 	u32 target_type;
-	u32 target_class;
+	u16 target_class;
 };
 
 /* Boolean data type */
@@ -195,7 +195,7 @@ struct ocontext {
 		} ibendport;
 	} u;
 	union {
-		u32 sclass; /* security class for genfs */
+		u16 sclass; /* security class for genfs */
 		u32 behavior; /* labeling behavior for fs_use */
 	} v;
 	struct context context[2]; /* security context(s) */
@@ -320,7 +320,7 @@ struct policy_file {
 extern void policydb_destroy(struct policydb *p);
 extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
 extern int policydb_context_isvalid(struct policydb *p, struct context *c);
-extern int policydb_class_isvalid(struct policydb *p, unsigned int class);
+extern int policydb_class_isvalid(struct policydb *p, u16 class);
 extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
 extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
 extern int policydb_read(struct policydb *p, struct policy_file *fp);
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 7becf3808818..a2dd42e750fe 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -3387,7 +3387,7 @@ static int get_classes_callback(void *k, void *d, void *args)
 {
 	struct class_datum *datum = d;
 	char *name = k, **classes = args;
-	u32 value = datum->value - 1;
+	u16 value = datum->value - 1;
 
 	classes[value] = kstrdup(name, GFP_ATOMIC);
 	if (!classes[value])
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 03/14] selinux: more strict policy parsing
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
  2025-05-11 17:30 ` [PATCH v3 02/14] selinux: use u16 for security classes Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 14:15   ` Stephen Smalley
  2026-05-06 23:43   ` [PATCH v3 3/14] " Paul Moore
  2025-05-11 17:30 ` [PATCH v3 04/14] selinux: check length fields in policies Christian Göttsche
                   ` (12 subsequent siblings)
  14 siblings, 2 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Thiébaud Weksteen,
	Bram Bonné,
	Eric Suen, Casey Schaufler, Canfeng Guo, Takaya Saeki

From: Christian Göttsche <cgzones@googlemail.com>

Be more strict during parsing of policies and reject invalid values.

Add some error messages in the case of policy parse failures, to
enhance debugging, either on a malformed policy or a too strict check.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3:
  - incorporate the overflow checks on security classes from the
    previous patch, and permit U16_MAX as class ID
  - minimize the usage of magic values, by using macros or trivial
    helper functions
v2:
  accept unknown xperm specifiers to support backwards compatibility for
  future ones, suggested by Thiébaud
---
 security/selinux/include/security.h |   1 +
 security/selinux/ss/avtab.c         |  35 ++++-
 security/selinux/ss/avtab.h         |  13 ++
 security/selinux/ss/conditional.c   |  18 +--
 security/selinux/ss/constraint.h    |   1 +
 security/selinux/ss/policydb.c      | 196 +++++++++++++++++++++++-----
 security/selinux/ss/policydb.h      |  23 +++-
 security/selinux/ss/services.c      |   6 +-
 8 files changed, 233 insertions(+), 60 deletions(-)

diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 278c144c22d6..eca6693227ca 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -224,6 +224,7 @@ int security_read_policy(void **data, size_t *len);
 int security_read_state_kernel(void **data, size_t *len);
 int security_policycap_supported(unsigned int req_cap);
 
+/* Maximum supported number of permissions per class */
 #define SEL_VEC_MAX 32
 struct av_decision {
 	u32 allowed;
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index c2c31521cace..33556922f15e 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -349,7 +349,7 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 	struct avtab_extended_perms xperms;
 	__le32 buf32[ARRAY_SIZE(xperms.perms.p)];
 	int rc;
-	unsigned int set, vers = pol->policyvers;
+	unsigned int vers = pol->policyvers;
 
 	memset(&key, 0, sizeof(struct avtab_key));
 	memset(&datum, 0, sizeof(struct avtab_datum));
@@ -360,9 +360,12 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 			pr_err("SELinux: avtab: truncated entry\n");
 			return rc;
 		}
+		/* Read five or more items: source type, target type,
+		 * target class, AV type, and at least one datum.
+		 */
 		items2 = le32_to_cpu(buf32[0]);
-		if (items2 > ARRAY_SIZE(buf32)) {
-			pr_err("SELinux: avtab: entry overflow\n");
+		if (items2 < 5 || items2 > ARRAY_SIZE(buf32)) {
+			pr_err("SELinux: avtab: invalid item count\n");
 			return -EINVAL;
 		}
 		rc = next_entry(buf32, fp, sizeof(u32) * items2);
@@ -391,6 +394,13 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 			return -EINVAL;
 		}
 
+		if (!policydb_type_isvalid(pol, key.source_type) ||
+		    !policydb_type_isvalid(pol, key.target_type) ||
+		    !policydb_class_isvalid(pol, key.target_class)) {
+			pr_err("SELinux: avtab: invalid type or class\n");
+			return -EINVAL;
+		}
+
 		val = le32_to_cpu(buf32[items++]);
 		enabled = (val & AVTAB_ENABLED_OLD) ? AVTAB_ENABLED : 0;
 
@@ -409,6 +419,11 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 
 		for (i = 0; i < ARRAY_SIZE(spec_order); i++) {
 			if (val & spec_order[i]) {
+				if (items >= items2) {
+					pr_err("SELinux: avtab: entry has too many items (%d/%d)\n",
+					       items + 1, items2);
+					return -EINVAL;
+				}
 				key.specified = spec_order[i] | enabled;
 				datum.u.data = le32_to_cpu(buf32[items++]);
 				rc = insertf(a, &key, &datum, p);
@@ -444,9 +459,13 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 		return -EINVAL;
 	}
 
-	set = hweight16(key.specified & (AVTAB_XPERMS | AVTAB_TYPE | AVTAB_AV));
-	if (!set || set > 1) {
-		pr_err("SELinux:  avtab:  more than one specifier\n");
+	if (hweight16(key.specified & ~AVTAB_ENABLED) != 1) {
+		pr_err("SELinux:  avtab:  not exactly one specifier\n");
+		return -EINVAL;
+	}
+
+	if (key.specified & ~AVTAB_SPECIFIER_MASK) {
+		pr_err("SELinux:  avtab:  invalid specifier\n");
 		return -EINVAL;
 	}
 
@@ -471,6 +490,10 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 			pr_err("SELinux: avtab: truncated entry\n");
 			return rc;
 		}
+		if (!avtab_is_valid_xperm_specified(xperms.specified))
+			pr_warn_once_policyload(pol,
+						"SELinux: avtab: unsupported xperm specifier %#x\n",
+						xperms.specified);
 		rc = next_entry(&xperms.driver, fp, sizeof(u8));
 		if (rc) {
 			pr_err("SELinux: avtab: truncated entry\n");
diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
index 850b3453f259..1de4cce288a7 100644
--- a/security/selinux/ss/avtab.h
+++ b/security/selinux/ss/avtab.h
@@ -44,6 +44,7 @@ struct avtab_key {
 	 AVTAB_XPERMS_DONTAUDIT)
 #define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */
 #define AVTAB_ENABLED	  0x8000 /* reserved for used in cond_avtab */
+#define AVTAB_SPECIFIER_MASK (AVTAB_AV | AVTAB_TYPE | AVTAB_XPERMS | AVTAB_ENABLED)
 	u16 specified; /* what field is specified */
 };
 
@@ -68,6 +69,18 @@ struct avtab_extended_perms {
 	struct extended_perms_data perms;
 };
 
+static inline bool avtab_is_valid_xperm_specified(u8 specified)
+{
+	switch (specified) {
+	case AVTAB_XPERMS_IOCTLFUNCTION:
+	case AVTAB_XPERMS_IOCTLDRIVER:
+	case AVTAB_XPERMS_NLMSG:
+		return true;
+	default:
+		return false;
+	}
+}
+
 struct avtab_datum {
 	union {
 		u32 data; /* access vector or type value */
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index 1bebfcb9c6a1..db30462ed6a3 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -199,19 +199,12 @@ int cond_index_bool(void *key, void *datum, void *datap)
 	return 0;
 }
 
-static int bool_isvalid(struct cond_bool_datum *b)
-{
-	if (!(b->state == 0 || b->state == 1))
-		return 0;
-	return 1;
-}
-
 int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
 {
 	char *key = NULL;
 	struct cond_bool_datum *booldatum;
 	__le32 buf[3];
-	u32 len;
+	u32 len, val;
 	int rc;
 
 	booldatum = kzalloc(sizeof(*booldatum), GFP_KERNEL);
@@ -223,11 +216,12 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
 		goto err;
 
 	booldatum->value = le32_to_cpu(buf[0]);
-	booldatum->state = le32_to_cpu(buf[1]);
+	val = le32_to_cpu(buf[1]);
 
 	rc = -EINVAL;
-	if (!bool_isvalid(booldatum))
+	if (!val_is_boolean(val))
 		goto err;
+	booldatum->state = (int)val;
 
 	len = le32_to_cpu(buf[2]);
 
@@ -241,6 +235,7 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
 
 	return 0;
 err:
+	pr_err("SELinux: conditional: failed to read boolean\n");
 	cond_destroy_bool(key, booldatum, NULL);
 	return rc;
 }
@@ -362,7 +357,8 @@ static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr)
 		return 0;
 	}
 
-	if (expr->boolean > p->p_bools.nprim) {
+	if (expr->expr_type == COND_BOOL &&
+	    (expr->boolean == 0 || expr->boolean > p->p_bools.nprim)) {
 		pr_err("SELinux: conditional expressions uses unknown bool.\n");
 		return 0;
 	}
diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
index 203033cfad67..1d75a8a044df 100644
--- a/security/selinux/ss/constraint.h
+++ b/security/selinux/ss/constraint.h
@@ -50,6 +50,7 @@ struct constraint_expr {
 	u32 op; /* operator */
 
 	struct ebitmap names; /* names */
+	/* internally unused, only forwarded via policydb_write() */
 	struct type_set *type_names;
 
 	struct constraint_expr *next; /* next expression */
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index f490556ddb5c..3e4a28a2928b 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -634,13 +634,11 @@ static int sens_index(void *key, void *datum, void *datap)
 	levdatum = datum;
 	p = datap;
 
-	if (!levdatum->isalias) {
-		if (!levdatum->level.sens ||
-		    levdatum->level.sens > p->p_levels.nprim)
-			return -EINVAL;
+	if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim)
+		return -EINVAL;
 
+	if (!levdatum->isalias)
 		p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1] = key;
-	}
 
 	return 0;
 }
@@ -653,12 +651,11 @@ static int cat_index(void *key, void *datum, void *datap)
 	catdatum = datum;
 	p = datap;
 
-	if (!catdatum->isalias) {
-		if (!catdatum->value || catdatum->value > p->p_cats.nprim)
-			return -EINVAL;
+	if (!catdatum->value || catdatum->value > p->p_cats.nprim)
+		return -EINVAL;
 
+	if (!catdatum->isalias)
 		p->sym_val_to_name[SYM_CATS][catdatum->value - 1] = key;
-	}
 
 	return 0;
 }
@@ -1136,6 +1133,9 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f
 
 	len = le32_to_cpu(buf[0]);
 	perdatum->value = le32_to_cpu(buf[1]);
+	rc = -EINVAL;
+	if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
+		goto bad;
 
 	rc = str_read(&key, GFP_KERNEL, fp, len);
 	if (rc)
@@ -1170,6 +1170,9 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
 	len = le32_to_cpu(buf[0]);
 	comdatum->value = le32_to_cpu(buf[1]);
 	nel = le32_to_cpu(buf[3]);
+	rc = -EINVAL;
+	if (nel > SEL_VEC_MAX)
+		goto bad;
 
 	rc = symtab_init(&comdatum->permissions, nel);
 	if (rc)
@@ -1321,7 +1324,7 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
 	char *key = NULL;
 	struct class_datum *cladatum;
 	__le32 buf[6];
-	u32 i, len, len2, ncons, nel;
+	u32 i, len, len2, ncons, nel, val;
 	int rc;
 
 	cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL);
@@ -1334,8 +1337,16 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
 
 	len = le32_to_cpu(buf[0]);
 	len2 = le32_to_cpu(buf[1]);
-	cladatum->value = le32_to_cpu(buf[2]);
 	nel = le32_to_cpu(buf[4]);
+	rc = -EINVAL;
+	if (nel > SEL_VEC_MAX)
+		goto bad;
+
+	val = le32_to_cpu(buf[2]);
+	rc = -EINVAL;
+	if (val > U16_MAX)
+		goto bad;
+	cladatum->value = val;
 
 	rc = symtab_init(&cladatum->permissions, nel);
 	if (rc)
@@ -1391,16 +1402,59 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
 		if (rc)
 			goto bad;
 
-		cladatum->default_user = le32_to_cpu(buf[0]);
-		cladatum->default_role = le32_to_cpu(buf[1]);
-		cladatum->default_range = le32_to_cpu(buf[2]);
+		rc = -EINVAL;
+		val = le32_to_cpu(buf[0]);
+		switch (val) {
+		case 0:
+		case DEFAULT_SOURCE:
+		case DEFAULT_TARGET:
+			cladatum->default_user = val;
+			break;
+		default:
+			goto bad;
+		}
+		val = le32_to_cpu(buf[1]);
+		switch (val) {
+		case 0:
+		case DEFAULT_SOURCE:
+		case DEFAULT_TARGET:
+			cladatum->default_role = val;
+			break;
+		default:
+			goto bad;
+		}
+		val = le32_to_cpu(buf[2]);
+		switch (val) {
+		case 0:
+		case DEFAULT_SOURCE_LOW:
+		case DEFAULT_SOURCE_HIGH:
+		case DEFAULT_SOURCE_LOW_HIGH:
+		case DEFAULT_TARGET_LOW:
+		case DEFAULT_TARGET_HIGH:
+		case DEFAULT_TARGET_LOW_HIGH:
+		case DEFAULT_GLBLUB:
+			cladatum->default_range = val;
+			break;
+		default:
+			goto bad;
+		}
 	}
 
 	if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) {
 		rc = next_entry(buf, fp, sizeof(u32) * 1);
 		if (rc)
 			goto bad;
-		cladatum->default_type = le32_to_cpu(buf[0]);
+		rc = -EINVAL;
+		val = le32_to_cpu(buf[0]);
+		switch (val) {
+		case 0:
+		case DEFAULT_TARGET:
+		case DEFAULT_SOURCE:
+			cladatum->default_type = val;
+			break;
+		default:
+			goto bad;
+		}
 	}
 
 	rc = symtab_insert(s, key, cladatum);
@@ -1410,6 +1464,8 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
 	return 0;
 bad:
 	cls_destroy(key, cladatum, NULL);
+	if (rc)
+		pr_err("SELinux:  invalid class\n");
 	return rc;
 }
 
@@ -1601,7 +1657,7 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
 	struct level_datum *levdatum;
 	int rc;
 	__le32 buf[2];
-	u32 len;
+	u32 len, val;
 
 	levdatum = kzalloc(sizeof(*levdatum), GFP_KERNEL);
 	if (!levdatum)
@@ -1612,7 +1668,11 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
 		goto bad;
 
 	len = le32_to_cpu(buf[0]);
-	levdatum->isalias = le32_to_cpu(buf[1]);
+	val = le32_to_cpu(buf[1]);
+	rc = -EINVAL;
+	if (!val_is_boolean(val))
+		goto bad;
+	levdatum->isalias = val;
 
 	rc = str_read(&key, GFP_KERNEL, fp, len);
 	if (rc)
@@ -1628,6 +1688,8 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
 	return 0;
 bad:
 	sens_destroy(key, levdatum, NULL);
+	if (rc)
+		pr_err("SELinux:  invalid sensitivity\n");
 	return rc;
 }
 
@@ -1637,7 +1699,7 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
 	struct cat_datum *catdatum;
 	int rc;
 	__le32 buf[3];
-	u32 len;
+	u32 len, val;
 
 	catdatum = kzalloc(sizeof(*catdatum), GFP_KERNEL);
 	if (!catdatum)
@@ -1649,7 +1711,11 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
 
 	len = le32_to_cpu(buf[0]);
 	catdatum->value = le32_to_cpu(buf[1]);
-	catdatum->isalias = le32_to_cpu(buf[2]);
+	val = le32_to_cpu(buf[2]);
+	rc = -EINVAL;
+	if (!val_is_boolean(val))
+		goto bad;
+	catdatum->isalias = val;
 
 	rc = str_read(&key, GFP_KERNEL, fp, len);
 	if (rc)
@@ -1661,6 +1727,8 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
 	return 0;
 bad:
 	cat_destroy(key, catdatum, NULL);
+	if (rc)
+		pr_err("SELinux:  invalid category\n");
 	return rc;
 }
 
@@ -1842,7 +1910,7 @@ static int range_read(struct policydb *p, struct policy_file *fp)
 	struct mls_range *r = NULL;
 	int rc;
 	__le32 buf[2];
-	u32 i, nel;
+	u32 i, nel, val;
 
 	if (p->policyvers < POLICYDB_VERSION_MLS)
 		return 0;
@@ -1873,7 +1941,11 @@ static int range_read(struct policydb *p, struct policy_file *fp)
 			rc = next_entry(buf, fp, sizeof(u32));
 			if (rc)
 				goto out;
-			rt->target_class = le32_to_cpu(buf[0]);
+			rc = -EINVAL;
+			val = le32_to_cpu(buf[0]);
+			if (val > U16_MAX)
+				goto out;
+			rt->target_class = val;
 		} else
 			rt->target_class = p->process_class;
 
@@ -1910,6 +1982,8 @@ static int range_read(struct policydb *p, struct policy_file *fp)
 out:
 	kfree(rt);
 	kfree(r);
+	if (rc)
+		pr_err("SELinux:  invalid range\n");
 	return rc;
 }
 
@@ -1918,7 +1992,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
 	struct filename_trans_key key, *ft = NULL;
 	struct filename_trans_datum *last, *datum = NULL;
 	char *name = NULL;
-	u32 len, stype, otype;
+	u32 len, stype, otype, val;
 	__le32 buf[4];
 	int rc;
 
@@ -1937,9 +2011,17 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
 	if (rc)
 		goto out;
 
+	rc = -EINVAL;
 	stype = le32_to_cpu(buf[0]);
+	if (!policydb_type_isvalid(p, stype))
+		goto out;
 	key.ttype = le32_to_cpu(buf[1]);
-	key.tclass = le32_to_cpu(buf[2]);
+	if (!policydb_type_isvalid(p, key.ttype))
+		goto out;
+	val = le32_to_cpu(buf[2]);
+	if (val > U16_MAX || !policydb_class_isvalid(p, val))
+		goto out;
+	key.tclass = val;
 	key.name = name;
 
 	otype = le32_to_cpu(buf[3]);
@@ -1995,6 +2077,9 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
 	kfree(ft);
 	kfree(name);
 	kfree(datum);
+
+	if (rc)
+		pr_err("SELinux:  invalid compat filename transition\n");
 	return rc;
 }
 
@@ -2003,7 +2088,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 	struct filename_trans_key *ft = NULL;
 	struct filename_trans_datum **dst, *datum, *first = NULL;
 	char *name = NULL;
-	u32 len, ttype, ndatum, i;
+	u32 len, ttype, ndatum, i, val;
 	u16 tclass;
 	__le32 buf[3];
 	int rc;
@@ -2023,8 +2108,15 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 	if (rc)
 		goto out;
 
+	rc = -EINVAL;
 	ttype = le32_to_cpu(buf[0]);
-	tclass = le32_to_cpu(buf[1]);
+	if (!policydb_type_isvalid(p, ttype))
+		goto out;
+	val = le32_to_cpu(buf[1]);
+	rc = -EINVAL;
+	if (val > U16_MAX || !policydb_class_isvalid(p, val))
+		goto out;
+	tclass = val;
 
 	ndatum = le32_to_cpu(buf[2]);
 	if (ndatum == 0) {
@@ -2054,6 +2146,10 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 
 		datum->otype = le32_to_cpu(buf[0]);
 
+		rc = -EINVAL;
+		if (!policydb_type_isvalid(p, datum->otype))
+			goto out;
+
 		dst = &datum->next;
 	}
 
@@ -2085,6 +2181,9 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 		ebitmap_destroy(&datum->stypes);
 		kfree(datum);
 	}
+
+	if (rc)
+		pr_err("SELinux:  invalid filename transition\n");
 	return rc;
 }
 
@@ -2132,7 +2231,7 @@ static int filename_trans_read(struct policydb *p, struct policy_file *fp)
 static int genfs_read(struct policydb *p, struct policy_file *fp)
 {
 	int rc;
-	u32 i, j, nel, nel2, len, len2;
+	u32 i, j, nel, nel2, len, len2, val;
 	__le32 buf[1];
 	struct ocontext *l, *c;
 	struct ocontext *newc = NULL;
@@ -2202,7 +2301,11 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
 			if (rc)
 				goto out;
 
-			newc->v.sclass = le32_to_cpu(buf[0]);
+			rc = -EINVAL;
+			val = le32_to_cpu(buf[0]);
+			if (val > U16_MAX || (val != 0 && !policydb_class_isvalid(p, val)))
+				goto out;
+			newc->v.sclass = val;
 			rc = context_read_and_validate(&newc->context[0], p,
 						       fp);
 			if (rc)
@@ -2239,6 +2342,9 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
 	}
 	ocontext_destroy(newc, OCON_FSUSE);
 
+	if (rc)
+		pr_err("SELinux:  invalid genfs\n");
+
 	return rc;
 }
 
@@ -2247,7 +2353,7 @@ static int ocontext_read(struct policydb *p,
 {
 	int rc;
 	unsigned int i;
-	u32 j, nel, len;
+	u32 j, nel, len, val;
 	__be64 prefixbuf[1];
 	__le32 buf[3];
 	struct ocontext *l, *c;
@@ -2311,11 +2417,25 @@ static int ocontext_read(struct policydb *p,
 				rc = next_entry(buf, fp, sizeof(u32) * 3);
 				if (rc)
 					goto out;
-				c->u.port.protocol = le32_to_cpu(buf[0]);
-				c->u.port.low_port = le32_to_cpu(buf[1]);
-				c->u.port.high_port = le32_to_cpu(buf[2]);
-				rc = context_read_and_validate(&c->context[0],
-							       p, fp);
+
+				rc = -EINVAL;
+				val = le32_to_cpu(buf[0]);
+				if (val > U8_MAX)
+					goto out;
+				c->u.port.protocol = val;
+				val = le32_to_cpu(buf[1]);
+				if (val > U16_MAX)
+					goto out;
+				c->u.port.low_port = val;
+				val = le32_to_cpu(buf[2]);
+				if (val > U16_MAX)
+					goto out;
+				c->u.port.high_port = val;
+				if (c->u.port.low_port == 0 ||
+				    c->u.port.low_port > c->u.port.high_port)
+					goto out;
+
+				rc = context_read_and_validate(&c->context[0], p, fp);
 				if (rc)
 					goto out;
 				break;
@@ -2433,6 +2553,8 @@ static int ocontext_read(struct policydb *p,
 	}
 	rc = 0;
 out:
+	if (rc)
+		pr_err("SELinux:  invalid ocon\n");
 	return rc;
 }
 
@@ -2447,7 +2569,7 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 	struct role_trans_datum *rtd = NULL;
 	int rc;
 	__le32 buf[4];
-	u32 i, j, len, nprim, nel, perm;
+	u32 i, j, len, nprim, nel, perm, val;
 
 	char *policydb_str;
 	const struct policydb_compat_info *info;
@@ -2633,7 +2755,11 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 			rc = next_entry(buf, fp, sizeof(u32));
 			if (rc)
 				goto bad;
-			rtk->tclass = le32_to_cpu(buf[0]);
+			rc = -EINVAL;
+			val = le32_to_cpu(buf[0]);
+			if (val > U16_MAX)
+				goto bad;
+			rtk->tclass = val;
 		} else
 			rtk->tclass = p->process_class;
 
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 0c423ad77fd9..9b3cc393a979 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -74,7 +74,7 @@ struct class_datum {
 /* Role attributes */
 struct role_datum {
 	u32 value; /* internal role value */
-	u32 bounds; /* boundary of role */
+	u32 bounds; /* boundary of role, 0 for none */
 	struct ebitmap dominates; /* set of roles dominated by this role */
 	struct ebitmap types; /* set of authorized types for role */
 };
@@ -110,7 +110,8 @@ struct role_allow {
 /* Type attributes */
 struct type_datum {
 	u32 value; /* internal type value */
-	u32 bounds; /* boundary of type */
+	u32 bounds; /* boundary of type, 0 for none */
+	/* internally unused, only forwarded via policydb_write() */
 	unsigned char primary; /* primary name? */
 	unsigned char attribute; /* attribute ?*/
 };
@@ -118,7 +119,7 @@ struct type_datum {
 /* User attributes */
 struct user_datum {
 	u32 value; /* internal user value */
-	u32 bounds; /* bounds of user */
+	u32 bounds; /* bounds of user, 0 for none */
 	struct ebitmap roles; /* set of authorized roles for user */
 	struct mls_range range; /* MLS range (min - max) for user */
 	struct mls_level dfltlevel; /* default login MLS level for user */
@@ -195,7 +196,7 @@ struct ocontext {
 		} ibendport;
 	} u;
 	union {
-		u16 sclass; /* security class for genfs */
+		u16 sclass; /* security class for genfs (can be 0 for wildcard) */
 		u32 behavior; /* labeling behavior for fs_use */
 	} v;
 	struct context context[2]; /* security context(s) */
@@ -386,9 +387,23 @@ static inline char *sym_name(struct policydb *p, unsigned int sym_num,
 	return p->sym_val_to_name[sym_num][element_nr];
 }
 
+static inline bool val_is_boolean(u32 value)
+{
+	return value == 0 || value == 1;
+}
+
 extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
 
 extern u16 string_to_security_class(struct policydb *p, const char *name);
 extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
 
+#define pr_warn_once_policyload(policy, fmt, ...)                        \
+	do {                                                             \
+		static const void *prev_policy__;                        \
+		if (prev_policy__ != policy) {                           \
+			printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__); \
+			prev_policy__ = policy;                          \
+		}                                                        \
+	} while (0)
+
 #endif /* _SS_POLICYDB_H_ */
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index a2dd42e750fe..0f67a030b49b 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -446,8 +446,6 @@ static int dump_masked_av_helper(void *k, void *d, void *args)
 	struct perm_datum *pdatum = d;
 	char **permission_names = args;
 
-	BUG_ON(pdatum->value < 1 || pdatum->value > 32);
-
 	permission_names[pdatum->value - 1] = (char *)k;
 
 	return 0;
@@ -466,7 +464,7 @@ static void security_dump_masked_av(struct policydb *policydb,
 	char *tclass_name;
 	char *scontext_name = NULL;
 	char *tcontext_name = NULL;
-	char *permission_names[32];
+	char *permission_names[SEL_VEC_MAX];
 	int index;
 	u32 length;
 	bool need_comma = false;
@@ -507,7 +505,7 @@ static void security_dump_masked_av(struct policydb *policydb,
 			 "scontext=%s tcontext=%s tclass=%s perms=",
 			 reason, scontext_name, tcontext_name, tclass_name);
 
-	for (index = 0; index < 32; index++) {
+	for (index = 0; index < SEL_VEC_MAX; index++) {
 		u32 mask = (1 << index);
 
 		if ((mask & permissions) == 0)
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 04/14] selinux: check length fields in policies
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
  2025-05-11 17:30 ` [PATCH v3 02/14] selinux: use u16 for security classes Christian Göttsche
  2025-05-11 17:30 ` [PATCH v3 03/14] selinux: more strict policy parsing Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 17:26   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 05/14] selinux: validate constraints Christian Göttsche
                   ` (11 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Eric Suen

From: Christian Göttsche <cgzones@googlemail.com>

In multiple places the binary policy announces how many items of some
kind are to be expected next.  Before reading them the kernel already
allocates enough memory for that announced size.  Validate that the
remaining input size can actually fit the announced items, to avoid OOM
issues on malformed binary policies.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3:
  - fix error branch by returning directly instead of jumping to goto
    label, see
      https://lore.kernel.org/all/202412241405.LK8YTZqp-lkp@intel.com/
  - rename to size_check()
  - add comments for magic values
---
 security/selinux/ss/avtab.c       |  5 +++++
 security/selinux/ss/conditional.c | 17 ++++++++++++++++
 security/selinux/ss/policydb.c    | 32 +++++++++++++++++++++++++++++++
 security/selinux/ss/policydb.h    | 13 +++++++++++++
 4 files changed, 67 insertions(+)

diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index 33556922f15e..50df8b69de2b 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -548,6 +548,11 @@ int avtab_read(struct avtab *a, struct policy_file *fp, struct policydb *pol)
 		goto bad;
 	}
 
+	/* avtab_read_item() reads at least 96 bytes for any valid entry */
+	rc = size_check(3 * sizeof(u32), nel, fp);
+	if (rc)
+		goto bad;
+
 	rc = avtab_alloc(a, nel);
 	if (rc)
 		goto bad;
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index db30462ed6a3..92ed4f23a217 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -12,6 +12,7 @@
 
 #include "security.h"
 #include "conditional.h"
+#include "policydb.h"
 #include "services.h"
 
 /*
@@ -329,6 +330,11 @@ static int cond_read_av_list(struct policydb *p, struct policy_file *fp,
 	if (len == 0)
 		return 0;
 
+	/* avtab_read_item() reads at least 96 bytes for any valid entry */
+	rc = size_check(3 * sizeof(u32), len, fp);
+	if (rc)
+		return rc;
+
 	list->nodes = kcalloc(len, sizeof(*list->nodes), GFP_KERNEL);
 	if (!list->nodes)
 		return -ENOMEM;
@@ -379,6 +385,12 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol
 
 	/* expr */
 	len = le32_to_cpu(buf[1]);
+
+	/* we will read 64 bytes per node */
+	rc = size_check(2 * sizeof(u32), len, fp);
+	if (rc)
+		return rc;
+
 	node->expr.nodes = kcalloc(len, sizeof(*node->expr.nodes), GFP_KERNEL);
 	if (!node->expr.nodes)
 		return -ENOMEM;
@@ -417,6 +429,11 @@ int cond_read_list(struct policydb *p, struct policy_file *fp)
 
 	len = le32_to_cpu(buf[0]);
 
+	/* cond_read_node() reads at least 128 bytes for any valid node */
+	rc = size_check(4 * sizeof(u32), len, fp);
+	if (rc)
+		return rc;
+
 	p->cond_list = kcalloc(len, sizeof(*p->cond_list), GFP_KERNEL);
 	if (!p->cond_list)
 		return -ENOMEM;
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 3e4a28a2928b..46c010afd44f 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -1100,6 +1100,9 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
 	if ((len == 0) || (len == (u32)-1))
 		return -EINVAL;
 
+	if (size_check(sizeof(char), len, fp))
+		return -EINVAL;
+
 	str = kmalloc(len + 1, flags | __GFP_NOWARN);
 	if (!str)
 		return -ENOMEM;
@@ -1174,6 +1177,11 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
 	if (nel > SEL_VEC_MAX)
 		goto bad;
 
+	/* perm_read() reads at least 64 bytes for any valid permission */
+	rc = size_check(2 * sizeof(u32), nel, fp);
+	if (rc)
+		goto bad;
+
 	rc = symtab_init(&comdatum->permissions, nel);
 	if (rc)
 		goto bad;
@@ -1348,6 +1356,11 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
 		goto bad;
 	cladatum->value = val;
 
+	/* perm_read() reads at least 64 bytes for any valid permission */
+	rc = size_check(2 * sizeof(u32), nel, fp);
+	if (rc)
+		goto bad;
+
 	rc = symtab_init(&cladatum->permissions, nel);
 	if (rc)
 		goto bad;
@@ -1921,6 +1934,13 @@ static int range_read(struct policydb *p, struct policy_file *fp)
 
 	nel = le32_to_cpu(buf[0]);
 
+	/* we read at least 64 bytes and mls_read_range_helper() 32 bytes
+	 * for any valid range-transition
+	 */
+	rc = size_check(3 * sizeof(u32), nel, fp);
+	if (rc)
+		return rc;
+
 	rc = hashtab_init(&p->range_tr, nel);
 	if (rc)
 		return rc;
@@ -2689,6 +2709,13 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 		nprim = le32_to_cpu(buf[0]);
 		nel = le32_to_cpu(buf[1]);
 
+		/* every read_f() implementation reads at least 128 bytes
+		 * for any valid entry
+		 */
+		rc = size_check(4 * sizeof(u32), nel, fp);
+		if (rc)
+			goto out;
+
 		rc = symtab_init(&p->symtab[i], nel);
 		if (rc)
 			goto out;
@@ -2730,6 +2757,11 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 		goto bad;
 	nel = le32_to_cpu(buf[0]);
 
+	/* we read at least 96 bytes for any valid role-transition */
+	rc = size_check(3 * sizeof(u32), nel, fp);
+	if (rc)
+		goto bad;
+
 	rc = hashtab_init(&p->role_tr, nel);
 	if (rc)
 		goto bad;
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 9b3cc393a979..bb96a6cb6101 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -353,6 +353,19 @@ struct policy_data {
 	struct policy_file *fp;
 };
 
+static inline int size_check(size_t bytes, size_t num, const struct policy_file *fp)
+{
+	size_t len;
+
+	if (unlikely(check_mul_overflow(bytes, num, &len)))
+		return -EINVAL;
+
+	if (unlikely(len > fp->len))
+		return -EINVAL;
+
+	return 0;
+}
+
 static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes)
 {
 	if (bytes > fp->len)
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 05/14] selinux: validate constraints
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (2 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 04/14] selinux: check length fields in policies Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 17:51   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 06/14] selinux: pre-validate conditional expressions Christian Göttsche
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Thiébaud Weksteen,
	Casey Schaufler, Canfeng Guo, Takaya Saeki

From: Christian Göttsche <cgzones@googlemail.com>

Validate constraint expressions during reading the policy.
Avoid the usage of BUG() on constraint evaluation, to mitigate malformed
policies halting the system.

Closes: https://github.com/SELinuxProject/selinux-testsuite/issues/76

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/policydb.c | 61 ++++++++++++++++++++++++++++++++--
 security/selinux/ss/services.c | 55 +++++++++++++++---------------
 2 files changed, 88 insertions(+), 28 deletions(-)

diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 46c010afd44f..a8397ed66109 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -1257,6 +1257,8 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep,
 			return rc;
 		c->permissions = le32_to_cpu(buf[0]);
 		nexpr = le32_to_cpu(buf[1]);
+		if (nexpr == 0)
+			return -EINVAL;
 		le = NULL;
 		depth = -1;
 		for (j = 0; j < nexpr; j++) {
@@ -1288,15 +1290,70 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep,
 				depth--;
 				break;
 			case CEXPR_ATTR:
-				if (depth == (CEXPR_MAXDEPTH - 1))
+				if (depth >= (CEXPR_MAXDEPTH - 1))
 					return -EINVAL;
 				depth++;
 				break;
+
+				switch (e->op) {
+				case CEXPR_EQ:
+				case CEXPR_NEQ:
+					break;
+				case CEXPR_DOM:
+				case CEXPR_DOMBY:
+				case CEXPR_INCOMP:
+					if ((e->attr & CEXPR_USER) || (e->attr & CEXPR_TYPE))
+						return -EINVAL;
+					break;
+				default:
+					return -EINVAL;
+				}
+
+				switch (e->attr) {
+				case CEXPR_USER:
+				case CEXPR_ROLE:
+				case CEXPR_TYPE:
+				case CEXPR_L1L2:
+				case CEXPR_L1H2:
+				case CEXPR_H1L2:
+				case CEXPR_H1H2:
+				case CEXPR_L1H1:
+				case CEXPR_L2H2:
+					break;
+				default:
+					return -EINVAL;
+				}
+
+				break;
 			case CEXPR_NAMES:
 				if (!allowxtarget && (e->attr & CEXPR_XTARGET))
 					return -EINVAL;
-				if (depth == (CEXPR_MAXDEPTH - 1))
+				if (depth >= (CEXPR_MAXDEPTH - 1))
+					return -EINVAL;
+
+				switch (e->op) {
+				case CEXPR_EQ:
+				case CEXPR_NEQ:
+					break;
+				default:
+					return -EINVAL;
+				}
+
+				switch (e->attr) {
+				case CEXPR_USER:
+				case CEXPR_USER | CEXPR_TARGET:
+				case CEXPR_USER | CEXPR_XTARGET:
+				case CEXPR_ROLE:
+				case CEXPR_ROLE | CEXPR_TARGET:
+				case CEXPR_ROLE | CEXPR_XTARGET:
+				case CEXPR_TYPE:
+				case CEXPR_TYPE | CEXPR_TARGET:
+				case CEXPR_TYPE | CEXPR_XTARGET:
+					break;
+				default:
 					return -EINVAL;
+				}
+
 				depth++;
 				rc = ebitmap_read(&e->names, fp);
 				if (rc)
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 0f67a030b49b..3fb971fe4fd9 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -279,22 +279,25 @@ static int constraint_expr_eval(struct policydb *policydb,
 	for (e = cexpr; e; e = e->next) {
 		switch (e->expr_type) {
 		case CEXPR_NOT:
-			BUG_ON(sp < 0);
+			if (unlikely(sp < 0))
+				goto invalid;
 			s[sp] = !s[sp];
 			break;
 		case CEXPR_AND:
-			BUG_ON(sp < 1);
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] &= s[sp + 1];
 			break;
 		case CEXPR_OR:
-			BUG_ON(sp < 1);
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] |= s[sp + 1];
 			break;
 		case CEXPR_ATTR:
-			if (sp == (CEXPR_MAXDEPTH - 1))
-				return 0;
+			if (unlikely(sp >= (CEXPR_MAXDEPTH - 1)))
+				goto invalid;
 			switch (e->attr) {
 			case CEXPR_USER:
 				val1 = scontext->user;
@@ -370,13 +373,11 @@ static int constraint_expr_eval(struct policydb *policydb,
 					s[++sp] = mls_level_incomp(l2, l1);
 					continue;
 				default:
-					BUG();
-					return 0;
+					goto invalid;
 				}
 				break;
 			default:
-				BUG();
-				return 0;
+				goto invalid;
 			}
 
 			switch (e->op) {
@@ -387,22 +388,19 @@ static int constraint_expr_eval(struct policydb *policydb,
 				s[++sp] = (val1 != val2);
 				break;
 			default:
-				BUG();
-				return 0;
+				goto invalid;
 			}
 			break;
 		case CEXPR_NAMES:
-			if (sp == (CEXPR_MAXDEPTH-1))
-				return 0;
+			if (unlikely(sp >= (CEXPR_MAXDEPTH-1)))
+				goto invalid;
 			c = scontext;
 			if (e->attr & CEXPR_TARGET)
 				c = tcontext;
 			else if (e->attr & CEXPR_XTARGET) {
 				c = xcontext;
-				if (!c) {
-					BUG();
-					return 0;
-				}
+				if (unlikely(!c))
+					goto invalid;
 			}
 			if (e->attr & CEXPR_USER)
 				val1 = c->user;
@@ -410,10 +408,8 @@ static int constraint_expr_eval(struct policydb *policydb,
 				val1 = c->role;
 			else if (e->attr & CEXPR_TYPE)
 				val1 = c->type;
-			else {
-				BUG();
-				return 0;
-			}
+			else
+				goto invalid;
 
 			switch (e->op) {
 			case CEXPR_EQ:
@@ -423,18 +419,25 @@ static int constraint_expr_eval(struct policydb *policydb,
 				s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1);
 				break;
 			default:
-				BUG();
-				return 0;
+				goto invalid;
 			}
 			break;
 		default:
-			BUG();
-			return 0;
+			goto invalid;
 		}
 	}
 
-	BUG_ON(sp != 0);
+	if (unlikely(sp != 0))
+		goto invalid;
+
 	return s[0];
+
+invalid:
+	/* Should *never* be reached, cause malformed constraints should
+	 * have been filtered by read_cons_helper().
+	 */
+	WARN_ONCE(true, "SELinux: invalid constraint passed validation\n");
+	return 0;
 }
 
 /*
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 06/14] selinux: pre-validate conditional expressions
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (3 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 05/14] selinux: validate constraints Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 17:55   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 07/14] selinux: check type attr map overflows Christian Göttsche
                   ` (9 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel

From: Christian Göttsche <cgzones@googlemail.com>

Validate conditional expressions while reading the policy, to avoid
unexpected access decisions on malformed policies.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/conditional.c | 116 ++++++++++++++++++++----------
 security/selinux/ss/policydb.c    |   7 ++
 security/selinux/ss/policydb.h    |   1 +
 3 files changed, 88 insertions(+), 36 deletions(-)

diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index 92ed4f23a217..ce0281cce739 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -21,65 +21,119 @@
  * or undefined (-1). Undefined occurs when the expression
  * exceeds the stack depth of COND_EXPR_MAXDEPTH.
  */
-static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr)
+static int cond_evaluate_expr(const struct policydb *p, const struct cond_expr *expr)
 {
 	u32 i;
 	int s[COND_EXPR_MAXDEPTH];
 	int sp = -1;
 
-	if (expr->len == 0)
-		return -1;
+	if (unlikely(expr->len == 0))
+		goto invalid;
 
 	for (i = 0; i < expr->len; i++) {
-		struct cond_expr_node *node = &expr->nodes[i];
+		const struct cond_expr_node *node = &expr->nodes[i];
 
 		switch (node->expr_type) {
 		case COND_BOOL:
-			if (sp == (COND_EXPR_MAXDEPTH - 1))
-				return -1;
+			if (unlikely(sp >= (COND_EXPR_MAXDEPTH - 1)))
+				goto invalid;
 			sp++;
 			s[sp] = p->bool_val_to_struct[node->boolean - 1]->state;
 			break;
 		case COND_NOT:
-			if (sp < 0)
-				return -1;
+			if (unlikely(sp < 0))
+				goto invalid;
 			s[sp] = !s[sp];
 			break;
 		case COND_OR:
-			if (sp < 1)
-				return -1;
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] |= s[sp + 1];
 			break;
 		case COND_AND:
-			if (sp < 1)
-				return -1;
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] &= s[sp + 1];
 			break;
 		case COND_XOR:
-			if (sp < 1)
-				return -1;
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] ^= s[sp + 1];
 			break;
 		case COND_EQ:
-			if (sp < 1)
-				return -1;
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] = (s[sp] == s[sp + 1]);
 			break;
 		case COND_NEQ:
-			if (sp < 1)
-				return -1;
+			if (unlikely(sp < 1))
+				goto invalid;
 			sp--;
 			s[sp] = (s[sp] != s[sp + 1]);
 			break;
 		default:
-			return -1;
+			goto invalid;
 		}
 	}
+
+	if (unlikely(sp != 0))
+		goto invalid;
+
 	return s[0];
+
+invalid:
+	/* Should *never* be reached, cause malformed expressions should
+	 * have been filtered by cond_validate_expr().
+	 */
+	WARN_ONCE(true, "SELinux: invalid conditional expression passed validation\n");
+	return -1;
+}
+
+static int cond_validate_expr(const struct policydb *p, const struct cond_expr *expr)
+{
+	u32 i;
+	int depth = -1;
+
+	if (expr->len == 0)
+		return -EINVAL;
+
+	for (i = 0; i < expr->len; i++) {
+		const struct cond_expr_node *node = &expr->nodes[i];
+
+		switch (node->expr_type) {
+		case COND_BOOL:
+			if (depth >= (COND_EXPR_MAXDEPTH - 1))
+				return -EINVAL;
+			depth++;
+			if (!policydb_boolean_isvalid(p, node->boolean))
+				return -EINVAL;
+			break;
+		case COND_NOT:
+			if (depth < 0)
+				return -EINVAL;
+			break;
+		case COND_OR:
+		case COND_AND:
+		case COND_XOR:
+		case COND_EQ:
+		case COND_NEQ:
+			if (depth < 1)
+				return -EINVAL;
+			depth--;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (depth != 0)
+		return -EINVAL;
+
+	return 0;
 }
 
 /*
@@ -356,21 +410,6 @@ static int cond_read_av_list(struct policydb *p, struct policy_file *fp,
 	return 0;
 }
 
-static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr)
-{
-	if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) {
-		pr_err("SELinux: conditional expressions uses unknown operator.\n");
-		return 0;
-	}
-
-	if (expr->expr_type == COND_BOOL &&
-	    (expr->boolean == 0 || expr->boolean > p->p_bools.nprim)) {
-		pr_err("SELinux: conditional expressions uses unknown bool.\n");
-		return 0;
-	}
-	return 1;
-}
-
 static int cond_read_node(struct policydb *p, struct cond_node *node, struct policy_file *fp)
 {
 	__le32 buf[2];
@@ -385,6 +424,8 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol
 
 	/* expr */
 	len = le32_to_cpu(buf[1]);
+	if (len == 0)
+		return -EINVAL;
 
 	/* we will read 64 bytes per node */
 	rc = size_check(2 * sizeof(u32), len, fp);
@@ -406,9 +447,12 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol
 
 		expr->expr_type = le32_to_cpu(buf[0]);
 		expr->boolean = le32_to_cpu(buf[1]);
+	}
 
-		if (!expr_node_isvalid(p, expr))
-			return -EINVAL;
+	rc = cond_validate_expr(p, &node->expr);
+	if (rc) {
+		pr_err("SELinux: invalid conditional expression\n");
+		return rc;
 	}
 
 	rc = cond_read_av_list(p, fp, &node->true_list, NULL);
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index a8397ed66109..8969f7c8637c 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -945,6 +945,13 @@ int policydb_type_isvalid(struct policydb *p, unsigned int type)
 	return 1;
 }
 
+int policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
+{
+	if (!boolean || boolean > p->p_bools.nprim)
+		return 0;
+	return 1;
+}
+
 /*
  * Return 1 if the fields in the security context
  * structure `c' are valid.  Return 0 otherwise.
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index bb96a6cb6101..42117adb2ca0 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -324,6 +324,7 @@ extern int policydb_context_isvalid(struct policydb *p, struct context *c);
 extern int policydb_class_isvalid(struct policydb *p, u16 class);
 extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
 extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
+extern int policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
 extern int policydb_read(struct policydb *p, struct policy_file *fp);
 extern int policydb_write(struct policydb *p, struct policy_file *fp);
 
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 07/14] selinux: check type attr map overflows
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (4 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 06/14] selinux: pre-validate conditional expressions Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 18:15   ` Stephen Smalley
  2026-05-06 23:43   ` [PATCH v3 7/14] " Paul Moore
  2025-05-11 17:30 ` [PATCH v3 08/14] selinux: reorder policydb_index() Christian Göttsche
                   ` (8 subsequent siblings)
  14 siblings, 2 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Eric Suen, Canfeng Guo

From: Christian Göttsche <cgzones@googlemail.com>

Validate that no types with an invalid too high ID are present in the
attribute map.  Gaps are still not checked.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3: squash with previous patch ("selinux: introduce
    ebitmap_highest_set_bit()")
---
 security/selinux/ss/ebitmap.c  | 27 +++++++++++++++++++++++++++
 security/selinux/ss/ebitmap.h  |  1 +
 security/selinux/ss/policydb.c |  5 +++++
 3 files changed, 33 insertions(+)

diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c
index 43bc19e21960..5d6b5b72b3e5 100644
--- a/security/selinux/ss/ebitmap.c
+++ b/security/selinux/ss/ebitmap.c
@@ -257,6 +257,33 @@ int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2,
 	return 1;
 }
 
+u32 ebitmap_highest_set_bit(const struct ebitmap *e)
+{
+	const struct ebitmap_node *n;
+	unsigned long unit;
+	u32 pos = 0;
+
+	n = e->node;
+	if (!n)
+		return 0;
+
+	while (n->next)
+		n = n->next;
+
+	for (unsigned int i = EBITMAP_UNIT_NUMS; i > 0; i--) {
+		unit = n->maps[i - 1];
+		if (unit == 0)
+			continue;
+
+		pos = (i - 1) * EBITMAP_UNIT_SIZE;
+		while (unit >>= 1)
+			pos++;
+		break;
+	}
+
+	return n->startbit + pos;
+}
+
 int ebitmap_get_bit(const struct ebitmap *e, u32 bit)
 {
 	const struct ebitmap_node *n;
diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h
index c9569998f287..12bb359e83ff 100644
--- a/security/selinux/ss/ebitmap.h
+++ b/security/selinux/ss/ebitmap.h
@@ -126,6 +126,7 @@ int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1,
 		const struct ebitmap *e2);
 int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2,
 		     u32 last_e2bit);
+u32 ebitmap_highest_set_bit(const struct ebitmap *e);
 int ebitmap_get_bit(const struct ebitmap *e, u32 bit);
 int ebitmap_set_bit(struct ebitmap *e, u32 bit, int value);
 void ebitmap_destroy(struct ebitmap *e);
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 8969f7c8637c..27f6809b562a 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -2955,6 +2955,11 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 			if (rc)
 				goto bad;
 		}
+
+		rc = -EINVAL;
+		if (ebitmap_highest_set_bit(e) >= p->p_types.nprim)
+			goto bad;
+
 		/* add the type itself as the degenerate case */
 		rc = ebitmap_set_bit(e, i, 1);
 		if (rc)
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 08/14] selinux: reorder policydb_index()
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (5 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 07/14] selinux: check type attr map overflows Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 18:24   ` Stephen Smalley
  2026-05-06 23:43   ` [PATCH v3 8/14] " Paul Moore
  2025-05-11 17:30 ` [PATCH v3 09/14] selinux: beef up isvalid checks Christian Göttsche
                   ` (7 subsequent siblings)
  14 siblings, 2 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel

From: Christian Göttsche <cgzones@googlemail.com>

Index as soon as possible to enable isvalid() checks to fail on gaps.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/policydb.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 27f6809b562a..326d82f8db8c 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -728,7 +728,6 @@ static int policydb_index(struct policydb *p)
 	pr_debug("SELinux:  %d classes, %d rules\n", p->p_classes.nprim,
 		 p->te_avtab.nel);
 
-	avtab_hash_eval(&p->te_avtab, "rules");
 	symtab_hash_eval(p->symtab);
 
 	p->class_val_to_struct = kcalloc(p->p_classes.nprim,
@@ -2799,6 +2798,10 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 		p->symtab[i].nprim = nprim;
 	}
 
+	rc = policydb_index(p);
+	if (rc)
+		goto bad;
+
 	rc = -EINVAL;
 	p->process_class = string_to_security_class(p, "process");
 	if (!p->process_class) {
@@ -2810,6 +2813,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 	if (rc)
 		goto bad;
 
+	avtab_hash_eval(&p->te_avtab, "rules");
+
 	if (p->policyvers >= POLICYDB_VERSION_BOOL) {
 		rc = cond_read_list(p, fp);
 		if (rc)
@@ -2907,10 +2912,6 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 	if (rc)
 		goto bad;
 
-	rc = policydb_index(p);
-	if (rc)
-		goto bad;
-
 	rc = -EINVAL;
 	perm = string_to_av_perm(p, p->process_class, "transition");
 	if (!perm) {
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 09/14] selinux: beef up isvalid checks
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (6 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 08/14] selinux: reorder policydb_index() Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 18:59   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 10/14] selinux: validate symbols Christian Göttsche
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Casey Schaufler,
	Thiébaud Weksteen, Canfeng Guo, Takaya Saeki

From: Christian Göttsche <cgzones@googlemail.com>

Check that an ID does not refer to a gap in the global array of
definitions.

Constify parameters of isvalid() function and change return type to
bool.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/hashtab.h  |  4 +--
 security/selinux/ss/mls.c      | 66 +++++++++++++++++++++-------------
 security/selinux/ss/mls.h      |  6 ++--
 security/selinux/ss/policydb.c | 56 ++++++++++++++++-------------
 security/selinux/ss/policydb.h | 12 +++----
 security/selinux/ss/services.c |  2 +-
 security/selinux/ss/symtab.c   |  2 +-
 security/selinux/ss/symtab.h   |  2 +-
 8 files changed, 88 insertions(+), 62 deletions(-)

diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h
index deba82d78c3a..c641fb12916b 100644
--- a/security/selinux/ss/hashtab.h
+++ b/security/selinux/ss/hashtab.h
@@ -94,11 +94,11 @@ static inline int hashtab_insert(struct hashtab *h, void *key, void *datum,
  * Returns NULL if no entry has the specified key or
  * the datum of the entry otherwise.
  */
-static inline void *hashtab_search(struct hashtab *h, const void *key,
+static inline void *hashtab_search(const struct hashtab *h, const void *key,
 				   struct hashtab_key_params key_params)
 {
 	u32 hvalue;
-	struct hashtab_node *cur;
+	const struct hashtab_node *cur;
 
 	if (!h->size)
 		return NULL;
diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c
index a6e49269f535..3cd36e2015fa 100644
--- a/security/selinux/ss/mls.c
+++ b/security/selinux/ss/mls.c
@@ -32,7 +32,7 @@
 int mls_compute_context_len(struct policydb *p, struct context *context)
 {
 	int i, l, len, head, prev;
-	char *nm;
+	const char *nm;
 	struct ebitmap *e;
 	struct ebitmap_node *node;
 
@@ -86,7 +86,8 @@ int mls_compute_context_len(struct policydb *p, struct context *context)
 void mls_sid_to_context(struct policydb *p, struct context *context,
 			char **scontext)
 {
-	char *scontextp, *nm;
+	const char *nm;
+	char *scontextp;
 	int i, l, head, prev;
 	struct ebitmap *e;
 	struct ebitmap_node *node;
@@ -155,27 +156,44 @@ void mls_sid_to_context(struct policydb *p, struct context *context,
 	*scontext = scontextp;
 }
 
-int mls_level_isvalid(struct policydb *p, struct mls_level *l)
+bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l)
 {
-	struct level_datum *levdatum;
+	const char *name;
+	const struct level_datum *levdatum;
+	struct ebitmap_node *node;
+	u32 bit;
+	int rc;
 
 	if (!l->sens || l->sens > p->p_levels.nprim)
-		return 0;
-	levdatum = symtab_search(&p->p_levels,
-				 sym_name(p, SYM_LEVELS, l->sens - 1));
+		return false;
+
+	name = sym_name(p, SYM_LEVELS, l->sens - 1);
+	if (!name)
+		return false;
+
+	levdatum = symtab_search(&p->p_levels, name);
 	if (!levdatum)
-		return 0;
+		return false;
 
 	/*
-	 * Return 1 iff all the bits set in l->cat are also be set in
+	 * Validate that all bits set in l->cat are also be set in
 	 * levdatum->level->cat and no bit in l->cat is larger than
 	 * p->p_cats.nprim.
 	 */
-	return ebitmap_contains(&levdatum->level.cat, &l->cat,
-				p->p_cats.nprim);
+	rc = ebitmap_contains(&levdatum->level.cat, &l->cat,
+			      p->p_cats.nprim);
+	if (!rc)
+		return false;
+
+	ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) {
+		if (!sym_name(p, SYM_CATS, bit))
+			return false;
+	}
+
+	return true;
 }
 
-int mls_range_isvalid(struct policydb *p, struct mls_range *r)
+bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r)
 {
 	return (mls_level_isvalid(p, &r->level[0]) &&
 		mls_level_isvalid(p, &r->level[1]) &&
@@ -183,32 +201,32 @@ int mls_range_isvalid(struct policydb *p, struct mls_range *r)
 }
 
 /*
- * Return 1 if the MLS fields in the security context
+ * Return true if the MLS fields in the security context
  * structure `c' are valid.  Return 0 otherwise.
  */
-int mls_context_isvalid(struct policydb *p, struct context *c)
+bool mls_context_isvalid(const struct policydb *p, const struct context *c)
 {
-	struct user_datum *usrdatum;
+	const struct user_datum *usrdatum;
 
 	if (!p->mls_enabled)
-		return 1;
+		return true;
 
 	if (!mls_range_isvalid(p, &c->range))
-		return 0;
+		return false;
 
 	if (c->role == OBJECT_R_VAL)
-		return 1;
+		return true;
 
 	/*
 	 * User must be authorized for the MLS range.
 	 */
 	if (!c->user || c->user > p->p_users.nprim)
-		return 0;
+		return false;
 	usrdatum = p->user_val_to_struct[c->user - 1];
-	if (!mls_range_contains(usrdatum->range, c->range))
-		return 0; /* user may not be associated with range */
+	if (!usrdatum || !mls_range_contains(usrdatum->range, c->range))
+		return false; /* user may not be associated with range */
 
-	return 1;
+	return true;
 }
 
 /*
@@ -449,8 +467,8 @@ int mls_convert_context(struct policydb *oldp, struct policydb *newp,
 		return 0;
 
 	for (l = 0; l < 2; l++) {
-		char *name = sym_name(oldp, SYM_LEVELS,
-				      oldc->range.level[l].sens - 1);
+		const char *name = sym_name(oldp, SYM_LEVELS,
+					    oldc->range.level[l].sens - 1);
 
 		levdatum = symtab_search(&newp->p_levels, name);
 
diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h
index 07980636751f..93cde1b22992 100644
--- a/security/selinux/ss/mls.h
+++ b/security/selinux/ss/mls.h
@@ -27,9 +27,9 @@
 int mls_compute_context_len(struct policydb *p, struct context *context);
 void mls_sid_to_context(struct policydb *p, struct context *context,
 			char **scontext);
-int mls_context_isvalid(struct policydb *p, struct context *c);
-int mls_range_isvalid(struct policydb *p, struct mls_range *r);
-int mls_level_isvalid(struct policydb *p, struct mls_level *l);
+bool mls_context_isvalid(const struct policydb *p, const struct context *c);
+bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r);
+bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l);
 
 int mls_context_to_sid(struct policydb *p, char oldc, char *scontext,
 		       struct context *context, struct sidtab *s, u32 def_sid);
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 326d82f8db8c..f8d6e993ce89 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -923,51 +923,59 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s)
 	return 0;
 }
 
-int policydb_class_isvalid(struct policydb *p, u16 class)
+bool policydb_class_isvalid(const struct policydb *p, u16 class)
 {
 	if (!class || class > p->p_classes.nprim)
-		return 0;
-	return 1;
+		return false;
+	if (!p->sym_val_to_name[SYM_CLASSES][class - 1])
+		return false;
+	return true;
 }
 
-int policydb_role_isvalid(struct policydb *p, unsigned int role)
+bool policydb_role_isvalid(const struct policydb *p, u32 role)
 {
 	if (!role || role > p->p_roles.nprim)
-		return 0;
-	return 1;
+		return false;
+	if (!p->sym_val_to_name[SYM_ROLES][role - 1])
+		return false;
+	return true;
 }
 
-int policydb_type_isvalid(struct policydb *p, unsigned int type)
+bool policydb_type_isvalid(const struct policydb *p, u32 type)
 {
 	if (!type || type > p->p_types.nprim)
-		return 0;
-	return 1;
+		return false;
+	if (!p->sym_val_to_name[SYM_TYPES][type - 1])
+		return false;
+	return true;
 }
 
-int policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
+bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
 {
 	if (!boolean || boolean > p->p_bools.nprim)
-		return 0;
-	return 1;
+		return false;
+	if (!p->sym_val_to_name[SYM_BOOLS][boolean - 1])
+		return false;
+	return true;
 }
 
 /*
- * Return 1 if the fields in the security context
+ * Return true if the fields in the security context
  * structure `c' are valid.  Return 0 otherwise.
  */
-int policydb_context_isvalid(struct policydb *p, struct context *c)
+bool policydb_context_isvalid(const struct policydb *p, const struct context *c)
 {
-	struct role_datum *role;
-	struct user_datum *usrdatum;
+	const struct role_datum *role;
+	const struct user_datum *usrdatum;
 
 	if (!c->role || c->role > p->p_roles.nprim)
-		return 0;
+		return false;
 
 	if (!c->user || c->user > p->p_users.nprim)
-		return 0;
+		return false;
 
 	if (!c->type || c->type > p->p_types.nprim)
-		return 0;
+		return false;
 
 	if (c->role != OBJECT_R_VAL) {
 		/*
@@ -976,24 +984,24 @@ int policydb_context_isvalid(struct policydb *p, struct context *c)
 		role = p->role_val_to_struct[c->role - 1];
 		if (!role || !ebitmap_get_bit(&role->types, c->type - 1))
 			/* role may not be associated with type */
-			return 0;
+			return false;
 
 		/*
 		 * User must be authorized for the role.
 		 */
 		usrdatum = p->user_val_to_struct[c->user - 1];
 		if (!usrdatum)
-			return 0;
+			return false;
 
 		if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1))
 			/* user may not be associated with role */
-			return 0;
+			return false;
 	}
 
 	if (!mls_context_isvalid(p, c))
-		return 0;
+		return false;
 
-	return 1;
+	return true;
 }
 
 /*
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 42117adb2ca0..1367387beaa7 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -320,11 +320,11 @@ struct policy_file {
 
 extern void policydb_destroy(struct policydb *p);
 extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
-extern int policydb_context_isvalid(struct policydb *p, struct context *c);
-extern int policydb_class_isvalid(struct policydb *p, u16 class);
-extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
-extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
-extern int policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
+extern bool policydb_context_isvalid(const struct policydb *p, const struct context *c);
+extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
+extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
+extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
+extern bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
 extern int policydb_read(struct policydb *p, struct policy_file *fp);
 extern int policydb_write(struct policydb *p, struct policy_file *fp);
 
@@ -395,7 +395,7 @@ static inline int put_entry(const void *buf, size_t bytes, size_t num,
 	return 0;
 }
 
-static inline char *sym_name(struct policydb *p, unsigned int sym_num,
+static inline const char *sym_name(const struct policydb *p, unsigned int sym_num,
 			     unsigned int element_nr)
 {
 	return p->sym_val_to_name[sym_num][element_nr];
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 3fb971fe4fd9..5b1d0e80d975 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -464,7 +464,7 @@ static void security_dump_masked_av(struct policydb *policydb,
 	struct common_datum *common_dat;
 	struct class_datum *tclass_dat;
 	struct audit_buffer *ab;
-	char *tclass_name;
+	const char *tclass_name;
 	char *scontext_name = NULL;
 	char *tcontext_name = NULL;
 	char *permission_names[SEL_VEC_MAX];
diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c
index 832660fd84a9..a756554e7f1d 100644
--- a/security/selinux/ss/symtab.c
+++ b/security/selinux/ss/symtab.c
@@ -50,7 +50,7 @@ int symtab_insert(struct symtab *s, char *name, void *datum)
 	return hashtab_insert(&s->table, name, datum, symtab_key_params);
 }
 
-void *symtab_search(struct symtab *s, const char *name)
+void *symtab_search(const struct symtab *s, const char *name)
 {
 	return hashtab_search(&s->table, name, symtab_key_params);
 }
diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h
index 8e667cdbf38f..7cfa3b44953a 100644
--- a/security/selinux/ss/symtab.h
+++ b/security/selinux/ss/symtab.h
@@ -21,6 +21,6 @@ struct symtab {
 int symtab_init(struct symtab *s, u32 size);
 
 int symtab_insert(struct symtab *s, char *name, void *datum);
-void *symtab_search(struct symtab *s, const char *name);
+void *symtab_search(const struct symtab *s, const char *name);
 
 #endif /* _SS_SYMTAB_H_ */
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 10/14] selinux: validate symbols
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (7 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 09/14] selinux: beef up isvalid checks Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 19:06   ` Stephen Smalley
  2026-05-06 23:43   ` Paul Moore
  2025-05-11 17:30 ` [PATCH v3 11/14] selinux: more strict bounds check Christian Göttsche
                   ` (5 subsequent siblings)
  14 siblings, 2 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Nathan Chancellor,
	Nick Desaulniers, Bill Wendling, Justin Stitt, llvm

From: Christian Göttsche <cgzones@googlemail.com>

Some symbol tables need to be validated after indexing, since during
indexing their referenced entries might not yet have been indexed.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/policydb.c | 94 ++++++++++++++++++++++++++++++++++
 1 file changed, 94 insertions(+)

diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index f8d6e993ce89..4559c8918134 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -673,6 +673,84 @@ static int (*const index_f[SYM_NUM])(void *key, void *datum, void *datap) = {
 };
 /* clang-format on */
 
+static int role_validate(void *key, void *datum, void *datap)
+{
+	const struct policydb *p = datap;
+	const struct role_datum *role = datum;
+	struct ebitmap_node *node;
+	u32 i;
+
+	ebitmap_for_each_positive_bit(&role->dominates, node, i) {
+		if (!policydb_role_isvalid(p, i))
+			goto bad;
+	}
+
+	ebitmap_for_each_positive_bit(&role->types, node, i) {
+		if (!policydb_type_isvalid(p, i + 1))
+			goto bad;
+	}
+
+	return 0;
+
+bad:
+	pr_err("SELinux:  invalid role %s\n", sym_name(p, SYM_ROLES, role->value - 1));
+	return -EINVAL;
+}
+
+static int user_validate(void *key, void *datum, void *datap)
+{
+	const struct policydb *p = datap;
+	const struct user_datum *usrdatum = datum;
+	struct ebitmap_node *node;
+	u32 i;
+
+	ebitmap_for_each_positive_bit(&usrdatum->roles, node, i) {
+		if (!policydb_role_isvalid(p, i))
+			goto bad;
+	}
+
+	if (!mls_range_isvalid(p, &usrdatum->range))
+		goto bad;
+
+	if (!mls_level_isvalid(p, &usrdatum->dfltlevel))
+		goto bad;
+
+	return 0;
+
+bad:
+	pr_err("SELinux:  invalid user %s\n", sym_name(p, SYM_USERS, usrdatum->value - 1));
+	return -EINVAL;
+}
+
+static int sens_validate(void *key, void *datum, void *datap)
+{
+	const struct policydb *p = datap;
+	const struct level_datum *levdatum = datum;
+
+	if (!mls_level_isvalid(p, &levdatum->level))
+		goto bad;
+
+	return 0;
+
+bad:
+	pr_err("SELinux:  invalid sensitivity\n");
+	return -EINVAL;
+}
+
+
+/* clang-format off */
+static int (*const validate_f[SYM_NUM])(void *key, void *datum, void *datap) = {
+	NULL, /* Everything validated in common_read() and common_index() */
+	NULL, /* Everything validated in class_read() and class_index() */
+	role_validate,
+	NULL, /* Everything validated in type_read(), type_index() and type_bounds_sanity_check() */
+	user_validate,
+	NULL, /* Everything validated in cond_read_bool() and cond_index_bool() */
+	sens_validate,
+	NULL, /* Everything validated in cat_read() and cat_index() */
+};
+/* clang-format on */
+
 #ifdef CONFIG_SECURITY_SELINUX_DEBUG
 static void hash_eval(struct hashtab *h, const char *hash_name,
 		      const char *hash_details)
@@ -765,6 +843,16 @@ static int policydb_index(struct policydb *p)
 		if (rc)
 			goto out;
 	}
+
+	for (i = 0; i < SYM_NUM; i++) {
+		if (!validate_f[i])
+			continue;
+
+		rc = hashtab_map(&p->symtab[i].table, validate_f[i], p);
+		if (rc)
+			goto out;
+	}
+
 	rc = 0;
 out:
 	return rc;
@@ -1087,6 +1175,12 @@ static int context_read_and_validate(struct context *c, struct policydb *p,
 			pr_err("SELinux: error reading MLS range of context\n");
 			goto out;
 		}
+
+		rc = -EINVAL;
+		if (!mls_range_isvalid(p, &c->range)) {
+			pr_warn("SELinux: invalid range in security context\n");
+			goto out;
+		}
 	}
 
 	rc = -EINVAL;
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 11/14] selinux: more strict bounds check
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (8 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 10/14] selinux: validate symbols Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 19:11   ` Stephen Smalley
  2026-05-01 17:15   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 12/14] selinux: check for simple types Christian Göttsche
                   ` (4 subsequent siblings)
  14 siblings, 2 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Casey Schaufler,
	Thiébaud Weksteen, Canfeng Guo, Takaya Saeki

From: Christian Göttsche <cgzones@googlemail.com>

Validate the types used in bounds checks.
Replace the usage of BUG(), to avoid halting the system on malformed
polices.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/policydb.c | 29 +++++++++++++++++++++++++++--
 security/selinux/ss/policydb.h |  1 +
 security/selinux/ss/services.c |  3 +++
 3 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 4559c8918134..7774f6da2ebe 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -1020,6 +1020,15 @@ bool policydb_class_isvalid(const struct policydb *p, u16 class)
 	return true;
 }
 
+bool policydb_user_isvalid(const struct policydb *p, u32 user)
+{
+	if (!user || user > p->p_roles.nprim)
+		return false;
+	if (!p->sym_val_to_name[SYM_USERS][user - 1])
+		return false;
+	return true;
+}
+
 bool policydb_role_isvalid(const struct policydb *p, u32 role)
 {
 	if (!role || role > p->p_roles.nprim)
@@ -1942,6 +1951,12 @@ static int user_bounds_sanity_check(void *key, void *datum, void *datap)
 			return -EINVAL;
 		}
 
+		if (!policydb_user_isvalid(p, upper->bounds)) {
+			pr_err("SELinux: user %s: invalid boundary id %d\n",
+			       (char *) key, upper->bounds);
+			return -EINVAL;
+		}
+
 		upper = p->user_val_to_struct[upper->bounds - 1];
 		ebitmap_for_each_positive_bit(&user->roles, node, bit)
 		{
@@ -1979,6 +1994,12 @@ static int role_bounds_sanity_check(void *key, void *datum, void *datap)
 			return -EINVAL;
 		}
 
+		if (!policydb_role_isvalid(p, upper->bounds)) {
+			pr_err("SELinux: role %s: invalid boundary id %d\n",
+			       (char *) key, upper->bounds);
+			return -EINVAL;
+		}
+
 		upper = p->role_val_to_struct[upper->bounds - 1];
 		ebitmap_for_each_positive_bit(&role->types, node, bit)
 		{
@@ -2013,9 +2034,13 @@ static int type_bounds_sanity_check(void *key, void *datum, void *datap)
 			return -EINVAL;
 		}
 
-		upper = p->type_val_to_struct[upper->bounds - 1];
-		BUG_ON(!upper);
+		if (!policydb_type_isvalid(p, upper->bounds)) {
+			pr_err("SELinux: type %s: invalid boundary id %d\n",
+			       (char *) key, upper->bounds);
+			return -EINVAL;
+		}
 
+		upper = p->type_val_to_struct[upper->bounds - 1];
 		if (upper->attribute) {
 			pr_err("SELinux: type %s: "
 			       "bounded by attribute %s\n",
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 1367387beaa7..04acf414fffa 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -324,6 +324,7 @@ extern bool policydb_context_isvalid(const struct policydb *p, const struct cont
 extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
 extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
 extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
+extern bool policydb_user_isvalid(const struct policydb *p, u32 user);
 extern bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
 extern int policydb_read(struct policydb *p, struct policy_file *fp);
 extern int policydb_write(struct policydb *p, struct policy_file *fp);
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 5b1d0e80d975..464a4663c993 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -718,6 +718,9 @@ static void context_struct_compute_av(struct policydb *policydb,
 	 * If the given source and target types have boundary
 	 * constraint, lazy checks have to mask any violated
 	 * permission and notice it to userspace via audit.
+	 *
+	 * Infinite recursion is avoided via a depth pre-check in
+	 * type_bounds_sanity_check().
 	 */
 	type_attribute_bounds_av(policydb, scontext, tcontext,
 				 tclass, avd);
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 12/14] selinux: check for simple types
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (9 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 11/14] selinux: more strict bounds check Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 19:22   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 13/14] selinux: restrict policy strings Christian Göttsche
                   ` (3 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Eric Suen

From: Christian Göttsche <cgzones@googlemail.com>

Validate that the target of AVTAB_TYPE rules and file transitions are
simple types and not attributes.

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
 security/selinux/ss/avtab.c    |  9 ++++++++-
 security/selinux/ss/policydb.c | 23 +++++++++++++++++++++--
 security/selinux/ss/policydb.h |  1 +
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index 50df8b69de2b..5b45f37fdcbb 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -426,6 +426,13 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 				}
 				key.specified = spec_order[i] | enabled;
 				datum.u.data = le32_to_cpu(buf32[items++]);
+
+				if ((key.specified & AVTAB_TYPE) &&
+				    !policydb_simpletype_isvalid(pol, datum.u.data)) {
+					pr_err("SELinux: avtab: invalid type\n");
+					return -EINVAL;
+				}
+
 				rc = insertf(a, &key, &datum, p);
 				if (rc)
 					return rc;
@@ -517,7 +524,7 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
 		datum.u.data = le32_to_cpu(*buf32);
 	}
 	if ((key.specified & AVTAB_TYPE) &&
-	    !policydb_type_isvalid(pol, datum.u.data)) {
+	    !policydb_simpletype_isvalid(pol, datum.u.data)) {
 		pr_err("SELinux: avtab: invalid type\n");
 		return -EINVAL;
 	}
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 7774f6da2ebe..2b098d9abf17 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -686,7 +686,7 @@ static int role_validate(void *key, void *datum, void *datap)
 	}
 
 	ebitmap_for_each_positive_bit(&role->types, node, i) {
-		if (!policydb_type_isvalid(p, i + 1))
+		if (!policydb_simpletype_isvalid(p, i + 1))
 			goto bad;
 	}
 
@@ -1047,6 +1047,23 @@ bool policydb_type_isvalid(const struct policydb *p, u32 type)
 	return true;
 }
 
+bool policydb_simpletype_isvalid(const struct policydb *p, u32 type)
+{
+	const struct type_datum *datum;
+
+	if (!type || type > p->p_types.nprim)
+		return false;
+
+	datum = p->type_val_to_struct[type - 1];
+	if (!datum)
+		return false;
+
+	if (datum->attribute)
+		return false;
+
+	return true;
+}
+
 bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
 {
 	if (!boolean || boolean > p->p_bools.nprim)
@@ -2235,6 +2252,8 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
 	key.name = name;
 
 	otype = le32_to_cpu(buf[3]);
+	if (!policydb_simpletype_isvalid(p, otype))
+		goto out;
 
 	last = NULL;
 	datum = policydb_filenametr_search(p, &key);
@@ -2357,7 +2376,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 		datum->otype = le32_to_cpu(buf[0]);
 
 		rc = -EINVAL;
-		if (!policydb_type_isvalid(p, datum->otype))
+		if (!policydb_simpletype_isvalid(p, datum->otype))
 			goto out;
 
 		dst = &datum->next;
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index 04acf414fffa..b4f0c1a754cf 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -323,6 +323,7 @@ extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
 extern bool policydb_context_isvalid(const struct policydb *p, const struct context *c);
 extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
 extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
+extern bool policydb_simpletype_isvalid(const struct policydb *p, u32 type);
 extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
 extern bool policydb_user_isvalid(const struct policydb *p, u32 user);
 extern bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 13/14] selinux: restrict policy strings
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (10 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 12/14] selinux: check for simple types Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 19:40   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 14/14] selinux: harden MLS context string generation against overflows Christian Göttsche
                   ` (2 subsequent siblings)
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel

From: Christian Göttsche <cgzones@googlemail.com>

Validate the characters and the lengths of strings parsed from binary
policies.

  * Disallow control characters
  * Limit characters of identifiers to alphanumeric, underscore, dash,
    and dot
  * Limit identifiers in length to 64, expect types to 1024,
    sensitivities to 32 and categories to 16, characters
    (excluding NUL-terminator)

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3:
  - introduce a central limits.h header
  - add limits for all kinds of string: filesystem names, filetrans
    keys, genfs paths, infiniband device names
v2:
  - add wrappers for str_read() to minimize the usage of magic numbers
  - limit sensitivities to a length of 32, to match categories,
    suggested by Daniel
---
 security/selinux/include/limits.h | 90 +++++++++++++++++++++++++++++++
 security/selinux/ss/conditional.c |  5 +-
 security/selinux/ss/conditional.h |  2 -
 security/selinux/ss/constraint.h  |  2 -
 security/selinux/ss/policydb.c    | 78 ++++++++++++++++++---------
 security/selinux/ss/policydb.h    | 51 +++++++++++++++++-
 6 files changed, 196 insertions(+), 32 deletions(-)
 create mode 100644 security/selinux/include/limits.h

diff --git a/security/selinux/include/limits.h b/security/selinux/include/limits.h
new file mode 100644
index 000000000000..d267c0c64f49
--- /dev/null
+++ b/security/selinux/include/limits.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Limits for various policy database elements.
+ */
+
+/*
+ * Maximum supported depth of conditional expressions.
+ */
+#define COND_EXPR_MAXDEPTH 10
+
+/*
+ * Maximum supported depth for constraint expressions.
+ */
+#define CEXPR_MAXDEPTH 5
+
+/*
+ * Maximum supported identifier value.
+ *
+ * Reasoning: The most used symbols are types and they need to fit into
+ *            an u16 for the avtab entries. Keep U16_MAX as special value
+ *            and U16_MAX-1 to avoid accidental overflows into U16_MAX.
+ */
+#define IDENTIFIER_MAXVALUE (U16_MAX - 2)
+
+/*
+ * Maximum supported length of security context strings.
+ *
+ * Reasoning: The string must fir into a PAGE_SIZE.
+ */
+#define CONTEXT_MAXLENGTH 4000
+
+/*
+ * Maximum supported boolean name length.
+ */
+#define BOOLEAN_NAME_MAXLENGTH 64
+
+/*
+ * Maximum supported security class and common class name length.
+ */
+#define CLASS_NAME_MAXLENGTH 64
+
+/*
+ * Maximum supported permission name length.
+ */
+#define PERMISSION_NAME_MAXLENGTH 64
+
+/*
+ * Maximum supported user name length.
+ */
+#define USER_NAME_MAXLENGTH 64
+
+/*
+ * Maximum supported role name length.
+ */
+#define ROLE_NAME_MAXLENGTH 64
+
+/*
+ * Maximum supported type name length.
+ */
+#define TYPE_NAME_MAXLENGTH 1024
+
+/*
+ * Maximum supported sensitivity name length.
+ */
+#define SENSITIVITY_NAME_MAXLENGTH 32
+
+/*
+ * Maximum supported category name length.
+ */
+#define CATEGORY_NAME_MAXLENGTH 16
+
+/*
+ * Maximum supported path name length for keys in filename transitions.
+ */
+#define FILETRANSKEY_NAME_MAXLENGTH 1024
+
+/*
+ * Maximum supported filesystem name length.
+ */
+#define FILESYSTEM_NAME_MAXLENGTH 128
+
+/*
+ * Maximum supported path prefix length for genfs statements.
+ */
+#define GENFS_PATH_MAXLENGTH 1024
+
+/*
+ * Maximum supported Infiniband device name length.
+ */
+#define INFINIBAND_DEVNAME_MAXLENGTH 256
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index ce0281cce739..c0a2814dafdb 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -245,7 +245,8 @@ int cond_index_bool(void *key, void *datum, void *datap)
 	booldatum = datum;
 	p = datap;
 
-	if (!booldatum->value || booldatum->value > p->p_bools.nprim)
+	if (!booldatum->value || booldatum->value > p->p_bools.nprim ||
+	    booldatum->value > IDENTIFIER_MAXVALUE)
 		return -EINVAL;
 
 	p->sym_val_to_name[SYM_BOOLS][booldatum->value - 1] = key;
@@ -280,7 +281,7 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
 
 	len = le32_to_cpu(buf[2]);
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_bool(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto err;
 
diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
index 468e98ad3ea1..d5aefcbaa1eb 100644
--- a/security/selinux/ss/conditional.h
+++ b/security/selinux/ss/conditional.h
@@ -12,8 +12,6 @@
 #include "policydb.h"
 #include "../include/conditional.h"
 
-#define COND_EXPR_MAXDEPTH 10
-
 /*
  * A conditional expression is a list of operators and operands
  * in reverse polish notation.
diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
index 1d75a8a044df..f986156de856 100644
--- a/security/selinux/ss/constraint.h
+++ b/security/selinux/ss/constraint.h
@@ -19,8 +19,6 @@
 
 #include "ebitmap.h"
 
-#define CEXPR_MAXDEPTH 5
-
 struct constraint_expr {
 #define CEXPR_NOT   1 /* not expr */
 #define CEXPR_AND   2 /* expr and expr */
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index 2b098d9abf17..e64254985762 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -552,7 +552,8 @@ static int common_index(void *key, void *datum, void *datap)
 
 	comdatum = datum;
 	p = datap;
-	if (!comdatum->value || comdatum->value > p->p_commons.nprim)
+	if (!comdatum->value || comdatum->value > p->p_commons.nprim ||
+	    comdatum->value > IDENTIFIER_MAXVALUE)
 		return -EINVAL;
 
 	p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1] = key;
@@ -567,7 +568,8 @@ static int class_index(void *key, void *datum, void *datap)
 
 	cladatum = datum;
 	p = datap;
-	if (!cladatum->value || cladatum->value > p->p_classes.nprim)
+	if (!cladatum->value || cladatum->value > p->p_classes.nprim ||
+	    cladatum->value > IDENTIFIER_MAXVALUE)
 		return -EINVAL;
 
 	p->sym_val_to_name[SYM_CLASSES][cladatum->value - 1] = key;
@@ -583,6 +585,7 @@ static int role_index(void *key, void *datum, void *datap)
 	role = datum;
 	p = datap;
 	if (!role->value || role->value > p->p_roles.nprim ||
+	    role->value > IDENTIFIER_MAXVALUE ||
 	    role->bounds > p->p_roles.nprim)
 		return -EINVAL;
 
@@ -601,6 +604,7 @@ static int type_index(void *key, void *datum, void *datap)
 
 	if (typdatum->primary) {
 		if (!typdatum->value || typdatum->value > p->p_types.nprim ||
+		    typdatum->value > IDENTIFIER_MAXVALUE ||
 		    typdatum->bounds > p->p_types.nprim)
 			return -EINVAL;
 		p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key;
@@ -618,6 +622,7 @@ static int user_index(void *key, void *datum, void *datap)
 	usrdatum = datum;
 	p = datap;
 	if (!usrdatum->value || usrdatum->value > p->p_users.nprim ||
+	    usrdatum->value > IDENTIFIER_MAXVALUE ||
 	    usrdatum->bounds > p->p_users.nprim)
 		return -EINVAL;
 
@@ -634,7 +639,8 @@ static int sens_index(void *key, void *datum, void *datap)
 	levdatum = datum;
 	p = datap;
 
-	if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim)
+	if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim ||
+	    levdatum->level.sens > IDENTIFIER_MAXVALUE)
 		return -EINVAL;
 
 	if (!levdatum->isalias)
@@ -651,7 +657,8 @@ static int cat_index(void *key, void *datum, void *datap)
 	catdatum = datum;
 	p = datap;
 
-	if (!catdatum->value || catdatum->value > p->p_cats.nprim)
+	if (!catdatum->value || catdatum->value > p->p_cats.nprim ||
+	    catdatum->value > IDENTIFIER_MAXVALUE)
 		return -EINVAL;
 
 	if (!catdatum->isalias)
@@ -1226,8 +1233,9 @@ static int context_read_and_validate(struct context *c, struct policydb *p,
  * binary representation file.
  */
 
-int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len, int kind, u32 max_len)
 {
+	u32 i;
 	int rc;
 	char *str;
 
@@ -1237,19 +1245,35 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
 	if (size_check(sizeof(char), len, fp))
 		return -EINVAL;
 
+	if (len > max_len)
+		return -EINVAL;
+
 	str = kmalloc(len + 1, flags | __GFP_NOWARN);
 	if (!str)
 		return -ENOMEM;
 
 	rc = next_entry(str, fp, len);
-	if (rc) {
-		kfree(str);
-		return rc;
+	if (rc)
+		goto bad_str;
+
+	rc = -EINVAL;
+	for (i = 0; i < len; i++) {
+		if (iscntrl(str[i]))
+			goto bad_str;
+
+		if (kind == STR_IDENTIFIER &&
+		    !(isalnum(str[i]) || str[i] == '_' || str[i] == '-' || str[i] == '.'))
+			goto bad_str;
+
 	}
 
 	str[len] = '\0';
 	*strp = str;
 	return 0;
+
+bad_str:
+	kfree(str);
+	return rc;
 }
 
 static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
@@ -1274,7 +1298,7 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f
 	if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
 		goto bad;
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_perm(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -1321,7 +1345,7 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
 		goto bad;
 	comdatum->permissions.nprim = le32_to_cpu(buf[2]);
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_class(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -1559,12 +1583,12 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
 
 	ncons = le32_to_cpu(buf[5]);
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_class(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
 	if (len2) {
-		rc = str_read(&cladatum->comkey, GFP_KERNEL, fp, len2);
+		rc = str_read_class(&cladatum->comkey, GFP_KERNEL, fp, len2);
 		if (rc)
 			goto bad;
 
@@ -1698,7 +1722,7 @@ static int role_read(struct policydb *p, struct symtab *s, struct policy_file *f
 	if (p->policyvers >= POLICYDB_VERSION_BOUNDARY)
 		role->bounds = le32_to_cpu(buf[2]);
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_role(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -1765,7 +1789,7 @@ static int type_read(struct policydb *p, struct symtab *s, struct policy_file *f
 		typdatum->primary = le32_to_cpu(buf[2]);
 	}
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_type(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -1829,7 +1853,7 @@ static int user_read(struct policydb *p, struct symtab *s, struct policy_file *f
 	if (p->policyvers >= POLICYDB_VERSION_BOUNDARY)
 		usrdatum->bounds = le32_to_cpu(buf[2]);
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_user(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -1878,7 +1902,7 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
 		goto bad;
 	levdatum->isalias = val;
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_sens(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -1921,7 +1945,7 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
 		goto bad;
 	catdatum->isalias = val;
 
-	rc = str_read(&key, GFP_KERNEL, fp, len);
+	rc = str_read_cat(&key, GFP_KERNEL, fp, len);
 	if (rc)
 		goto bad;
 
@@ -2230,7 +2254,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
 	len = le32_to_cpu(buf[0]);
 
 	/* path component string */
-	rc = str_read(&name, GFP_KERNEL, fp, len);
+	rc = str_read(&name, GFP_KERNEL, fp, len, STR_UNCONSTRAINT, FILETRANSKEY_NAME_MAXLENGTH);
 	if (rc)
 		return rc;
 
@@ -2329,7 +2353,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
 	len = le32_to_cpu(buf[0]);
 
 	/* path component string */
-	rc = str_read(&name, GFP_KERNEL, fp, len);
+	rc = str_read(&name, GFP_KERNEL, fp, len, STR_UNCONSTRAINT, FILETRANSKEY_NAME_MAXLENGTH);
 	if (rc)
 		return rc;
 
@@ -2483,7 +2507,7 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
 		if (!newgenfs)
 			goto out;
 
-		rc = str_read(&newgenfs->fstype, GFP_KERNEL, fp, len);
+		rc = str_read_fsname(&newgenfs->fstype, GFP_KERNEL, fp, len);
 		if (rc)
 			goto out;
 
@@ -2522,7 +2546,8 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
 			if (!newc)
 				goto out;
 
-			rc = str_read(&newc->u.name, GFP_KERNEL, fp, len);
+			rc = str_read(&newc->u.name, GFP_KERNEL, fp, len,
+				      STR_UNCONSTRAINT, GENFS_PATH_MAXLENGTH);
 			if (rc)
 				goto out;
 
@@ -2625,7 +2650,7 @@ static int ocontext_read(struct policydb *p,
 					goto out;
 				len = le32_to_cpu(buf[0]);
 
-				rc = str_read(&c->u.name, GFP_KERNEL, fp, len);
+				rc = str_read_fsname(&c->u.name, GFP_KERNEL, fp, len);
 				if (rc)
 					goto out;
 
@@ -2693,7 +2718,7 @@ static int ocontext_read(struct policydb *p,
 					goto out;
 
 				len = le32_to_cpu(buf[1]);
-				rc = str_read(&c->u.name, GFP_KERNEL, fp, len);
+				rc = str_read_fsname(&c->u.name, GFP_KERNEL, fp, len);
 				if (rc)
 					goto out;
 
@@ -2759,7 +2784,9 @@ static int ocontext_read(struct policydb *p,
 				len = le32_to_cpu(buf[0]);
 
 				rc = str_read(&c->u.ibendport.dev_name,
-					      GFP_KERNEL, fp, len);
+					      GFP_KERNEL, fp, len,
+					      STR_UNCONSTRAINT,
+					      INFINIBAND_DEVNAME_MAXLENGTH);
 				if (rc)
 					goto out;
 
@@ -2827,7 +2854,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
 		goto bad;
 	}
 
-	rc = str_read(&policydb_str, GFP_KERNEL, fp, len);
+	rc = str_read(&policydb_str, GFP_KERNEL, fp, len,
+		      STR_UNCONSTRAINT, strlen(POLICYDB_STRING));
 	if (rc) {
 		if (rc == -ENOMEM) {
 			pr_err("SELinux:  unable to allocate memory for policydb string of length %d\n",
diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
index b4f0c1a754cf..e901ec648cbf 100644
--- a/security/selinux/ss/policydb.h
+++ b/security/selinux/ss/policydb.h
@@ -27,6 +27,7 @@
 #include "mls_types.h"
 #include "context.h"
 #include "constraint.h"
+#include "limits.h"
 
 /*
  * A datum type is defined for each kind of symbol
@@ -408,7 +409,55 @@ static inline bool val_is_boolean(u32 value)
 	return value == 0 || value == 1;
 }
 
-extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
+#define STR_UNCONSTRAINT 0
+#define STR_IDENTIFIER 1
+extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len,
+		    int kind, u32 max_len);
+
+static inline int str_read_bool(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, BOOLEAN_NAME_MAXLENGTH);
+}
+
+static inline int str_read_cat(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, CATEGORY_NAME_MAXLENGTH);
+}
+
+static inline int str_read_class(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, CLASS_NAME_MAXLENGTH);
+}
+
+static inline int str_read_perm(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, PERMISSION_NAME_MAXLENGTH);
+}
+
+static inline int str_read_role(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, ROLE_NAME_MAXLENGTH);
+}
+
+static inline int str_read_sens(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, SENSITIVITY_NAME_MAXLENGTH);
+}
+
+static inline int str_read_type(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, TYPE_NAME_MAXLENGTH);
+}
+
+static inline int str_read_user(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, USER_NAME_MAXLENGTH);
+}
+
+static inline int str_read_fsname(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+	return str_read(strp, flags, fp, len, STR_IDENTIFIER, FILESYSTEM_NAME_MAXLENGTH);
+}
 
 extern u16 string_to_security_class(struct policydb *p, const char *name);
 extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 14/14] selinux: harden MLS context string generation against overflows
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (11 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 13/14] selinux: restrict policy strings Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-14 19:53   ` Stephen Smalley
  2025-05-11 17:30 ` [PATCH v3 00/14] selinux: harden against malformed policies Christian Göttsche
  2025-05-13 19:40 ` [PATCH v3 01/14] selinux: avoid nontransitive comparison Stephen Smalley
  14 siblings, 1 reply; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel, Thiébaud Weksteen,
	Casey Schaufler, Canfeng Guo, Takaya Saeki

From: Christian Göttsche <cgzones@googlemail.com>

Check the length accumulator for the MLS component of security contexts
does not overflow in mls_compute_context_len() resulting in
out-of-buffer writes in mls_sid_to_context().

Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
---
v3: add patch
---
 security/selinux/ss/mls.c      | 18 +++++++++-----
 security/selinux/ss/services.c | 43 +++++++++++++++++++++++++++++-----
 2 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c
index 3cd36e2015fa..aa25724f0b0f 100644
--- a/security/selinux/ss/mls.c
+++ b/security/selinux/ss/mls.c
@@ -42,7 +42,8 @@ int mls_compute_context_len(struct policydb *p, struct context *context)
 	len = 1; /* for the beginning ":" */
 	for (l = 0; l < 2; l++) {
 		u32 index_sens = context->range.level[l].sens;
-		len += strlen(sym_name(p, SYM_LEVELS, index_sens - 1));
+		if (check_add_overflow(len, strlen(sym_name(p, SYM_LEVELS, index_sens - 1)), &len))
+			return -EOVERFLOW;
 
 		/* categories */
 		head = -2;
@@ -54,24 +55,29 @@ int mls_compute_context_len(struct policydb *p, struct context *context)
 				/* one or more negative bits are skipped */
 				if (head != prev) {
 					nm = sym_name(p, SYM_CATS, prev);
-					len += strlen(nm) + 1;
+					if (check_add_overflow(len, strlen(nm) + 1, &len))
+						return -EOVERFLOW;
 				}
 				nm = sym_name(p, SYM_CATS, i);
-				len += strlen(nm) + 1;
+				if (check_add_overflow(len, strlen(nm) + 1, &len))
+					return -EOVERFLOW;
 				head = i;
 			}
 			prev = i;
 		}
 		if (prev != head) {
 			nm = sym_name(p, SYM_CATS, prev);
-			len += strlen(nm) + 1;
+			if (check_add_overflow(len, strlen(nm) + 1, &len))
+				return -EOVERFLOW;
 		}
 		if (l == 0) {
 			if (mls_level_eq(&context->range.level[0],
 					 &context->range.level[1]))
 				break;
-			else
-				len++;
+			else {
+				if (check_add_overflow(len, 1, &len))
+					return -EOVERFLOW;
+			}
 		}
 	}
 
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 464a4663c993..dc6dce2eb7d2 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -1247,10 +1247,12 @@ static int context_struct_to_string(struct policydb *p,
 				    char **scontext, u32 *scontext_len)
 {
 	char *scontextp;
+	size_t len;
+	int mls_len;
 
 	if (scontext)
 		*scontext = NULL;
-	*scontext_len = 0;
+	len = 0;
 
 	if (context->len) {
 		*scontext_len = context->len;
@@ -1263,16 +1265,45 @@ static int context_struct_to_string(struct policydb *p,
 	}
 
 	/* Compute the size of the context. */
-	*scontext_len += strlen(sym_name(p, SYM_USERS, context->user - 1)) + 1;
-	*scontext_len += strlen(sym_name(p, SYM_ROLES, context->role - 1)) + 1;
-	*scontext_len += strlen(sym_name(p, SYM_TYPES, context->type - 1)) + 1;
-	*scontext_len += mls_compute_context_len(p, context);
+	len += strlen(sym_name(p, SYM_USERS, context->user - 1)) + 1;
+	len += strlen(sym_name(p, SYM_ROLES, context->role - 1)) + 1;
+	len += strlen(sym_name(p, SYM_TYPES, context->type - 1)) + 1;
+
+	mls_len = mls_compute_context_len(p, context);
+	if (unlikely(mls_len < 0)) {
+		pr_warn_ratelimited("SELinux: %s:  MLS security context component too large [%s:%s:%s[:[%s:%d]-[%s:%d]]]\n",
+				    __func__,
+				    sym_name(p, SYM_USERS, context->user - 1),
+				    sym_name(p, SYM_ROLES, context->role - 1),
+				    sym_name(p, SYM_TYPES, context->type - 1),
+				    sym_name(p, SYM_LEVELS, context->range.level[0].sens - 1),
+				    ebitmap_length(&context->range.level[0].cat),
+				    sym_name(p, SYM_LEVELS, context->range.level[1].sens - 1),
+				    ebitmap_length(&context->range.level[1].cat));
+		return -EOVERFLOW;
+	}
+
+	if (unlikely(check_add_overflow(len, mls_len, &len) || len > CONTEXT_MAXLENGTH)) {
+		pr_warn_ratelimited("SELinux: %s:  security context string of length %zu too large [%s:%s:%s[:[%s:%d]-[%s:%d]]]\n",
+				    __func__,
+				    len,
+				    sym_name(p, SYM_USERS, context->user - 1),
+				    sym_name(p, SYM_ROLES, context->role - 1),
+				    sym_name(p, SYM_TYPES, context->type - 1),
+				    sym_name(p, SYM_LEVELS, context->range.level[0].sens - 1),
+				    ebitmap_length(&context->range.level[0].cat),
+				    sym_name(p, SYM_LEVELS, context->range.level[1].sens - 1),
+				    ebitmap_length(&context->range.level[1].cat));
+		return -EOVERFLOW;
+	}
+
+	*scontext_len = len;
 
 	if (!scontext)
 		return 0;
 
 	/* Allocate space for the context; caller must free this space. */
-	scontextp = kmalloc(*scontext_len, GFP_ATOMIC);
+	scontextp = kmalloc(len, GFP_ATOMIC);
 	if (!scontextp)
 		return -ENOMEM;
 	*scontext = scontextp;
-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* [PATCH v3 00/14] selinux: harden against malformed policies
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (12 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 14/14] selinux: harden MLS context string generation against overflows Christian Göttsche
@ 2025-05-11 17:30 ` Christian Göttsche
  2025-05-13 19:40 ` [PATCH v3 01/14] selinux: avoid nontransitive comparison Stephen Smalley
  14 siblings, 0 replies; 37+ messages in thread
From: Christian Göttsche @ 2025-05-11 17:30 UTC (permalink / raw)
  To: selinux
  Cc: Christian Göttsche, Paul Moore, Stephen Smalley,
	Ondrej Mosnacek, linux-kernel

From: Christian Göttsche <cgzones@googlemail.com>

With the SELinux namespace feature on the horizon it becomes important
to identify and reject malformed policies at load time.  Otherwise
memory corruptions can compromise the kernel or NULL-pointer dereferences
and BUG() encounters can bring systems down.  Currently this is not a
security relevant issue since loading a policy requires root privileges
and permission of the current loaded SELinux policy, making it one of the
most privileged operation.

Patch 13 limits the valid set of characters and the length for strings
defined by policies.  Currently there are no restrictions, so control
characters are accepted, e.g. Esc as part of a type name, and their
length can be arbitrary.  Human formatted security contexts however must
not be arbitrarily long, one example is they must fit in a page size for
selinuxfs interaction and network associations.
Thus the patch introduces the following restrictions:
  * Disallow control characters
  * Limit characters of identifiers to alphanumeric, underscore, dash,
    and dot
  * Limit identifiers in length to 128, expect types to 1024 and
    categories to 32, characters (excluding NUL-terminator)
    
v1: https://lore.kernel.org/selinux/20241115133619.114393-23-cgoettsche@seltendoof.de/

v2: https://lore.kernel.org/selinux/20241216164055.96267-23-cgoettsche@seltendoof.de/
  - also convert ebitmap_cmp() as suggested by Daniel
  - accept instead of rejecting unknown xperm specifiers to support
    backwards compatibility for future ones, suggested by Thiébaud
  - add wrappers for str_read() to minimize the usage of magic numbers
  - limit sensitivities to a length of 32, to match categories,
    suggested by Daniel
    
v3:
  - rename comparison macro to cmp_int()
  - move some portions from patch 2 to patch 3
  - minimize magic value usage
  - fix error branch reported by kernel test robot
  - merge ebitmap_highest_set_bit() patch into patch making use of it
  - introduce a central limits.h header
  - add limits for all kinds of string: filesystem names, filetrans
    keys, genfs paths, infiniband device names
  - add patch 14 for MLS stringification hardening

Christian Göttsche (14):
  selinux: avoid nontransitive comparison
  selinux: use u16 for security classes
  selinux: more strict policy parsing
  selinux: check length fields in policies
  selinux: validate constraints
  selinux: pre-validate conditional expressions
  selinux: check type attr map overflows
  selinux: reorder policydb_index()
  selinux: beef up isvalid checks
  selinux: validate symbols
  selinux: more strict bounds check
  selinux: check for simple types
  selinux: restrict policy strings
  selinux: harden MLS context string generation against overflows

 security/selinux/include/limits.h   |  90 +++++
 security/selinux/include/security.h |   1 +
 security/selinux/ss/avtab.c         |  49 ++-
 security/selinux/ss/avtab.h         |  13 +
 security/selinux/ss/conditional.c   | 152 ++++---
 security/selinux/ss/conditional.h   |   2 -
 security/selinux/ss/constraint.h    |   3 +-
 security/selinux/ss/ebitmap.c       |  27 ++
 security/selinux/ss/ebitmap.h       |   1 +
 security/selinux/ss/hashtab.h       |   4 +-
 security/selinux/ss/mls.c           |  84 ++--
 security/selinux/ss/mls.h           |   6 +-
 security/selinux/ss/policydb.c      | 597 +++++++++++++++++++++++-----
 security/selinux/ss/policydb.h      | 106 ++++-
 security/selinux/ss/services.c      | 111 ++++--
 security/selinux/ss/symtab.c        |   2 +-
 security/selinux/ss/symtab.h        |   2 +-
 17 files changed, 1008 insertions(+), 242 deletions(-)
 create mode 100644 security/selinux/include/limits.h

-- 
2.49.0


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 01/14] selinux: avoid nontransitive comparison
  2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
                   ` (13 preceding siblings ...)
  2025-05-11 17:30 ` [PATCH v3 00/14] selinux: harden against malformed policies Christian Göttsche
@ 2025-05-13 19:40 ` Stephen Smalley
  14 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-13 19:40 UTC (permalink / raw)
  To: cgzones; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Avoid using nontransitive comparison to prevent unexpected sorting
> results due to (well-defined) overflows.
> See https://www.qualys.com/2024/01/30/qsort.txt for a related issue in
> glibc's qsort(3).
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

I don't especially care either way about this patch, but it seems harmless.

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
> v3: rename macro to cmp_int()
> ---
>  security/selinux/ss/policydb.c | 18 ++++++++++--------
>  1 file changed, 10 insertions(+), 8 deletions(-)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 9ea971943713..dc701a7f8652 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -37,6 +37,8 @@
>  #include "mls.h"
>  #include "services.h"
>
> +#define cmp_int(a, b) (((a) > (b)) - ((a) < (b)))
> +
>  #ifdef CONFIG_SECURITY_SELINUX_DEBUG
>  /* clang-format off */
>  static const char *const symtab_name[SYM_NUM] = {
> @@ -424,11 +426,11 @@ static int filenametr_cmp(const void *k1, const void *k2)
>         const struct filename_trans_key *ft2 = k2;
>         int v;
>
> -       v = ft1->ttype - ft2->ttype;
> +       v = cmp_int(ft1->ttype, ft2->ttype);
>         if (v)
>                 return v;
>
> -       v = ft1->tclass - ft2->tclass;
> +       v = cmp_int(ft1->tclass, ft2->tclass);
>         if (v)
>                 return v;
>
> @@ -459,15 +461,15 @@ static int rangetr_cmp(const void *k1, const void *k2)
>         const struct range_trans *key1 = k1, *key2 = k2;
>         int v;
>
> -       v = key1->source_type - key2->source_type;
> +       v = cmp_int(key1->source_type, key2->source_type);
>         if (v)
>                 return v;
>
> -       v = key1->target_type - key2->target_type;
> +       v = cmp_int(key1->target_type, key2->target_type);
>         if (v)
>                 return v;
>
> -       v = key1->target_class - key2->target_class;
> +       v = cmp_int(key1->target_class, key2->target_class);
>
>         return v;
>  }
> @@ -496,15 +498,15 @@ static int role_trans_cmp(const void *k1, const void *k2)
>         const struct role_trans_key *key1 = k1, *key2 = k2;
>         int v;
>
> -       v = key1->role - key2->role;
> +       v = cmp_int(key1->role, key2->role);
>         if (v)
>                 return v;
>
> -       v = key1->type - key2->type;
> +       v = cmp_int(key1->type, key2->type);
>         if (v)
>                 return v;
>
> -       return key1->tclass - key2->tclass;
> +       return cmp_int(key1->tclass, key2->tclass);
>  }
>
>  static const struct hashtab_key_params roletr_key_params = {
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 02/14] selinux: use u16 for security classes
  2025-05-11 17:30 ` [PATCH v3 02/14] selinux: use u16 for security classes Christian Göttsche
@ 2025-05-13 19:44   ` Stephen Smalley
  2025-05-17 21:53     ` David Laight
  0 siblings, 1 reply; 37+ messages in thread
From: Stephen Smalley @ 2025-05-13 19:44 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Thiébaud Weksteen, Casey Schaufler, Canfeng Guo,
	Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Security class identifiers are limited to 2^16, thus use the appropriate
> type u16 consistently.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Historical footnote: originally security classes were _not_ limited to
2^16 but a later memory optimization of the avtab rendered them so.

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
> v3: only change type, move the validation (> U16_MAX) to the subsequent
>     patch
> ---
>  security/selinux/ss/policydb.c |  5 +++--
>  security/selinux/ss/policydb.h | 10 +++++-----
>  security/selinux/ss/services.c |  2 +-
>  3 files changed, 9 insertions(+), 8 deletions(-)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index dc701a7f8652..f490556ddb5c 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -927,7 +927,7 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s)
>         return 0;
>  }
>
> -int policydb_class_isvalid(struct policydb *p, unsigned int class)
> +int policydb_class_isvalid(struct policydb *p, u16 class)
>  {
>         if (!class || class > p->p_classes.nprim)
>                 return 0;
> @@ -2003,7 +2003,8 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>         struct filename_trans_key *ft = NULL;
>         struct filename_trans_datum **dst, *datum, *first = NULL;
>         char *name = NULL;
> -       u32 len, ttype, tclass, ndatum, i;
> +       u32 len, ttype, ndatum, i;
> +       u16 tclass;
>         __le32 buf[3];
>         int rc;
>
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index 25650224b6e7..0c423ad77fd9 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -48,7 +48,7 @@ struct common_datum {
>
>  /* Class attributes */
>  struct class_datum {
> -       u32 value; /* class value */
> +       u16 value; /* class value */
>         char *comkey; /* common name */
>         struct common_datum *comdatum; /* common datum */
>         struct symtab permissions; /* class-specific permission symbol table */
> @@ -82,7 +82,7 @@ struct role_datum {
>  struct role_trans_key {
>         u32 role; /* current role */
>         u32 type; /* program executable type, or new object type */
> -       u32 tclass; /* process class, or new object class */
> +       u16 tclass; /* process class, or new object class */
>  };
>
>  struct role_trans_datum {
> @@ -139,7 +139,7 @@ struct cat_datum {
>  struct range_trans {
>         u32 source_type;
>         u32 target_type;
> -       u32 target_class;
> +       u16 target_class;
>  };
>
>  /* Boolean data type */
> @@ -195,7 +195,7 @@ struct ocontext {
>                 } ibendport;
>         } u;
>         union {
> -               u32 sclass; /* security class for genfs */
> +               u16 sclass; /* security class for genfs */
>                 u32 behavior; /* labeling behavior for fs_use */
>         } v;
>         struct context context[2]; /* security context(s) */
> @@ -320,7 +320,7 @@ struct policy_file {
>  extern void policydb_destroy(struct policydb *p);
>  extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
>  extern int policydb_context_isvalid(struct policydb *p, struct context *c);
> -extern int policydb_class_isvalid(struct policydb *p, unsigned int class);
> +extern int policydb_class_isvalid(struct policydb *p, u16 class);
>  extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
>  extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
>  extern int policydb_read(struct policydb *p, struct policy_file *fp);
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 7becf3808818..a2dd42e750fe 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -3387,7 +3387,7 @@ static int get_classes_callback(void *k, void *d, void *args)
>  {
>         struct class_datum *datum = d;
>         char *name = k, **classes = args;
> -       u32 value = datum->value - 1;
> +       u16 value = datum->value - 1;
>
>         classes[value] = kstrdup(name, GFP_ATOMIC);
>         if (!classes[value])
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 03/14] selinux: more strict policy parsing
  2025-05-11 17:30 ` [PATCH v3 03/14] selinux: more strict policy parsing Christian Göttsche
@ 2025-05-14 14:15   ` Stephen Smalley
  2025-05-14 14:25     ` Stephen Smalley
  2026-05-06 23:43   ` [PATCH v3 3/14] " Paul Moore
  1 sibling, 1 reply; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 14:15 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Thiébaud Weksteen, Bram Bonné,
	Eric Suen, Casey Schaufler, Canfeng Guo, Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Be more strict during parsing of policies and reject invalid values.
>
> Add some error messages in the case of policy parse failures, to
> enhance debugging, either on a malformed policy or a too strict check.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
> v3:
>   - incorporate the overflow checks on security classes from the
>     previous patch, and permit U16_MAX as class ID
>   - minimize the usage of magic values, by using macros or trivial
>     helper functions
> v2:
>   accept unknown xperm specifiers to support backwards compatibility for
>   future ones, suggested by Thiébaud
> ---
>  security/selinux/include/security.h |   1 +
>  security/selinux/ss/avtab.c         |  35 ++++-
>  security/selinux/ss/avtab.h         |  13 ++
>  security/selinux/ss/conditional.c   |  18 +--
>  security/selinux/ss/constraint.h    |   1 +
>  security/selinux/ss/policydb.c      | 196 +++++++++++++++++++++++-----
>  security/selinux/ss/policydb.h      |  23 +++-
>  security/selinux/ss/services.c      |   6 +-
>  8 files changed, 233 insertions(+), 60 deletions(-)
>
> diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
> index 278c144c22d6..eca6693227ca 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -224,6 +224,7 @@ int security_read_policy(void **data, size_t *len);
>  int security_read_state_kernel(void **data, size_t *len);
>  int security_policycap_supported(unsigned int req_cap);
>
> +/* Maximum supported number of permissions per class */
>  #define SEL_VEC_MAX 32
>  struct av_decision {
>         u32 allowed;
> diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
> index c2c31521cace..33556922f15e 100644
> --- a/security/selinux/ss/avtab.c
> +++ b/security/selinux/ss/avtab.c
> @@ -349,7 +349,7 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>         struct avtab_extended_perms xperms;
>         __le32 buf32[ARRAY_SIZE(xperms.perms.p)];
>         int rc;
> -       unsigned int set, vers = pol->policyvers;
> +       unsigned int vers = pol->policyvers;
>
>         memset(&key, 0, sizeof(struct avtab_key));
>         memset(&datum, 0, sizeof(struct avtab_datum));
> @@ -360,9 +360,12 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>                         pr_err("SELinux: avtab: truncated entry\n");
>                         return rc;
>                 }
> +               /* Read five or more items: source type, target type,
> +                * target class, AV type, and at least one datum.
> +                */
>                 items2 = le32_to_cpu(buf32[0]);
> -               if (items2 > ARRAY_SIZE(buf32)) {
> -                       pr_err("SELinux: avtab: entry overflow\n");
> +               if (items2 < 5 || items2 > ARRAY_SIZE(buf32)) {
> +                       pr_err("SELinux: avtab: invalid item count\n");
>                         return -EINVAL;
>                 }
>                 rc = next_entry(buf32, fp, sizeof(u32) * items2);
> @@ -391,6 +394,13 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>                         return -EINVAL;
>                 }
>
> +               if (!policydb_type_isvalid(pol, key.source_type) ||
> +                   !policydb_type_isvalid(pol, key.target_type) ||
> +                   !policydb_class_isvalid(pol, key.target_class)) {
> +                       pr_err("SELinux: avtab: invalid type or class\n");
> +                       return -EINVAL;
> +               }
> +
>                 val = le32_to_cpu(buf32[items++]);
>                 enabled = (val & AVTAB_ENABLED_OLD) ? AVTAB_ENABLED : 0;
>
> @@ -409,6 +419,11 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>
>                 for (i = 0; i < ARRAY_SIZE(spec_order); i++) {
>                         if (val & spec_order[i]) {
> +                               if (items >= items2) {
> +                                       pr_err("SELinux: avtab: entry has too many items (%d/%d)\n",
> +                                              items + 1, items2);
> +                                       return -EINVAL;
> +                               }
>                                 key.specified = spec_order[i] | enabled;
>                                 datum.u.data = le32_to_cpu(buf32[items++]);
>                                 rc = insertf(a, &key, &datum, p);
> @@ -444,9 +459,13 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>                 return -EINVAL;
>         }
>
> -       set = hweight16(key.specified & (AVTAB_XPERMS | AVTAB_TYPE | AVTAB_AV));
> -       if (!set || set > 1) {
> -               pr_err("SELinux:  avtab:  more than one specifier\n");
> +       if (hweight16(key.specified & ~AVTAB_ENABLED) != 1) {
> +               pr_err("SELinux:  avtab:  not exactly one specifier\n");
> +               return -EINVAL;
> +       }
> +
> +       if (key.specified & ~AVTAB_SPECIFIER_MASK) {
> +               pr_err("SELinux:  avtab:  invalid specifier\n");
>                 return -EINVAL;
>         }
>
> @@ -471,6 +490,10 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>                         pr_err("SELinux: avtab: truncated entry\n");
>                         return rc;
>                 }
> +               if (!avtab_is_valid_xperm_specified(xperms.specified))
> +                       pr_warn_once_policyload(pol,
> +                                               "SELinux: avtab: unsupported xperm specifier %#x\n",
> +                                               xperms.specified);
>                 rc = next_entry(&xperms.driver, fp, sizeof(u8));
>                 if (rc) {
>                         pr_err("SELinux: avtab: truncated entry\n");
> diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
> index 850b3453f259..1de4cce288a7 100644
> --- a/security/selinux/ss/avtab.h
> +++ b/security/selinux/ss/avtab.h
> @@ -44,6 +44,7 @@ struct avtab_key {
>          AVTAB_XPERMS_DONTAUDIT)
>  #define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */
>  #define AVTAB_ENABLED    0x8000 /* reserved for used in cond_avtab */
> +#define AVTAB_SPECIFIER_MASK (AVTAB_AV | AVTAB_TYPE | AVTAB_XPERMS | AVTAB_ENABLED)
>         u16 specified; /* what field is specified */
>  };
>
> @@ -68,6 +69,18 @@ struct avtab_extended_perms {
>         struct extended_perms_data perms;
>  };
>
> +static inline bool avtab_is_valid_xperm_specified(u8 specified)
> +{
> +       switch (specified) {
> +       case AVTAB_XPERMS_IOCTLFUNCTION:
> +       case AVTAB_XPERMS_IOCTLDRIVER:
> +       case AVTAB_XPERMS_NLMSG:
> +               return true;
> +       default:
> +               return false;
> +       }
> +}
> +
>  struct avtab_datum {
>         union {
>                 u32 data; /* access vector or type value */
> diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
> index 1bebfcb9c6a1..db30462ed6a3 100644
> --- a/security/selinux/ss/conditional.c
> +++ b/security/selinux/ss/conditional.c
> @@ -199,19 +199,12 @@ int cond_index_bool(void *key, void *datum, void *datap)
>         return 0;
>  }
>
> -static int bool_isvalid(struct cond_bool_datum *b)
> -{
> -       if (!(b->state == 0 || b->state == 1))
> -               return 0;
> -       return 1;
> -}
> -
>  int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
>  {
>         char *key = NULL;
>         struct cond_bool_datum *booldatum;
>         __le32 buf[3];
> -       u32 len;
> +       u32 len, val;
>         int rc;
>
>         booldatum = kzalloc(sizeof(*booldatum), GFP_KERNEL);
> @@ -223,11 +216,12 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
>                 goto err;
>
>         booldatum->value = le32_to_cpu(buf[0]);
> -       booldatum->state = le32_to_cpu(buf[1]);
> +       val = le32_to_cpu(buf[1]);
>
>         rc = -EINVAL;
> -       if (!bool_isvalid(booldatum))
> +       if (!val_is_boolean(val))
>                 goto err;
> +       booldatum->state = (int)val;
>
>         len = le32_to_cpu(buf[2]);
>
> @@ -241,6 +235,7 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
>
>         return 0;
>  err:
> +       pr_err("SELinux: conditional: failed to read boolean\n");
>         cond_destroy_bool(key, booldatum, NULL);
>         return rc;
>  }
> @@ -362,7 +357,8 @@ static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr)
>                 return 0;
>         }
>
> -       if (expr->boolean > p->p_bools.nprim) {
> +       if (expr->expr_type == COND_BOOL &&
> +           (expr->boolean == 0 || expr->boolean > p->p_bools.nprim)) {
>                 pr_err("SELinux: conditional expressions uses unknown bool.\n");
>                 return 0;
>         }
> diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
> index 203033cfad67..1d75a8a044df 100644
> --- a/security/selinux/ss/constraint.h
> +++ b/security/selinux/ss/constraint.h
> @@ -50,6 +50,7 @@ struct constraint_expr {
>         u32 op; /* operator */
>
>         struct ebitmap names; /* names */
> +       /* internally unused, only forwarded via policydb_write() */
>         struct type_set *type_names;
>
>         struct constraint_expr *next; /* next expression */
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index f490556ddb5c..3e4a28a2928b 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -634,13 +634,11 @@ static int sens_index(void *key, void *datum, void *datap)
>         levdatum = datum;
>         p = datap;
>
> -       if (!levdatum->isalias) {
> -               if (!levdatum->level.sens ||
> -                   levdatum->level.sens > p->p_levels.nprim)
> -                       return -EINVAL;
> +       if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim)
> +               return -EINVAL;
>
> +       if (!levdatum->isalias)
>                 p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1] = key;
> -       }
>
>         return 0;
>  }
> @@ -653,12 +651,11 @@ static int cat_index(void *key, void *datum, void *datap)
>         catdatum = datum;
>         p = datap;
>
> -       if (!catdatum->isalias) {
> -               if (!catdatum->value || catdatum->value > p->p_cats.nprim)
> -                       return -EINVAL;
> +       if (!catdatum->value || catdatum->value > p->p_cats.nprim)
> +               return -EINVAL;
>
> +       if (!catdatum->isalias)
>                 p->sym_val_to_name[SYM_CATS][catdatum->value - 1] = key;
> -       }
>
>         return 0;
>  }
> @@ -1136,6 +1133,9 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f
>
>         len = le32_to_cpu(buf[0]);
>         perdatum->value = le32_to_cpu(buf[1]);
> +       rc = -EINVAL;
> +       if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
> +               goto bad;
>
>         rc = str_read(&key, GFP_KERNEL, fp, len);
>         if (rc)
> @@ -1170,6 +1170,9 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
>         len = le32_to_cpu(buf[0]);
>         comdatum->value = le32_to_cpu(buf[1]);
>         nel = le32_to_cpu(buf[3]);
> +       rc = -EINVAL;
> +       if (nel > SEL_VEC_MAX)
> +               goto bad;
>
>         rc = symtab_init(&comdatum->permissions, nel);
>         if (rc)
> @@ -1321,7 +1324,7 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
>         char *key = NULL;
>         struct class_datum *cladatum;
>         __le32 buf[6];
> -       u32 i, len, len2, ncons, nel;
> +       u32 i, len, len2, ncons, nel, val;
>         int rc;
>
>         cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL);
> @@ -1334,8 +1337,16 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
>
>         len = le32_to_cpu(buf[0]);
>         len2 = le32_to_cpu(buf[1]);
> -       cladatum->value = le32_to_cpu(buf[2]);
>         nel = le32_to_cpu(buf[4]);
> +       rc = -EINVAL;
> +       if (nel > SEL_VEC_MAX)
> +               goto bad;
> +
> +       val = le32_to_cpu(buf[2]);
> +       rc = -EINVAL;
> +       if (val > U16_MAX)
> +               goto bad;
> +       cladatum->value = val;
>
>         rc = symtab_init(&cladatum->permissions, nel);
>         if (rc)
> @@ -1391,16 +1402,59 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
>                 if (rc)
>                         goto bad;
>
> -               cladatum->default_user = le32_to_cpu(buf[0]);
> -               cladatum->default_role = le32_to_cpu(buf[1]);
> -               cladatum->default_range = le32_to_cpu(buf[2]);
> +               rc = -EINVAL;
> +               val = le32_to_cpu(buf[0]);
> +               switch (val) {
> +               case 0:
> +               case DEFAULT_SOURCE:
> +               case DEFAULT_TARGET:
> +                       cladatum->default_user = val;
> +                       break;
> +               default:
> +                       goto bad;
> +               }
> +               val = le32_to_cpu(buf[1]);
> +               switch (val) {
> +               case 0:
> +               case DEFAULT_SOURCE:
> +               case DEFAULT_TARGET:
> +                       cladatum->default_role = val;
> +                       break;
> +               default:
> +                       goto bad;
> +               }
> +               val = le32_to_cpu(buf[2]);
> +               switch (val) {
> +               case 0:
> +               case DEFAULT_SOURCE_LOW:
> +               case DEFAULT_SOURCE_HIGH:
> +               case DEFAULT_SOURCE_LOW_HIGH:
> +               case DEFAULT_TARGET_LOW:
> +               case DEFAULT_TARGET_HIGH:
> +               case DEFAULT_TARGET_LOW_HIGH:
> +               case DEFAULT_GLBLUB:
> +                       cladatum->default_range = val;
> +                       break;
> +               default:
> +                       goto bad;
> +               }
>         }
>
>         if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) {
>                 rc = next_entry(buf, fp, sizeof(u32) * 1);
>                 if (rc)
>                         goto bad;
> -               cladatum->default_type = le32_to_cpu(buf[0]);
> +               rc = -EINVAL;
> +               val = le32_to_cpu(buf[0]);
> +               switch (val) {
> +               case 0:
> +               case DEFAULT_TARGET:
> +               case DEFAULT_SOURCE:
> +                       cladatum->default_type = val;
> +                       break;
> +               default:
> +                       goto bad;
> +               }
>         }
>
>         rc = symtab_insert(s, key, cladatum);
> @@ -1410,6 +1464,8 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
>         return 0;
>  bad:
>         cls_destroy(key, cladatum, NULL);
> +       if (rc)
> +               pr_err("SELinux:  invalid class\n");
>         return rc;
>  }
>
> @@ -1601,7 +1657,7 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
>         struct level_datum *levdatum;
>         int rc;
>         __le32 buf[2];
> -       u32 len;
> +       u32 len, val;
>
>         levdatum = kzalloc(sizeof(*levdatum), GFP_KERNEL);
>         if (!levdatum)
> @@ -1612,7 +1668,11 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
>                 goto bad;
>
>         len = le32_to_cpu(buf[0]);
> -       levdatum->isalias = le32_to_cpu(buf[1]);
> +       val = le32_to_cpu(buf[1]);
> +       rc = -EINVAL;
> +       if (!val_is_boolean(val))
> +               goto bad;
> +       levdatum->isalias = val;
>
>         rc = str_read(&key, GFP_KERNEL, fp, len);
>         if (rc)
> @@ -1628,6 +1688,8 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
>         return 0;
>  bad:
>         sens_destroy(key, levdatum, NULL);
> +       if (rc)
> +               pr_err("SELinux:  invalid sensitivity\n");
>         return rc;
>  }
>
> @@ -1637,7 +1699,7 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
>         struct cat_datum *catdatum;
>         int rc;
>         __le32 buf[3];
> -       u32 len;
> +       u32 len, val;
>
>         catdatum = kzalloc(sizeof(*catdatum), GFP_KERNEL);
>         if (!catdatum)
> @@ -1649,7 +1711,11 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
>
>         len = le32_to_cpu(buf[0]);
>         catdatum->value = le32_to_cpu(buf[1]);
> -       catdatum->isalias = le32_to_cpu(buf[2]);
> +       val = le32_to_cpu(buf[2]);
> +       rc = -EINVAL;
> +       if (!val_is_boolean(val))
> +               goto bad;
> +       catdatum->isalias = val;
>
>         rc = str_read(&key, GFP_KERNEL, fp, len);
>         if (rc)
> @@ -1661,6 +1727,8 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
>         return 0;
>  bad:
>         cat_destroy(key, catdatum, NULL);
> +       if (rc)
> +               pr_err("SELinux:  invalid category\n");
>         return rc;
>  }
>
> @@ -1842,7 +1910,7 @@ static int range_read(struct policydb *p, struct policy_file *fp)
>         struct mls_range *r = NULL;
>         int rc;
>         __le32 buf[2];
> -       u32 i, nel;
> +       u32 i, nel, val;
>
>         if (p->policyvers < POLICYDB_VERSION_MLS)
>                 return 0;
> @@ -1873,7 +1941,11 @@ static int range_read(struct policydb *p, struct policy_file *fp)
>                         rc = next_entry(buf, fp, sizeof(u32));
>                         if (rc)
>                                 goto out;
> -                       rt->target_class = le32_to_cpu(buf[0]);
> +                       rc = -EINVAL;
> +                       val = le32_to_cpu(buf[0]);
> +                       if (val > U16_MAX)
> +                               goto out;
> +                       rt->target_class = val;
>                 } else
>                         rt->target_class = p->process_class;
>
> @@ -1910,6 +1982,8 @@ static int range_read(struct policydb *p, struct policy_file *fp)
>  out:
>         kfree(rt);
>         kfree(r);
> +       if (rc)
> +               pr_err("SELinux:  invalid range\n");
>         return rc;
>  }
>
> @@ -1918,7 +1992,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
>         struct filename_trans_key key, *ft = NULL;
>         struct filename_trans_datum *last, *datum = NULL;
>         char *name = NULL;
> -       u32 len, stype, otype;
> +       u32 len, stype, otype, val;
>         __le32 buf[4];
>         int rc;
>
> @@ -1937,9 +2011,17 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
>         if (rc)
>                 goto out;
>
> +       rc = -EINVAL;
>         stype = le32_to_cpu(buf[0]);
> +       if (!policydb_type_isvalid(p, stype))
> +               goto out;
>         key.ttype = le32_to_cpu(buf[1]);
> -       key.tclass = le32_to_cpu(buf[2]);
> +       if (!policydb_type_isvalid(p, key.ttype))
> +               goto out;
> +       val = le32_to_cpu(buf[2]);
> +       if (val > U16_MAX || !policydb_class_isvalid(p, val))
> +               goto out;
> +       key.tclass = val;
>         key.name = name;
>
>         otype = le32_to_cpu(buf[3]);
> @@ -1995,6 +2077,9 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
>         kfree(ft);
>         kfree(name);
>         kfree(datum);
> +
> +       if (rc)
> +               pr_err("SELinux:  invalid compat filename transition\n");
>         return rc;
>  }
>
> @@ -2003,7 +2088,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>         struct filename_trans_key *ft = NULL;
>         struct filename_trans_datum **dst, *datum, *first = NULL;
>         char *name = NULL;
> -       u32 len, ttype, ndatum, i;
> +       u32 len, ttype, ndatum, i, val;
>         u16 tclass;
>         __le32 buf[3];
>         int rc;
> @@ -2023,8 +2108,15 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>         if (rc)
>                 goto out;
>
> +       rc = -EINVAL;
>         ttype = le32_to_cpu(buf[0]);
> -       tclass = le32_to_cpu(buf[1]);
> +       if (!policydb_type_isvalid(p, ttype))
> +               goto out;
> +       val = le32_to_cpu(buf[1]);
> +       rc = -EINVAL;
> +       if (val > U16_MAX || !policydb_class_isvalid(p, val))
> +               goto out;
> +       tclass = val;
>
>         ndatum = le32_to_cpu(buf[2]);
>         if (ndatum == 0) {
> @@ -2054,6 +2146,10 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>
>                 datum->otype = le32_to_cpu(buf[0]);
>
> +               rc = -EINVAL;
> +               if (!policydb_type_isvalid(p, datum->otype))
> +                       goto out;
> +
>                 dst = &datum->next;
>         }
>
> @@ -2085,6 +2181,9 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>                 ebitmap_destroy(&datum->stypes);
>                 kfree(datum);
>         }
> +
> +       if (rc)
> +               pr_err("SELinux:  invalid filename transition\n");
>         return rc;
>  }
>
> @@ -2132,7 +2231,7 @@ static int filename_trans_read(struct policydb *p, struct policy_file *fp)
>  static int genfs_read(struct policydb *p, struct policy_file *fp)
>  {
>         int rc;
> -       u32 i, j, nel, nel2, len, len2;
> +       u32 i, j, nel, nel2, len, len2, val;
>         __le32 buf[1];
>         struct ocontext *l, *c;
>         struct ocontext *newc = NULL;
> @@ -2202,7 +2301,11 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
>                         if (rc)
>                                 goto out;
>
> -                       newc->v.sclass = le32_to_cpu(buf[0]);
> +                       rc = -EINVAL;
> +                       val = le32_to_cpu(buf[0]);
> +                       if (val > U16_MAX || (val != 0 && !policydb_class_isvalid(p, val)))
> +                               goto out;
> +                       newc->v.sclass = val;
>                         rc = context_read_and_validate(&newc->context[0], p,
>                                                        fp);
>                         if (rc)
> @@ -2239,6 +2342,9 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
>         }
>         ocontext_destroy(newc, OCON_FSUSE);
>
> +       if (rc)
> +               pr_err("SELinux:  invalid genfs\n");
> +
>         return rc;
>  }
>
> @@ -2247,7 +2353,7 @@ static int ocontext_read(struct policydb *p,
>  {
>         int rc;
>         unsigned int i;
> -       u32 j, nel, len;
> +       u32 j, nel, len, val;
>         __be64 prefixbuf[1];
>         __le32 buf[3];
>         struct ocontext *l, *c;
> @@ -2311,11 +2417,25 @@ static int ocontext_read(struct policydb *p,
>                                 rc = next_entry(buf, fp, sizeof(u32) * 3);
>                                 if (rc)
>                                         goto out;
> -                               c->u.port.protocol = le32_to_cpu(buf[0]);
> -                               c->u.port.low_port = le32_to_cpu(buf[1]);
> -                               c->u.port.high_port = le32_to_cpu(buf[2]);
> -                               rc = context_read_and_validate(&c->context[0],
> -                                                              p, fp);
> +
> +                               rc = -EINVAL;
> +                               val = le32_to_cpu(buf[0]);
> +                               if (val > U8_MAX)
> +                                       goto out;
> +                               c->u.port.protocol = val;
> +                               val = le32_to_cpu(buf[1]);
> +                               if (val > U16_MAX)
> +                                       goto out;
> +                               c->u.port.low_port = val;
> +                               val = le32_to_cpu(buf[2]);
> +                               if (val > U16_MAX)
> +                                       goto out;
> +                               c->u.port.high_port = val;
> +                               if (c->u.port.low_port == 0 ||
> +                                   c->u.port.low_port > c->u.port.high_port)
> +                                       goto out;
> +
> +                               rc = context_read_and_validate(&c->context[0], p, fp);
>                                 if (rc)
>                                         goto out;
>                                 break;
> @@ -2433,6 +2553,8 @@ static int ocontext_read(struct policydb *p,
>         }
>         rc = 0;
>  out:
> +       if (rc)
> +               pr_err("SELinux:  invalid ocon\n");
>         return rc;
>  }
>
> @@ -2447,7 +2569,7 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>         struct role_trans_datum *rtd = NULL;
>         int rc;
>         __le32 buf[4];
> -       u32 i, j, len, nprim, nel, perm;
> +       u32 i, j, len, nprim, nel, perm, val;
>
>         char *policydb_str;
>         const struct policydb_compat_info *info;
> @@ -2633,7 +2755,11 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>                         rc = next_entry(buf, fp, sizeof(u32));
>                         if (rc)
>                                 goto bad;
> -                       rtk->tclass = le32_to_cpu(buf[0]);
> +                       rc = -EINVAL;
> +                       val = le32_to_cpu(buf[0]);
> +                       if (val > U16_MAX)
> +                               goto bad;
> +                       rtk->tclass = val;
>                 } else
>                         rtk->tclass = p->process_class;
>
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index 0c423ad77fd9..9b3cc393a979 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -74,7 +74,7 @@ struct class_datum {
>  /* Role attributes */
>  struct role_datum {
>         u32 value; /* internal role value */
> -       u32 bounds; /* boundary of role */
> +       u32 bounds; /* boundary of role, 0 for none */
>         struct ebitmap dominates; /* set of roles dominated by this role */
>         struct ebitmap types; /* set of authorized types for role */
>  };
> @@ -110,7 +110,8 @@ struct role_allow {
>  /* Type attributes */
>  struct type_datum {
>         u32 value; /* internal type value */
> -       u32 bounds; /* boundary of type */
> +       u32 bounds; /* boundary of type, 0 for none */
> +       /* internally unused, only forwarded via policydb_write() */
>         unsigned char primary; /* primary name? */
>         unsigned char attribute; /* attribute ?*/
>  };
> @@ -118,7 +119,7 @@ struct type_datum {
>  /* User attributes */
>  struct user_datum {
>         u32 value; /* internal user value */
> -       u32 bounds; /* bounds of user */
> +       u32 bounds; /* bounds of user, 0 for none */
>         struct ebitmap roles; /* set of authorized roles for user */
>         struct mls_range range; /* MLS range (min - max) for user */
>         struct mls_level dfltlevel; /* default login MLS level for user */
> @@ -195,7 +196,7 @@ struct ocontext {
>                 } ibendport;
>         } u;
>         union {
> -               u16 sclass; /* security class for genfs */
> +               u16 sclass; /* security class for genfs (can be 0 for wildcard) */
>                 u32 behavior; /* labeling behavior for fs_use */
>         } v;
>         struct context context[2]; /* security context(s) */
> @@ -386,9 +387,23 @@ static inline char *sym_name(struct policydb *p, unsigned int sym_num,
>         return p->sym_val_to_name[sym_num][element_nr];
>  }
>
> +static inline bool val_is_boolean(u32 value)
> +{
> +       return value == 0 || value == 1;
> +}
> +
>  extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
>
>  extern u16 string_to_security_class(struct policydb *p, const char *name);
>  extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
>
> +#define pr_warn_once_policyload(policy, fmt, ...)                        \
> +       do {                                                             \
> +               static const void *prev_policy__;                        \
> +               if (prev_policy__ != policy) {                           \
> +                       printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__); \
> +                       prev_policy__ = policy;                          \
> +               }                                                        \
> +       } while (0)
> +
>  #endif /* _SS_POLICYDB_H_ */
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index a2dd42e750fe..0f67a030b49b 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -446,8 +446,6 @@ static int dump_masked_av_helper(void *k, void *d, void *args)
>         struct perm_datum *pdatum = d;
>         char **permission_names = args;
>
> -       BUG_ON(pdatum->value < 1 || pdatum->value > 32);
> -
>         permission_names[pdatum->value - 1] = (char *)k;
>
>         return 0;
> @@ -466,7 +464,7 @@ static void security_dump_masked_av(struct policydb *policydb,
>         char *tclass_name;
>         char *scontext_name = NULL;
>         char *tcontext_name = NULL;
> -       char *permission_names[32];
> +       char *permission_names[SEL_VEC_MAX];
>         int index;
>         u32 length;
>         bool need_comma = false;
> @@ -507,7 +505,7 @@ static void security_dump_masked_av(struct policydb *policydb,
>                          "scontext=%s tcontext=%s tclass=%s perms=",
>                          reason, scontext_name, tcontext_name, tclass_name);
>
> -       for (index = 0; index < 32; index++) {
> +       for (index = 0; index < SEL_VEC_MAX; index++) {
>                 u32 mask = (1 << index);
>
>                 if ((mask & permissions) == 0)
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 03/14] selinux: more strict policy parsing
  2025-05-14 14:15   ` Stephen Smalley
@ 2025-05-14 14:25     ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 14:25 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Thiébaud Weksteen, Bram Bonné,
	Eric Suen, Casey Schaufler, Canfeng Guo, Takaya Saeki

On Wed, May 14, 2025 at 10:15 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
> <cgoettsche@seltendoof.de> wrote:
> >
> > From: Christian Göttsche <cgzones@googlemail.com>
> >
> > Be more strict during parsing of policies and reject invalid values.
> >
> > Add some error messages in the case of policy parse failures, to
> > enhance debugging, either on a malformed policy or a too strict check.
> >
> > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
>
> > ---
> > v3:
> >   - incorporate the overflow checks on security classes from the
> >     previous patch, and permit U16_MAX as class ID
> >   - minimize the usage of magic values, by using macros or trivial
> >     helper functions
> > v2:
> >   accept unknown xperm specifiers to support backwards compatibility for
> >   future ones, suggested by Thiébaud
> > ---
> >  security/selinux/include/security.h |   1 +
> >  security/selinux/ss/avtab.c         |  35 ++++-
> >  security/selinux/ss/avtab.h         |  13 ++
> >  security/selinux/ss/conditional.c   |  18 +--
> >  security/selinux/ss/constraint.h    |   1 +
> >  security/selinux/ss/policydb.c      | 196 +++++++++++++++++++++++-----
> >  security/selinux/ss/policydb.h      |  23 +++-
> >  security/selinux/ss/services.c      |   6 +-
> >  8 files changed, 233 insertions(+), 60 deletions(-)
> >
>
> > diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> > index 0c423ad77fd9..9b3cc393a979 100644
> > --- a/security/selinux/ss/policydb.h
> > +++ b/security/selinux/ss/policydb.h
> > @@ -386,9 +387,23 @@ static inline char *sym_name(struct policydb *p, unsigned int sym_num,
> >         return p->sym_val_to_name[sym_num][element_nr];
> >  }
> >
> > +static inline bool val_is_boolean(u32 value)
> > +{
> > +       return value == 0 || value == 1;
> > +}
> > +
> >  extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
> >
> >  extern u16 string_to_security_class(struct policydb *p, const char *name);
> >  extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
> >
> > +#define pr_warn_once_policyload(policy, fmt, ...)                        \
> > +       do {                                                             \
> > +               static const void *prev_policy__;                        \
> > +               if (prev_policy__ != policy) {                           \
> > +                       printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__); \
> > +                       prev_policy__ = policy;                          \
> > +               }                                                        \
> > +       } while (0)
> > +
> >  #endif /* _SS_POLICYDB_H_ */

checkpatch.pl warning:
WARNING: Prefer [subsystem eg: netdev]_warn([subsystem]dev, ... then
dev_warn(dev, ... then pr_warn(...  to printk(KERN_WARNING ...
#718: FILE: security/selinux/ss/policydb.h:404:
+ printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__); \

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 04/14] selinux: check length fields in policies
  2025-05-11 17:30 ` [PATCH v3 04/14] selinux: check length fields in policies Christian Göttsche
@ 2025-05-14 17:26   ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 17:26 UTC (permalink / raw)
  To: cgzones; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel, Eric Suen

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> In multiple places the binary policy announces how many items of some
> kind are to be expected next.  Before reading them the kernel already
> allocates enough memory for that announced size.  Validate that the
> remaining input size can actually fit the announced items, to avoid OOM
> issues on malformed binary policies.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
> v3:
>   - fix error branch by returning directly instead of jumping to goto
>     label, see
>       https://lore.kernel.org/all/202412241405.LK8YTZqp-lkp@intel.com/
>   - rename to size_check()
>   - add comments for magic values
> ---
>  security/selinux/ss/avtab.c       |  5 +++++
>  security/selinux/ss/conditional.c | 17 ++++++++++++++++
>  security/selinux/ss/policydb.c    | 32 +++++++++++++++++++++++++++++++
>  security/selinux/ss/policydb.h    | 13 +++++++++++++
>  4 files changed, 67 insertions(+)
>
> diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
> index 33556922f15e..50df8b69de2b 100644
> --- a/security/selinux/ss/avtab.c
> +++ b/security/selinux/ss/avtab.c
> @@ -548,6 +548,11 @@ int avtab_read(struct avtab *a, struct policy_file *fp, struct policydb *pol)
>                 goto bad;
>         }
>
> +       /* avtab_read_item() reads at least 96 bytes for any valid entry */
> +       rc = size_check(3 * sizeof(u32), nel, fp);
> +       if (rc)
> +               goto bad;
> +
>         rc = avtab_alloc(a, nel);
>         if (rc)
>                 goto bad;
> diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
> index db30462ed6a3..92ed4f23a217 100644
> --- a/security/selinux/ss/conditional.c
> +++ b/security/selinux/ss/conditional.c
> @@ -12,6 +12,7 @@
>
>  #include "security.h"
>  #include "conditional.h"
> +#include "policydb.h"
>  #include "services.h"
>
>  /*
> @@ -329,6 +330,11 @@ static int cond_read_av_list(struct policydb *p, struct policy_file *fp,
>         if (len == 0)
>                 return 0;
>
> +       /* avtab_read_item() reads at least 96 bytes for any valid entry */
> +       rc = size_check(3 * sizeof(u32), len, fp);
> +       if (rc)
> +               return rc;
> +
>         list->nodes = kcalloc(len, sizeof(*list->nodes), GFP_KERNEL);
>         if (!list->nodes)
>                 return -ENOMEM;
> @@ -379,6 +385,12 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol
>
>         /* expr */
>         len = le32_to_cpu(buf[1]);
> +
> +       /* we will read 64 bytes per node */
> +       rc = size_check(2 * sizeof(u32), len, fp);
> +       if (rc)
> +               return rc;
> +
>         node->expr.nodes = kcalloc(len, sizeof(*node->expr.nodes), GFP_KERNEL);
>         if (!node->expr.nodes)
>                 return -ENOMEM;
> @@ -417,6 +429,11 @@ int cond_read_list(struct policydb *p, struct policy_file *fp)
>
>         len = le32_to_cpu(buf[0]);
>
> +       /* cond_read_node() reads at least 128 bytes for any valid node */
> +       rc = size_check(4 * sizeof(u32), len, fp);
> +       if (rc)
> +               return rc;
> +
>         p->cond_list = kcalloc(len, sizeof(*p->cond_list), GFP_KERNEL);
>         if (!p->cond_list)
>                 return -ENOMEM;
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 3e4a28a2928b..46c010afd44f 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -1100,6 +1100,9 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
>         if ((len == 0) || (len == (u32)-1))
>                 return -EINVAL;
>
> +       if (size_check(sizeof(char), len, fp))
> +               return -EINVAL;
> +
>         str = kmalloc(len + 1, flags | __GFP_NOWARN);
>         if (!str)
>                 return -ENOMEM;
> @@ -1174,6 +1177,11 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
>         if (nel > SEL_VEC_MAX)
>                 goto bad;
>
> +       /* perm_read() reads at least 64 bytes for any valid permission */
> +       rc = size_check(2 * sizeof(u32), nel, fp);
> +       if (rc)
> +               goto bad;
> +
>         rc = symtab_init(&comdatum->permissions, nel);
>         if (rc)
>                 goto bad;
> @@ -1348,6 +1356,11 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
>                 goto bad;
>         cladatum->value = val;
>
> +       /* perm_read() reads at least 64 bytes for any valid permission */
> +       rc = size_check(2 * sizeof(u32), nel, fp);
> +       if (rc)
> +               goto bad;
> +
>         rc = symtab_init(&cladatum->permissions, nel);
>         if (rc)
>                 goto bad;
> @@ -1921,6 +1934,13 @@ static int range_read(struct policydb *p, struct policy_file *fp)
>
>         nel = le32_to_cpu(buf[0]);
>
> +       /* we read at least 64 bytes and mls_read_range_helper() 32 bytes
> +        * for any valid range-transition
> +        */
> +       rc = size_check(3 * sizeof(u32), nel, fp);
> +       if (rc)
> +               return rc;
> +
>         rc = hashtab_init(&p->range_tr, nel);
>         if (rc)
>                 return rc;
> @@ -2689,6 +2709,13 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>                 nprim = le32_to_cpu(buf[0]);
>                 nel = le32_to_cpu(buf[1]);
>
> +               /* every read_f() implementation reads at least 128 bytes
> +                * for any valid entry
> +                */
> +               rc = size_check(4 * sizeof(u32), nel, fp);
> +               if (rc)
> +                       goto out;
> +
>                 rc = symtab_init(&p->symtab[i], nel);
>                 if (rc)
>                         goto out;
> @@ -2730,6 +2757,11 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>                 goto bad;
>         nel = le32_to_cpu(buf[0]);
>
> +       /* we read at least 96 bytes for any valid role-transition */
> +       rc = size_check(3 * sizeof(u32), nel, fp);
> +       if (rc)
> +               goto bad;
> +
>         rc = hashtab_init(&p->role_tr, nel);
>         if (rc)
>                 goto bad;
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index 9b3cc393a979..bb96a6cb6101 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -353,6 +353,19 @@ struct policy_data {
>         struct policy_file *fp;
>  };
>
> +static inline int size_check(size_t bytes, size_t num, const struct policy_file *fp)
> +{
> +       size_t len;
> +
> +       if (unlikely(check_mul_overflow(bytes, num, &len)))
> +               return -EINVAL;
> +
> +       if (unlikely(len > fp->len))
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
>  static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes)
>  {
>         if (bytes > fp->len)
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 05/14] selinux: validate constraints
  2025-05-11 17:30 ` [PATCH v3 05/14] selinux: validate constraints Christian Göttsche
@ 2025-05-14 17:51   ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 17:51 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Thiébaud Weksteen, Casey Schaufler, Canfeng Guo,
	Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate constraint expressions during reading the policy.
> Avoid the usage of BUG() on constraint evaluation, to mitigate malformed
> policies halting the system.
>
> Closes: https://github.com/SELinuxProject/selinux-testsuite/issues/76
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> ---
>  security/selinux/ss/policydb.c | 61 ++++++++++++++++++++++++++++++++--
>  security/selinux/ss/services.c | 55 +++++++++++++++---------------
>  2 files changed, 88 insertions(+), 28 deletions(-)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 46c010afd44f..a8397ed66109 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -1288,15 +1290,70 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep,
>                                 depth--;
>                                 break;
>                         case CEXPR_ATTR:
> -                               if (depth == (CEXPR_MAXDEPTH - 1))
> +                               if (depth >= (CEXPR_MAXDEPTH - 1))

How would the > case be possible? If it isn't something that can be
done by a malformed policy, drop it. Same applies later too.

>                                         return -EINVAL;
>                                 depth++;
>                                 break;
> +
> +                               switch (e->op) {

Something went wrong here, probably a merge failure. Statement after a break?

> +                               case CEXPR_EQ:
> +                               case CEXPR_NEQ:
> +                                       break;
> +                               case CEXPR_DOM:
> +                               case CEXPR_DOMBY:
> +                               case CEXPR_INCOMP:
> +                                       if ((e->attr & CEXPR_USER) || (e->attr & CEXPR_TYPE))
> +                                               return -EINVAL;
> +                                       break;
> +                               default:
> +                                       return -EINVAL;
> +                               }
> +
> +                               switch (e->attr) {
> +                               case CEXPR_USER:
> +                               case CEXPR_ROLE:
> +                               case CEXPR_TYPE:
> +                               case CEXPR_L1L2:
> +                               case CEXPR_L1H2:
> +                               case CEXPR_H1L2:
> +                               case CEXPR_H1H2:
> +                               case CEXPR_L1H1:
> +                               case CEXPR_L2H2:
> +                                       break;
> +                               default:
> +                                       return -EINVAL;
> +                               }
> +
> +                               break;
>                         case CEXPR_NAMES:
>                                 if (!allowxtarget && (e->attr & CEXPR_XTARGET))
>                                         return -EINVAL;
> -                               if (depth == (CEXPR_MAXDEPTH - 1))
> +                               if (depth >= (CEXPR_MAXDEPTH - 1))
> +                                       return -EINVAL;
> +
> +                               switch (e->op) {
> +                               case CEXPR_EQ:
> +                               case CEXPR_NEQ:
> +                                       break;
> +                               default:
> +                                       return -EINVAL;
> +                               }
> +
> +                               switch (e->attr) {
> +                               case CEXPR_USER:
> +                               case CEXPR_USER | CEXPR_TARGET:
> +                               case CEXPR_USER | CEXPR_XTARGET:
> +                               case CEXPR_ROLE:
> +                               case CEXPR_ROLE | CEXPR_TARGET:
> +                               case CEXPR_ROLE | CEXPR_XTARGET:
> +                               case CEXPR_TYPE:
> +                               case CEXPR_TYPE | CEXPR_TARGET:
> +                               case CEXPR_TYPE | CEXPR_XTARGET:
> +                                       break;
> +                               default:
>                                         return -EINVAL;
> +                               }
> +
>                                 depth++;
>                                 rc = ebitmap_read(&e->names, fp);
>                                 if (rc)
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 0f67a030b49b..3fb971fe4fd9 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -279,22 +279,25 @@ static int constraint_expr_eval(struct policydb *policydb,
>         for (e = cexpr; e; e = e->next) {
>                 switch (e->expr_type) {
>                 case CEXPR_NOT:
> -                       BUG_ON(sp < 0);
> +                       if (unlikely(sp < 0))
> +                               goto invalid;

If you have ensured that this is not possible at policy load time,
then we don't need to recheck it at runtime. Same applies later.

>                         s[sp] = !s[sp];
>                         break;
>                 case CEXPR_AND:
> -                       BUG_ON(sp < 1);
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] &= s[sp + 1];
>                         break;
>                 case CEXPR_OR:
> -                       BUG_ON(sp < 1);
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] |= s[sp + 1];
>                         break;
>                 case CEXPR_ATTR:
> -                       if (sp == (CEXPR_MAXDEPTH - 1))
> -                               return 0;
> +                       if (unlikely(sp >= (CEXPR_MAXDEPTH - 1)))
> +                               goto invalid;
>                         switch (e->attr) {
>                         case CEXPR_USER:
>                                 val1 = scontext->user;
> @@ -370,13 +373,11 @@ static int constraint_expr_eval(struct policydb *policydb,
>                                         s[++sp] = mls_level_incomp(l2, l1);
>                                         continue;
>                                 default:
> -                                       BUG();
> -                                       return 0;
> +                                       goto invalid;
>                                 }
>                                 break;
>                         default:
> -                               BUG();
> -                               return 0;
> +                               goto invalid;
>                         }
>
>                         switch (e->op) {
> @@ -387,22 +388,19 @@ static int constraint_expr_eval(struct policydb *policydb,
>                                 s[++sp] = (val1 != val2);
>                                 break;
>                         default:
> -                               BUG();
> -                               return 0;
> +                               goto invalid;
>                         }
>                         break;
>                 case CEXPR_NAMES:
> -                       if (sp == (CEXPR_MAXDEPTH-1))
> -                               return 0;
> +                       if (unlikely(sp >= (CEXPR_MAXDEPTH-1)))
> +                               goto invalid;
>                         c = scontext;
>                         if (e->attr & CEXPR_TARGET)
>                                 c = tcontext;
>                         else if (e->attr & CEXPR_XTARGET) {
>                                 c = xcontext;
> -                               if (!c) {
> -                                       BUG();
> -                                       return 0;
> -                               }
> +                               if (unlikely(!c))
> +                                       goto invalid;
>                         }
>                         if (e->attr & CEXPR_USER)
>                                 val1 = c->user;
> @@ -410,10 +408,8 @@ static int constraint_expr_eval(struct policydb *policydb,
>                                 val1 = c->role;
>                         else if (e->attr & CEXPR_TYPE)
>                                 val1 = c->type;
> -                       else {
> -                               BUG();
> -                               return 0;
> -                       }
> +                       else
> +                               goto invalid;
>
>                         switch (e->op) {
>                         case CEXPR_EQ:
> @@ -423,18 +419,25 @@ static int constraint_expr_eval(struct policydb *policydb,
>                                 s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1);
>                                 break;
>                         default:
> -                               BUG();
> -                               return 0;
> +                               goto invalid;
>                         }
>                         break;
>                 default:
> -                       BUG();
> -                       return 0;
> +                       goto invalid;
>                 }
>         }
>
> -       BUG_ON(sp != 0);
> +       if (unlikely(sp != 0))
> +               goto invalid;
> +
>         return s[0];
> +
> +invalid:
> +       /* Should *never* be reached, cause malformed constraints should
> +        * have been filtered by read_cons_helper().
> +        */
> +       WARN_ONCE(true, "SELinux: invalid constraint passed validation\n");
> +       return 0;
>  }
>
>  /*
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 06/14] selinux: pre-validate conditional expressions
  2025-05-11 17:30 ` [PATCH v3 06/14] selinux: pre-validate conditional expressions Christian Göttsche
@ 2025-05-14 17:55   ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 17:55 UTC (permalink / raw)
  To: cgzones; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate conditional expressions while reading the policy, to avoid
> unexpected access decisions on malformed policies.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> ---
>  security/selinux/ss/conditional.c | 116 ++++++++++++++++++++----------
>  security/selinux/ss/policydb.c    |   7 ++
>  security/selinux/ss/policydb.h    |   1 +
>  3 files changed, 88 insertions(+), 36 deletions(-)
>
> diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
> index 92ed4f23a217..ce0281cce739 100644
> --- a/security/selinux/ss/conditional.c
> +++ b/security/selinux/ss/conditional.c
> @@ -21,65 +21,119 @@
>   * or undefined (-1). Undefined occurs when the expression
>   * exceeds the stack depth of COND_EXPR_MAXDEPTH.
>   */
> -static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr)
> +static int cond_evaluate_expr(const struct policydb *p, const struct cond_expr *expr)
>  {
>         u32 i;
>         int s[COND_EXPR_MAXDEPTH];
>         int sp = -1;
>
> -       if (expr->len == 0)
> -               return -1;
> +       if (unlikely(expr->len == 0))
> +               goto invalid;

If this is not possible at runtime due to load-time checking, drop it
please. Same applies later. Split the evaluator checking between
load-time and runtime so we don't do it twice.

>
>         for (i = 0; i < expr->len; i++) {
> -               struct cond_expr_node *node = &expr->nodes[i];
> +               const struct cond_expr_node *node = &expr->nodes[i];
>
>                 switch (node->expr_type) {
>                 case COND_BOOL:
> -                       if (sp == (COND_EXPR_MAXDEPTH - 1))
> -                               return -1;
> +                       if (unlikely(sp >= (COND_EXPR_MAXDEPTH - 1)))
> +                               goto invalid;
>                         sp++;
>                         s[sp] = p->bool_val_to_struct[node->boolean - 1]->state;
>                         break;
>                 case COND_NOT:
> -                       if (sp < 0)
> -                               return -1;
> +                       if (unlikely(sp < 0))
> +                               goto invalid;
>                         s[sp] = !s[sp];
>                         break;
>                 case COND_OR:
> -                       if (sp < 1)
> -                               return -1;
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] |= s[sp + 1];
>                         break;
>                 case COND_AND:
> -                       if (sp < 1)
> -                               return -1;
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] &= s[sp + 1];
>                         break;
>                 case COND_XOR:
> -                       if (sp < 1)
> -                               return -1;
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] ^= s[sp + 1];
>                         break;
>                 case COND_EQ:
> -                       if (sp < 1)
> -                               return -1;
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] = (s[sp] == s[sp + 1]);
>                         break;
>                 case COND_NEQ:
> -                       if (sp < 1)
> -                               return -1;
> +                       if (unlikely(sp < 1))
> +                               goto invalid;
>                         sp--;
>                         s[sp] = (s[sp] != s[sp + 1]);
>                         break;
>                 default:
> -                       return -1;
> +                       goto invalid;
>                 }
>         }
> +
> +       if (unlikely(sp != 0))
> +               goto invalid;
> +
>         return s[0];
> +
> +invalid:
> +       /* Should *never* be reached, cause malformed expressions should
> +        * have been filtered by cond_validate_expr().
> +        */
> +       WARN_ONCE(true, "SELinux: invalid conditional expression passed validation\n");
> +       return -1;
> +}
> +
> +static int cond_validate_expr(const struct policydb *p, const struct cond_expr *expr)
> +{
> +       u32 i;
> +       int depth = -1;
> +
> +       if (expr->len == 0)
> +               return -EINVAL;
> +
> +       for (i = 0; i < expr->len; i++) {
> +               const struct cond_expr_node *node = &expr->nodes[i];
> +
> +               switch (node->expr_type) {
> +               case COND_BOOL:
> +                       if (depth >= (COND_EXPR_MAXDEPTH - 1))
> +                               return -EINVAL;
> +                       depth++;
> +                       if (!policydb_boolean_isvalid(p, node->boolean))
> +                               return -EINVAL;
> +                       break;
> +               case COND_NOT:
> +                       if (depth < 0)
> +                               return -EINVAL;
> +                       break;
> +               case COND_OR:
> +               case COND_AND:
> +               case COND_XOR:
> +               case COND_EQ:
> +               case COND_NEQ:
> +                       if (depth < 1)
> +                               return -EINVAL;
> +                       depth--;
> +                       break;
> +               default:
> +                       return -EINVAL;
> +               }
> +       }
> +
> +       if (depth != 0)
> +               return -EINVAL;
> +
> +       return 0;
>  }
>
>  /*
> @@ -356,21 +410,6 @@ static int cond_read_av_list(struct policydb *p, struct policy_file *fp,
>         return 0;
>  }
>
> -static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr)
> -{
> -       if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) {
> -               pr_err("SELinux: conditional expressions uses unknown operator.\n");
> -               return 0;
> -       }
> -
> -       if (expr->expr_type == COND_BOOL &&
> -           (expr->boolean == 0 || expr->boolean > p->p_bools.nprim)) {
> -               pr_err("SELinux: conditional expressions uses unknown bool.\n");
> -               return 0;
> -       }
> -       return 1;
> -}
> -
>  static int cond_read_node(struct policydb *p, struct cond_node *node, struct policy_file *fp)
>  {
>         __le32 buf[2];
> @@ -385,6 +424,8 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol
>
>         /* expr */
>         len = le32_to_cpu(buf[1]);
> +       if (len == 0)
> +               return -EINVAL;
>
>         /* we will read 64 bytes per node */
>         rc = size_check(2 * sizeof(u32), len, fp);
> @@ -406,9 +447,12 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol
>
>                 expr->expr_type = le32_to_cpu(buf[0]);
>                 expr->boolean = le32_to_cpu(buf[1]);
> +       }
>
> -               if (!expr_node_isvalid(p, expr))
> -                       return -EINVAL;
> +       rc = cond_validate_expr(p, &node->expr);
> +       if (rc) {
> +               pr_err("SELinux: invalid conditional expression\n");
> +               return rc;
>         }
>
>         rc = cond_read_av_list(p, fp, &node->true_list, NULL);
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index a8397ed66109..8969f7c8637c 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -945,6 +945,13 @@ int policydb_type_isvalid(struct policydb *p, unsigned int type)
>         return 1;
>  }
>
> +int policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
> +{
> +       if (!boolean || boolean > p->p_bools.nprim)
> +               return 0;
> +       return 1;
> +}
> +
>  /*
>   * Return 1 if the fields in the security context
>   * structure `c' are valid.  Return 0 otherwise.
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index bb96a6cb6101..42117adb2ca0 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -324,6 +324,7 @@ extern int policydb_context_isvalid(struct policydb *p, struct context *c);
>  extern int policydb_class_isvalid(struct policydb *p, u16 class);
>  extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
>  extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
> +extern int policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
>  extern int policydb_read(struct policydb *p, struct policy_file *fp);
>  extern int policydb_write(struct policydb *p, struct policy_file *fp);
>
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 07/14] selinux: check type attr map overflows
  2025-05-11 17:30 ` [PATCH v3 07/14] selinux: check type attr map overflows Christian Göttsche
@ 2025-05-14 18:15   ` Stephen Smalley
  2026-05-06 23:43   ` [PATCH v3 7/14] " Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 18:15 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel, Eric Suen,
	Canfeng Guo

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate that no types with an invalid too high ID are present in the
> attribute map.  Gaps are still not checked.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

But see my commentary below.

> ---
> v3: squash with previous patch ("selinux: introduce
>     ebitmap_highest_set_bit()")
> ---
>  security/selinux/ss/ebitmap.c  | 27 +++++++++++++++++++++++++++
>  security/selinux/ss/ebitmap.h  |  1 +
>  security/selinux/ss/policydb.c |  5 +++++
>  3 files changed, 33 insertions(+)
>
> diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c
> index 43bc19e21960..5d6b5b72b3e5 100644
> --- a/security/selinux/ss/ebitmap.c
> +++ b/security/selinux/ss/ebitmap.c
> @@ -257,6 +257,33 @@ int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2,
>         return 1;
>  }
>
> +u32 ebitmap_highest_set_bit(const struct ebitmap *e)
> +{
> +       const struct ebitmap_node *n;
> +       unsigned long unit;
> +       u32 pos = 0;
> +
> +       n = e->node;
> +       if (!n)
> +               return 0;
> +
> +       while (n->next)
> +               n = n->next;
> +
> +       for (unsigned int i = EBITMAP_UNIT_NUMS; i > 0; i--) {
> +               unit = n->maps[i - 1];
> +               if (unit == 0)
> +                       continue;
> +
> +               pos = (i - 1) * EBITMAP_UNIT_SIZE;
> +               while (unit >>= 1)
> +                       pos++;
> +               break;
> +       }
> +
> +       return n->startbit + pos;
> +}

Always fun to see new extensions to the ebitmap code that dates back
at least to 1996 in the original Flask code base. It's changed a lot
since then. That said, if we ever want to compute this frequently
we'll likely want to cache it in the ebitmap struct itself but this
seems fine for this usage. On a different but related note, I saw a
lot of time spent spinning through all-bits-set category set ebitmaps
in child SELinux namespaces due to the global SID mapping needing to
map contexts to each namespace. I solved that by introducing a cache
for the global SID mapping but was wondering if we ought to have a
more compressed representation of the all-bits-set ebitmap and more
efficient iterators for it. Just noting for future, not a request to
change this.


> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 8969f7c8637c..27f6809b562a 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -2955,6 +2955,11 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>                         if (rc)
>                                 goto bad;
>                 }
> +
> +               rc = -EINVAL;
> +               if (ebitmap_highest_set_bit(e) >= p->p_types.nprim)
> +                       goto bad;
> +
>                 /* add the type itself as the degenerate case */
>                 rc = ebitmap_set_bit(e, i, 1);
>                 if (rc)
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 08/14] selinux: reorder policydb_index()
  2025-05-11 17:30 ` [PATCH v3 08/14] selinux: reorder policydb_index() Christian Göttsche
@ 2025-05-14 18:24   ` Stephen Smalley
  2026-05-06 23:43   ` [PATCH v3 8/14] " Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 18:24 UTC (permalink / raw)
  To: cgzones; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Index as soon as possible to enable isvalid() checks to fail on gaps.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
>  security/selinux/ss/policydb.c | 11 ++++++-----
>  1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 27f6809b562a..326d82f8db8c 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -728,7 +728,6 @@ static int policydb_index(struct policydb *p)
>         pr_debug("SELinux:  %d classes, %d rules\n", p->p_classes.nprim,
>                  p->te_avtab.nel);
>
> -       avtab_hash_eval(&p->te_avtab, "rules");
>         symtab_hash_eval(p->symtab);
>
>         p->class_val_to_struct = kcalloc(p->p_classes.nprim,
> @@ -2799,6 +2798,10 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>                 p->symtab[i].nprim = nprim;
>         }
>
> +       rc = policydb_index(p);
> +       if (rc)
> +               goto bad;
> +
>         rc = -EINVAL;
>         p->process_class = string_to_security_class(p, "process");
>         if (!p->process_class) {
> @@ -2810,6 +2813,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>         if (rc)
>                 goto bad;
>
> +       avtab_hash_eval(&p->te_avtab, "rules");
> +
>         if (p->policyvers >= POLICYDB_VERSION_BOOL) {
>                 rc = cond_read_list(p, fp);
>                 if (rc)
> @@ -2907,10 +2912,6 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>         if (rc)
>                 goto bad;
>
> -       rc = policydb_index(p);
> -       if (rc)
> -               goto bad;
> -
>         rc = -EINVAL;
>         perm = string_to_av_perm(p, p->process_class, "transition");
>         if (!perm) {
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 09/14] selinux: beef up isvalid checks
  2025-05-11 17:30 ` [PATCH v3 09/14] selinux: beef up isvalid checks Christian Göttsche
@ 2025-05-14 18:59   ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 18:59 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Casey Schaufler, Thiébaud Weksteen, Canfeng Guo,
	Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Check that an ID does not refer to a gap in the global array of
> definitions.
>
> Constify parameters of isvalid() function and change return type to
> bool.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
>  security/selinux/ss/hashtab.h  |  4 +--
>  security/selinux/ss/mls.c      | 66 +++++++++++++++++++++-------------
>  security/selinux/ss/mls.h      |  6 ++--
>  security/selinux/ss/policydb.c | 56 ++++++++++++++++-------------
>  security/selinux/ss/policydb.h | 12 +++----
>  security/selinux/ss/services.c |  2 +-
>  security/selinux/ss/symtab.c   |  2 +-
>  security/selinux/ss/symtab.h   |  2 +-
>  8 files changed, 88 insertions(+), 62 deletions(-)
>
> diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h
> index deba82d78c3a..c641fb12916b 100644
> --- a/security/selinux/ss/hashtab.h
> +++ b/security/selinux/ss/hashtab.h
> @@ -94,11 +94,11 @@ static inline int hashtab_insert(struct hashtab *h, void *key, void *datum,
>   * Returns NULL if no entry has the specified key or
>   * the datum of the entry otherwise.
>   */
> -static inline void *hashtab_search(struct hashtab *h, const void *key,
> +static inline void *hashtab_search(const struct hashtab *h, const void *key,
>                                    struct hashtab_key_params key_params)
>  {
>         u32 hvalue;
> -       struct hashtab_node *cur;
> +       const struct hashtab_node *cur;
>
>         if (!h->size)
>                 return NULL;
> diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c
> index a6e49269f535..3cd36e2015fa 100644
> --- a/security/selinux/ss/mls.c
> +++ b/security/selinux/ss/mls.c
> @@ -32,7 +32,7 @@
>  int mls_compute_context_len(struct policydb *p, struct context *context)
>  {
>         int i, l, len, head, prev;
> -       char *nm;
> +       const char *nm;
>         struct ebitmap *e;
>         struct ebitmap_node *node;
>
> @@ -86,7 +86,8 @@ int mls_compute_context_len(struct policydb *p, struct context *context)
>  void mls_sid_to_context(struct policydb *p, struct context *context,
>                         char **scontext)
>  {
> -       char *scontextp, *nm;
> +       const char *nm;
> +       char *scontextp;
>         int i, l, head, prev;
>         struct ebitmap *e;
>         struct ebitmap_node *node;
> @@ -155,27 +156,44 @@ void mls_sid_to_context(struct policydb *p, struct context *context,
>         *scontext = scontextp;
>  }
>
> -int mls_level_isvalid(struct policydb *p, struct mls_level *l)
> +bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l)
>  {
> -       struct level_datum *levdatum;
> +       const char *name;
> +       const struct level_datum *levdatum;
> +       struct ebitmap_node *node;
> +       u32 bit;
> +       int rc;
>
>         if (!l->sens || l->sens > p->p_levels.nprim)
> -               return 0;
> -       levdatum = symtab_search(&p->p_levels,
> -                                sym_name(p, SYM_LEVELS, l->sens - 1));
> +               return false;
> +
> +       name = sym_name(p, SYM_LEVELS, l->sens - 1);
> +       if (!name)
> +               return false;
> +
> +       levdatum = symtab_search(&p->p_levels, name);
>         if (!levdatum)
> -               return 0;
> +               return false;
>
>         /*
> -        * Return 1 iff all the bits set in l->cat are also be set in
> +        * Validate that all bits set in l->cat are also be set in
>          * levdatum->level->cat and no bit in l->cat is larger than
>          * p->p_cats.nprim.
>          */
> -       return ebitmap_contains(&levdatum->level.cat, &l->cat,
> -                               p->p_cats.nprim);
> +       rc = ebitmap_contains(&levdatum->level.cat, &l->cat,
> +                             p->p_cats.nprim);
> +       if (!rc)
> +               return false;
> +
> +       ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) {
> +               if (!sym_name(p, SYM_CATS, bit))
> +                       return false;
> +       }
> +
> +       return true;
>  }
>
> -int mls_range_isvalid(struct policydb *p, struct mls_range *r)
> +bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r)
>  {
>         return (mls_level_isvalid(p, &r->level[0]) &&
>                 mls_level_isvalid(p, &r->level[1]) &&
> @@ -183,32 +201,32 @@ int mls_range_isvalid(struct policydb *p, struct mls_range *r)
>  }
>
>  /*
> - * Return 1 if the MLS fields in the security context
> + * Return true if the MLS fields in the security context
>   * structure `c' are valid.  Return 0 otherwise.
>   */
> -int mls_context_isvalid(struct policydb *p, struct context *c)
> +bool mls_context_isvalid(const struct policydb *p, const struct context *c)
>  {
> -       struct user_datum *usrdatum;
> +       const struct user_datum *usrdatum;
>
>         if (!p->mls_enabled)
> -               return 1;
> +               return true;
>
>         if (!mls_range_isvalid(p, &c->range))
> -               return 0;
> +               return false;
>
>         if (c->role == OBJECT_R_VAL)
> -               return 1;
> +               return true;
>
>         /*
>          * User must be authorized for the MLS range.
>          */
>         if (!c->user || c->user > p->p_users.nprim)
> -               return 0;
> +               return false;
>         usrdatum = p->user_val_to_struct[c->user - 1];
> -       if (!mls_range_contains(usrdatum->range, c->range))
> -               return 0; /* user may not be associated with range */
> +       if (!usrdatum || !mls_range_contains(usrdatum->range, c->range))
> +               return false; /* user may not be associated with range */
>
> -       return 1;
> +       return true;
>  }
>
>  /*
> @@ -449,8 +467,8 @@ int mls_convert_context(struct policydb *oldp, struct policydb *newp,
>                 return 0;
>
>         for (l = 0; l < 2; l++) {
> -               char *name = sym_name(oldp, SYM_LEVELS,
> -                                     oldc->range.level[l].sens - 1);
> +               const char *name = sym_name(oldp, SYM_LEVELS,
> +                                           oldc->range.level[l].sens - 1);
>
>                 levdatum = symtab_search(&newp->p_levels, name);
>
> diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h
> index 07980636751f..93cde1b22992 100644
> --- a/security/selinux/ss/mls.h
> +++ b/security/selinux/ss/mls.h
> @@ -27,9 +27,9 @@
>  int mls_compute_context_len(struct policydb *p, struct context *context);
>  void mls_sid_to_context(struct policydb *p, struct context *context,
>                         char **scontext);
> -int mls_context_isvalid(struct policydb *p, struct context *c);
> -int mls_range_isvalid(struct policydb *p, struct mls_range *r);
> -int mls_level_isvalid(struct policydb *p, struct mls_level *l);
> +bool mls_context_isvalid(const struct policydb *p, const struct context *c);
> +bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r);
> +bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l);
>
>  int mls_context_to_sid(struct policydb *p, char oldc, char *scontext,
>                        struct context *context, struct sidtab *s, u32 def_sid);
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 326d82f8db8c..f8d6e993ce89 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -923,51 +923,59 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s)
>         return 0;
>  }
>
> -int policydb_class_isvalid(struct policydb *p, u16 class)
> +bool policydb_class_isvalid(const struct policydb *p, u16 class)
>  {
>         if (!class || class > p->p_classes.nprim)
> -               return 0;
> -       return 1;
> +               return false;
> +       if (!p->sym_val_to_name[SYM_CLASSES][class - 1])
> +               return false;
> +       return true;
>  }
>
> -int policydb_role_isvalid(struct policydb *p, unsigned int role)
> +bool policydb_role_isvalid(const struct policydb *p, u32 role)
>  {
>         if (!role || role > p->p_roles.nprim)
> -               return 0;
> -       return 1;
> +               return false;
> +       if (!p->sym_val_to_name[SYM_ROLES][role - 1])
> +               return false;
> +       return true;
>  }
>
> -int policydb_type_isvalid(struct policydb *p, unsigned int type)
> +bool policydb_type_isvalid(const struct policydb *p, u32 type)
>  {
>         if (!type || type > p->p_types.nprim)
> -               return 0;
> -       return 1;
> +               return false;
> +       if (!p->sym_val_to_name[SYM_TYPES][type - 1])
> +               return false;
> +       return true;
>  }
>
> -int policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
> +bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
>  {
>         if (!boolean || boolean > p->p_bools.nprim)
> -               return 0;
> -       return 1;
> +               return false;
> +       if (!p->sym_val_to_name[SYM_BOOLS][boolean - 1])
> +               return false;
> +       return true;
>  }
>
>  /*
> - * Return 1 if the fields in the security context
> + * Return true if the fields in the security context
>   * structure `c' are valid.  Return 0 otherwise.
>   */
> -int policydb_context_isvalid(struct policydb *p, struct context *c)
> +bool policydb_context_isvalid(const struct policydb *p, const struct context *c)
>  {
> -       struct role_datum *role;
> -       struct user_datum *usrdatum;
> +       const struct role_datum *role;
> +       const struct user_datum *usrdatum;
>
>         if (!c->role || c->role > p->p_roles.nprim)
> -               return 0;
> +               return false;
>
>         if (!c->user || c->user > p->p_users.nprim)
> -               return 0;
> +               return false;
>
>         if (!c->type || c->type > p->p_types.nprim)
> -               return 0;
> +               return false;
>
>         if (c->role != OBJECT_R_VAL) {
>                 /*
> @@ -976,24 +984,24 @@ int policydb_context_isvalid(struct policydb *p, struct context *c)
>                 role = p->role_val_to_struct[c->role - 1];
>                 if (!role || !ebitmap_get_bit(&role->types, c->type - 1))
>                         /* role may not be associated with type */
> -                       return 0;
> +                       return false;
>
>                 /*
>                  * User must be authorized for the role.
>                  */
>                 usrdatum = p->user_val_to_struct[c->user - 1];
>                 if (!usrdatum)
> -                       return 0;
> +                       return false;
>
>                 if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1))
>                         /* user may not be associated with role */
> -                       return 0;
> +                       return false;
>         }
>
>         if (!mls_context_isvalid(p, c))
> -               return 0;
> +               return false;
>
> -       return 1;
> +       return true;
>  }
>
>  /*
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index 42117adb2ca0..1367387beaa7 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -320,11 +320,11 @@ struct policy_file {
>
>  extern void policydb_destroy(struct policydb *p);
>  extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
> -extern int policydb_context_isvalid(struct policydb *p, struct context *c);
> -extern int policydb_class_isvalid(struct policydb *p, u16 class);
> -extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
> -extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
> -extern int policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
> +extern bool policydb_context_isvalid(const struct policydb *p, const struct context *c);
> +extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
> +extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
> +extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
> +extern bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
>  extern int policydb_read(struct policydb *p, struct policy_file *fp);
>  extern int policydb_write(struct policydb *p, struct policy_file *fp);
>
> @@ -395,7 +395,7 @@ static inline int put_entry(const void *buf, size_t bytes, size_t num,
>         return 0;
>  }
>
> -static inline char *sym_name(struct policydb *p, unsigned int sym_num,
> +static inline const char *sym_name(const struct policydb *p, unsigned int sym_num,
>                              unsigned int element_nr)
>  {
>         return p->sym_val_to_name[sym_num][element_nr];
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 3fb971fe4fd9..5b1d0e80d975 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -464,7 +464,7 @@ static void security_dump_masked_av(struct policydb *policydb,
>         struct common_datum *common_dat;
>         struct class_datum *tclass_dat;
>         struct audit_buffer *ab;
> -       char *tclass_name;
> +       const char *tclass_name;
>         char *scontext_name = NULL;
>         char *tcontext_name = NULL;
>         char *permission_names[SEL_VEC_MAX];
> diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c
> index 832660fd84a9..a756554e7f1d 100644
> --- a/security/selinux/ss/symtab.c
> +++ b/security/selinux/ss/symtab.c
> @@ -50,7 +50,7 @@ int symtab_insert(struct symtab *s, char *name, void *datum)
>         return hashtab_insert(&s->table, name, datum, symtab_key_params);
>  }
>
> -void *symtab_search(struct symtab *s, const char *name)
> +void *symtab_search(const struct symtab *s, const char *name)
>  {
>         return hashtab_search(&s->table, name, symtab_key_params);
>  }
> diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h
> index 8e667cdbf38f..7cfa3b44953a 100644
> --- a/security/selinux/ss/symtab.h
> +++ b/security/selinux/ss/symtab.h
> @@ -21,6 +21,6 @@ struct symtab {
>  int symtab_init(struct symtab *s, u32 size);
>
>  int symtab_insert(struct symtab *s, char *name, void *datum);
> -void *symtab_search(struct symtab *s, const char *name);
> +void *symtab_search(const struct symtab *s, const char *name);
>
>  #endif /* _SS_SYMTAB_H_ */
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 10/14] selinux: validate symbols
  2025-05-11 17:30 ` [PATCH v3 10/14] selinux: validate symbols Christian Göttsche
@ 2025-05-14 19:06   ` Stephen Smalley
  2026-05-06 23:43   ` Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 19:06 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
	llvm

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Some symbol tables need to be validated after indexing, since during
> indexing their referenced entries might not yet have been indexed.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

NB role dominance is already gone in checkpolicy and never supported by secilc.
At some point likely should drop corresponding kernel and libsepol support too.

> ---
>  security/selinux/ss/policydb.c | 94 ++++++++++++++++++++++++++++++++++
>  1 file changed, 94 insertions(+)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index f8d6e993ce89..4559c8918134 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -673,6 +673,84 @@ static int (*const index_f[SYM_NUM])(void *key, void *datum, void *datap) = {
>  };
>  /* clang-format on */
>
> +static int role_validate(void *key, void *datum, void *datap)
> +{
> +       const struct policydb *p = datap;
> +       const struct role_datum *role = datum;
> +       struct ebitmap_node *node;
> +       u32 i;
> +
> +       ebitmap_for_each_positive_bit(&role->dominates, node, i) {
> +               if (!policydb_role_isvalid(p, i))
> +                       goto bad;
> +       }
> +
> +       ebitmap_for_each_positive_bit(&role->types, node, i) {
> +               if (!policydb_type_isvalid(p, i + 1))
> +                       goto bad;
> +       }
> +
> +       return 0;
> +
> +bad:
> +       pr_err("SELinux:  invalid role %s\n", sym_name(p, SYM_ROLES, role->value - 1));
> +       return -EINVAL;
> +}
> +
> +static int user_validate(void *key, void *datum, void *datap)
> +{
> +       const struct policydb *p = datap;
> +       const struct user_datum *usrdatum = datum;
> +       struct ebitmap_node *node;
> +       u32 i;
> +
> +       ebitmap_for_each_positive_bit(&usrdatum->roles, node, i) {
> +               if (!policydb_role_isvalid(p, i))
> +                       goto bad;
> +       }
> +
> +       if (!mls_range_isvalid(p, &usrdatum->range))
> +               goto bad;
> +
> +       if (!mls_level_isvalid(p, &usrdatum->dfltlevel))
> +               goto bad;
> +
> +       return 0;
> +
> +bad:
> +       pr_err("SELinux:  invalid user %s\n", sym_name(p, SYM_USERS, usrdatum->value - 1));
> +       return -EINVAL;
> +}
> +
> +static int sens_validate(void *key, void *datum, void *datap)
> +{
> +       const struct policydb *p = datap;
> +       const struct level_datum *levdatum = datum;
> +
> +       if (!mls_level_isvalid(p, &levdatum->level))
> +               goto bad;
> +
> +       return 0;
> +
> +bad:
> +       pr_err("SELinux:  invalid sensitivity\n");
> +       return -EINVAL;
> +}
> +
> +
> +/* clang-format off */
> +static int (*const validate_f[SYM_NUM])(void *key, void *datum, void *datap) = {
> +       NULL, /* Everything validated in common_read() and common_index() */
> +       NULL, /* Everything validated in class_read() and class_index() */
> +       role_validate,
> +       NULL, /* Everything validated in type_read(), type_index() and type_bounds_sanity_check() */
> +       user_validate,
> +       NULL, /* Everything validated in cond_read_bool() and cond_index_bool() */
> +       sens_validate,
> +       NULL, /* Everything validated in cat_read() and cat_index() */
> +};
> +/* clang-format on */
> +
>  #ifdef CONFIG_SECURITY_SELINUX_DEBUG
>  static void hash_eval(struct hashtab *h, const char *hash_name,
>                       const char *hash_details)
> @@ -765,6 +843,16 @@ static int policydb_index(struct policydb *p)
>                 if (rc)
>                         goto out;
>         }
> +
> +       for (i = 0; i < SYM_NUM; i++) {
> +               if (!validate_f[i])
> +                       continue;
> +
> +               rc = hashtab_map(&p->symtab[i].table, validate_f[i], p);
> +               if (rc)
> +                       goto out;
> +       }
> +
>         rc = 0;
>  out:
>         return rc;
> @@ -1087,6 +1175,12 @@ static int context_read_and_validate(struct context *c, struct policydb *p,
>                         pr_err("SELinux: error reading MLS range of context\n");
>                         goto out;
>                 }
> +
> +               rc = -EINVAL;
> +               if (!mls_range_isvalid(p, &c->range)) {
> +                       pr_warn("SELinux: invalid range in security context\n");
> +                       goto out;
> +               }
>         }
>
>         rc = -EINVAL;
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 11/14] selinux: more strict bounds check
  2025-05-11 17:30 ` [PATCH v3 11/14] selinux: more strict bounds check Christian Göttsche
@ 2025-05-14 19:11   ` Stephen Smalley
  2026-05-01 17:15   ` Stephen Smalley
  1 sibling, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 19:11 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Casey Schaufler, Thiébaud Weksteen, Canfeng Guo,
	Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate the types used in bounds checks.
> Replace the usage of BUG(), to avoid halting the system on malformed
> polices.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
>  security/selinux/ss/policydb.c | 29 +++++++++++++++++++++++++++--
>  security/selinux/ss/policydb.h |  1 +
>  security/selinux/ss/services.c |  3 +++
>  3 files changed, 31 insertions(+), 2 deletions(-)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 4559c8918134..7774f6da2ebe 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -1020,6 +1020,15 @@ bool policydb_class_isvalid(const struct policydb *p, u16 class)
>         return true;
>  }
>
> +bool policydb_user_isvalid(const struct policydb *p, u32 user)
> +{
> +       if (!user || user > p->p_roles.nprim)
> +               return false;
> +       if (!p->sym_val_to_name[SYM_USERS][user - 1])
> +               return false;
> +       return true;
> +}
> +
>  bool policydb_role_isvalid(const struct policydb *p, u32 role)
>  {
>         if (!role || role > p->p_roles.nprim)
> @@ -1942,6 +1951,12 @@ static int user_bounds_sanity_check(void *key, void *datum, void *datap)
>                         return -EINVAL;
>                 }
>
> +               if (!policydb_user_isvalid(p, upper->bounds)) {
> +                       pr_err("SELinux: user %s: invalid boundary id %d\n",
> +                              (char *) key, upper->bounds);
> +                       return -EINVAL;
> +               }
> +
>                 upper = p->user_val_to_struct[upper->bounds - 1];
>                 ebitmap_for_each_positive_bit(&user->roles, node, bit)
>                 {
> @@ -1979,6 +1994,12 @@ static int role_bounds_sanity_check(void *key, void *datum, void *datap)
>                         return -EINVAL;
>                 }
>
> +               if (!policydb_role_isvalid(p, upper->bounds)) {
> +                       pr_err("SELinux: role %s: invalid boundary id %d\n",
> +                              (char *) key, upper->bounds);
> +                       return -EINVAL;
> +               }
> +
>                 upper = p->role_val_to_struct[upper->bounds - 1];
>                 ebitmap_for_each_positive_bit(&role->types, node, bit)
>                 {
> @@ -2013,9 +2034,13 @@ static int type_bounds_sanity_check(void *key, void *datum, void *datap)
>                         return -EINVAL;
>                 }
>
> -               upper = p->type_val_to_struct[upper->bounds - 1];
> -               BUG_ON(!upper);
> +               if (!policydb_type_isvalid(p, upper->bounds)) {
> +                       pr_err("SELinux: type %s: invalid boundary id %d\n",
> +                              (char *) key, upper->bounds);
> +                       return -EINVAL;
> +               }
>
> +               upper = p->type_val_to_struct[upper->bounds - 1];
>                 if (upper->attribute) {
>                         pr_err("SELinux: type %s: "
>                                "bounded by attribute %s\n",
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index 1367387beaa7..04acf414fffa 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -324,6 +324,7 @@ extern bool policydb_context_isvalid(const struct policydb *p, const struct cont
>  extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
>  extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
>  extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
> +extern bool policydb_user_isvalid(const struct policydb *p, u32 user);
>  extern bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
>  extern int policydb_read(struct policydb *p, struct policy_file *fp);
>  extern int policydb_write(struct policydb *p, struct policy_file *fp);
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 5b1d0e80d975..464a4663c993 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -718,6 +718,9 @@ static void context_struct_compute_av(struct policydb *policydb,
>          * If the given source and target types have boundary
>          * constraint, lazy checks have to mask any violated
>          * permission and notice it to userspace via audit.
> +        *
> +        * Infinite recursion is avoided via a depth pre-check in
> +        * type_bounds_sanity_check().
>          */
>         type_attribute_bounds_av(policydb, scontext, tcontext,
>                                  tclass, avd);
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 12/14] selinux: check for simple types
  2025-05-11 17:30 ` [PATCH v3 12/14] selinux: check for simple types Christian Göttsche
@ 2025-05-14 19:22   ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 19:22 UTC (permalink / raw)
  To: cgzones; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel, Eric Suen

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate that the target of AVTAB_TYPE rules and file transitions are
> simple types and not attributes.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>

Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>

> ---
>  security/selinux/ss/avtab.c    |  9 ++++++++-
>  security/selinux/ss/policydb.c | 23 +++++++++++++++++++++--
>  security/selinux/ss/policydb.h |  1 +
>  3 files changed, 30 insertions(+), 3 deletions(-)
>
> diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
> index 50df8b69de2b..5b45f37fdcbb 100644
> --- a/security/selinux/ss/avtab.c
> +++ b/security/selinux/ss/avtab.c
> @@ -426,6 +426,13 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>                                 }
>                                 key.specified = spec_order[i] | enabled;
>                                 datum.u.data = le32_to_cpu(buf32[items++]);
> +
> +                               if ((key.specified & AVTAB_TYPE) &&
> +                                   !policydb_simpletype_isvalid(pol, datum.u.data)) {
> +                                       pr_err("SELinux: avtab: invalid type\n");
> +                                       return -EINVAL;
> +                               }
> +
>                                 rc = insertf(a, &key, &datum, p);
>                                 if (rc)
>                                         return rc;
> @@ -517,7 +524,7 @@ int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *po
>                 datum.u.data = le32_to_cpu(*buf32);
>         }
>         if ((key.specified & AVTAB_TYPE) &&
> -           !policydb_type_isvalid(pol, datum.u.data)) {
> +           !policydb_simpletype_isvalid(pol, datum.u.data)) {
>                 pr_err("SELinux: avtab: invalid type\n");
>                 return -EINVAL;
>         }
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 7774f6da2ebe..2b098d9abf17 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -686,7 +686,7 @@ static int role_validate(void *key, void *datum, void *datap)
>         }
>
>         ebitmap_for_each_positive_bit(&role->types, node, i) {
> -               if (!policydb_type_isvalid(p, i + 1))
> +               if (!policydb_simpletype_isvalid(p, i + 1))
>                         goto bad;
>         }
>
> @@ -1047,6 +1047,23 @@ bool policydb_type_isvalid(const struct policydb *p, u32 type)
>         return true;
>  }
>
> +bool policydb_simpletype_isvalid(const struct policydb *p, u32 type)
> +{
> +       const struct type_datum *datum;
> +
> +       if (!type || type > p->p_types.nprim)
> +               return false;
> +
> +       datum = p->type_val_to_struct[type - 1];
> +       if (!datum)
> +               return false;
> +
> +       if (datum->attribute)
> +               return false;
> +
> +       return true;
> +}
> +
>  bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean)
>  {
>         if (!boolean || boolean > p->p_bools.nprim)
> @@ -2235,6 +2252,8 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
>         key.name = name;
>
>         otype = le32_to_cpu(buf[3]);
> +       if (!policydb_simpletype_isvalid(p, otype))
> +               goto out;
>
>         last = NULL;
>         datum = policydb_filenametr_search(p, &key);
> @@ -2357,7 +2376,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>                 datum->otype = le32_to_cpu(buf[0]);
>
>                 rc = -EINVAL;
> -               if (!policydb_type_isvalid(p, datum->otype))
> +               if (!policydb_simpletype_isvalid(p, datum->otype))
>                         goto out;
>
>                 dst = &datum->next;
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index 04acf414fffa..b4f0c1a754cf 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -323,6 +323,7 @@ extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
>  extern bool policydb_context_isvalid(const struct policydb *p, const struct context *c);
>  extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
>  extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
> +extern bool policydb_simpletype_isvalid(const struct policydb *p, u32 type);
>  extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
>  extern bool policydb_user_isvalid(const struct policydb *p, u32 user);
>  extern bool policydb_boolean_isvalid(const struct policydb *p, u32 boolean);
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 13/14] selinux: restrict policy strings
  2025-05-11 17:30 ` [PATCH v3 13/14] selinux: restrict policy strings Christian Göttsche
@ 2025-05-14 19:40   ` Stephen Smalley
  2025-05-14 20:02     ` James Carter
  0 siblings, 1 reply; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 19:40 UTC (permalink / raw)
  To: cgzones, James Carter; +Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate the characters and the lengths of strings parsed from binary
> policies.
>
>   * Disallow control characters
>   * Limit characters of identifiers to alphanumeric, underscore, dash,
>     and dot
>   * Limit identifiers in length to 64, expect types to 1024,
>     sensitivities to 32 and categories to 16, characters
>     (excluding NUL-terminator)

(added James Carter to explicit cc for comparison with any
userspace-imposed restrictions)

I think we could easily go lower than 1024 characters for type names.

>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> ---
> v3:
>   - introduce a central limits.h header
>   - add limits for all kinds of string: filesystem names, filetrans
>     keys, genfs paths, infiniband device names
> v2:
>   - add wrappers for str_read() to minimize the usage of magic numbers
>   - limit sensitivities to a length of 32, to match categories,
>     suggested by Daniel
> ---
>  security/selinux/include/limits.h | 90 +++++++++++++++++++++++++++++++
>  security/selinux/ss/conditional.c |  5 +-
>  security/selinux/ss/conditional.h |  2 -
>  security/selinux/ss/constraint.h  |  2 -
>  security/selinux/ss/policydb.c    | 78 ++++++++++++++++++---------
>  security/selinux/ss/policydb.h    | 51 +++++++++++++++++-
>  6 files changed, 196 insertions(+), 32 deletions(-)
>  create mode 100644 security/selinux/include/limits.h
>
> diff --git a/security/selinux/include/limits.h b/security/selinux/include/limits.h
> new file mode 100644
> index 000000000000..d267c0c64f49
> --- /dev/null
> +++ b/security/selinux/include/limits.h
> @@ -0,0 +1,90 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Limits for various policy database elements.
> + */
> +
> +/*
> + * Maximum supported depth of conditional expressions.
> + */
> +#define COND_EXPR_MAXDEPTH 10
> +
> +/*
> + * Maximum supported depth for constraint expressions.
> + */
> +#define CEXPR_MAXDEPTH 5
> +
> +/*
> + * Maximum supported identifier value.
> + *
> + * Reasoning: The most used symbols are types and they need to fit into
> + *            an u16 for the avtab entries. Keep U16_MAX as special value
> + *            and U16_MAX-1 to avoid accidental overflows into U16_MAX.

This seems rather arbitrary and unnecessary to me? Unless userspace
does the same?

> + */
> +#define IDENTIFIER_MAXVALUE (U16_MAX - 2)
> +
> +/*
> + * Maximum supported length of security context strings.
> + *
> + * Reasoning: The string must fir into a PAGE_SIZE.

s/fir/fit/
s/into a/under/

> + */
> +#define CONTEXT_MAXLENGTH 4000

Any particular reason to not just make it PAGE_SIZE then?

> +
> +/*
> + * Maximum supported boolean name length.
> + */
> +#define BOOLEAN_NAME_MAXLENGTH 64
> +
> +/*
> + * Maximum supported security class and common class name length.
> + */
> +#define CLASS_NAME_MAXLENGTH 64
> +
> +/*
> + * Maximum supported permission name length.
> + */
> +#define PERMISSION_NAME_MAXLENGTH 64
> +
> +/*
> + * Maximum supported user name length.
> + */
> +#define USER_NAME_MAXLENGTH 64
> +
> +/*
> + * Maximum supported role name length.
> + */
> +#define ROLE_NAME_MAXLENGTH 64
> +
> +/*
> + * Maximum supported type name length.
> + */
> +#define TYPE_NAME_MAXLENGTH 1024

Would advocate for a lower limit unless we know of a policy that would
exceed it.

> +
> +/*
> + * Maximum supported sensitivity name length.
> + */
> +#define SENSITIVITY_NAME_MAXLENGTH 32
> +
> +/*
> + * Maximum supported category name length.
> + */
> +#define CATEGORY_NAME_MAXLENGTH 16
> +
> +/*
> + * Maximum supported path name length for keys in filename transitions.
> + */
> +#define FILETRANSKEY_NAME_MAXLENGTH 1024

These are component names only, right, not multi-component pathnames?
In that case open to lower limit or using something defined by fs layer.

> +
> +/*
> + * Maximum supported filesystem name length.

s/filesystem/filesystem type/

> + */
> +#define FILESYSTEM_NAME_MAXLENGTH 128

If we can find a limit imposed by the kernel elsewhere for fstype
names, we should just reuse that.

> +
> +/*
> + * Maximum supported path prefix length for genfs statements.
> + */
> +#define GENFS_PATH_MAXLENGTH 1024

Should just use PATH_MAX or similar definition from elsewhere.

> +
> +/*
> + * Maximum supported Infiniband device name length.
> + */
> +#define INFINIBAND_DEVNAME_MAXLENGTH 256

Would use a limit from infiniband subsystem if one exists.

> diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
> index ce0281cce739..c0a2814dafdb 100644
> --- a/security/selinux/ss/conditional.c
> +++ b/security/selinux/ss/conditional.c
> @@ -245,7 +245,8 @@ int cond_index_bool(void *key, void *datum, void *datap)
>         booldatum = datum;
>         p = datap;
>
> -       if (!booldatum->value || booldatum->value > p->p_bools.nprim)
> +       if (!booldatum->value || booldatum->value > p->p_bools.nprim ||
> +           booldatum->value > IDENTIFIER_MAXVALUE)
>                 return -EINVAL;
>
>         p->sym_val_to_name[SYM_BOOLS][booldatum->value - 1] = key;
> @@ -280,7 +281,7 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
>
>         len = le32_to_cpu(buf[2]);
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_bool(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto err;
>
> diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
> index 468e98ad3ea1..d5aefcbaa1eb 100644
> --- a/security/selinux/ss/conditional.h
> +++ b/security/selinux/ss/conditional.h
> @@ -12,8 +12,6 @@
>  #include "policydb.h"
>  #include "../include/conditional.h"
>
> -#define COND_EXPR_MAXDEPTH 10
> -
>  /*
>   * A conditional expression is a list of operators and operands
>   * in reverse polish notation.
> diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
> index 1d75a8a044df..f986156de856 100644
> --- a/security/selinux/ss/constraint.h
> +++ b/security/selinux/ss/constraint.h
> @@ -19,8 +19,6 @@
>
>  #include "ebitmap.h"
>
> -#define CEXPR_MAXDEPTH 5
> -
>  struct constraint_expr {
>  #define CEXPR_NOT   1 /* not expr */
>  #define CEXPR_AND   2 /* expr and expr */
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 2b098d9abf17..e64254985762 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -552,7 +552,8 @@ static int common_index(void *key, void *datum, void *datap)
>
>         comdatum = datum;
>         p = datap;
> -       if (!comdatum->value || comdatum->value > p->p_commons.nprim)
> +       if (!comdatum->value || comdatum->value > p->p_commons.nprim ||
> +           comdatum->value > IDENTIFIER_MAXVALUE)
>                 return -EINVAL;
>
>         p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1] = key;
> @@ -567,7 +568,8 @@ static int class_index(void *key, void *datum, void *datap)
>
>         cladatum = datum;
>         p = datap;
> -       if (!cladatum->value || cladatum->value > p->p_classes.nprim)
> +       if (!cladatum->value || cladatum->value > p->p_classes.nprim ||
> +           cladatum->value > IDENTIFIER_MAXVALUE)
>                 return -EINVAL;
>
>         p->sym_val_to_name[SYM_CLASSES][cladatum->value - 1] = key;
> @@ -583,6 +585,7 @@ static int role_index(void *key, void *datum, void *datap)
>         role = datum;
>         p = datap;
>         if (!role->value || role->value > p->p_roles.nprim ||
> +           role->value > IDENTIFIER_MAXVALUE ||
>             role->bounds > p->p_roles.nprim)
>                 return -EINVAL;
>
> @@ -601,6 +604,7 @@ static int type_index(void *key, void *datum, void *datap)
>
>         if (typdatum->primary) {
>                 if (!typdatum->value || typdatum->value > p->p_types.nprim ||
> +                   typdatum->value > IDENTIFIER_MAXVALUE ||
>                     typdatum->bounds > p->p_types.nprim)
>                         return -EINVAL;
>                 p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key;
> @@ -618,6 +622,7 @@ static int user_index(void *key, void *datum, void *datap)
>         usrdatum = datum;
>         p = datap;
>         if (!usrdatum->value || usrdatum->value > p->p_users.nprim ||
> +           usrdatum->value > IDENTIFIER_MAXVALUE ||
>             usrdatum->bounds > p->p_users.nprim)
>                 return -EINVAL;
>
> @@ -634,7 +639,8 @@ static int sens_index(void *key, void *datum, void *datap)
>         levdatum = datum;
>         p = datap;
>
> -       if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim)
> +       if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim ||
> +           levdatum->level.sens > IDENTIFIER_MAXVALUE)
>                 return -EINVAL;
>
>         if (!levdatum->isalias)
> @@ -651,7 +657,8 @@ static int cat_index(void *key, void *datum, void *datap)
>         catdatum = datum;
>         p = datap;
>
> -       if (!catdatum->value || catdatum->value > p->p_cats.nprim)
> +       if (!catdatum->value || catdatum->value > p->p_cats.nprim ||
> +           catdatum->value > IDENTIFIER_MAXVALUE)
>                 return -EINVAL;
>
>         if (!catdatum->isalias)
> @@ -1226,8 +1233,9 @@ static int context_read_and_validate(struct context *c, struct policydb *p,
>   * binary representation file.
>   */
>
> -int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len, int kind, u32 max_len)
>  {
> +       u32 i;
>         int rc;
>         char *str;
>
> @@ -1237,19 +1245,35 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
>         if (size_check(sizeof(char), len, fp))
>                 return -EINVAL;
>
> +       if (len > max_len)
> +               return -EINVAL;
> +
>         str = kmalloc(len + 1, flags | __GFP_NOWARN);
>         if (!str)
>                 return -ENOMEM;
>
>         rc = next_entry(str, fp, len);
> -       if (rc) {
> -               kfree(str);
> -               return rc;
> +       if (rc)
> +               goto bad_str;
> +
> +       rc = -EINVAL;
> +       for (i = 0; i < len; i++) {
> +               if (iscntrl(str[i]))
> +                       goto bad_str;
> +
> +               if (kind == STR_IDENTIFIER &&
> +                   !(isalnum(str[i]) || str[i] == '_' || str[i] == '-' || str[i] == '.'))
> +                       goto bad_str;
> +
>         }
>
>         str[len] = '\0';
>         *strp = str;
>         return 0;
> +
> +bad_str:
> +       kfree(str);
> +       return rc;
>  }
>
>  static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
> @@ -1274,7 +1298,7 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f
>         if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
>                 goto bad;
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_perm(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -1321,7 +1345,7 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
>                 goto bad;
>         comdatum->permissions.nprim = le32_to_cpu(buf[2]);
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_class(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -1559,12 +1583,12 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
>
>         ncons = le32_to_cpu(buf[5]);
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_class(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
>         if (len2) {
> -               rc = str_read(&cladatum->comkey, GFP_KERNEL, fp, len2);
> +               rc = str_read_class(&cladatum->comkey, GFP_KERNEL, fp, len2);
>                 if (rc)
>                         goto bad;
>
> @@ -1698,7 +1722,7 @@ static int role_read(struct policydb *p, struct symtab *s, struct policy_file *f
>         if (p->policyvers >= POLICYDB_VERSION_BOUNDARY)
>                 role->bounds = le32_to_cpu(buf[2]);
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_role(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -1765,7 +1789,7 @@ static int type_read(struct policydb *p, struct symtab *s, struct policy_file *f
>                 typdatum->primary = le32_to_cpu(buf[2]);
>         }
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_type(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -1829,7 +1853,7 @@ static int user_read(struct policydb *p, struct symtab *s, struct policy_file *f
>         if (p->policyvers >= POLICYDB_VERSION_BOUNDARY)
>                 usrdatum->bounds = le32_to_cpu(buf[2]);
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_user(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -1878,7 +1902,7 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
>                 goto bad;
>         levdatum->isalias = val;
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_sens(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -1921,7 +1945,7 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
>                 goto bad;
>         catdatum->isalias = val;
>
> -       rc = str_read(&key, GFP_KERNEL, fp, len);
> +       rc = str_read_cat(&key, GFP_KERNEL, fp, len);
>         if (rc)
>                 goto bad;
>
> @@ -2230,7 +2254,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
>         len = le32_to_cpu(buf[0]);
>
>         /* path component string */
> -       rc = str_read(&name, GFP_KERNEL, fp, len);
> +       rc = str_read(&name, GFP_KERNEL, fp, len, STR_UNCONSTRAINT, FILETRANSKEY_NAME_MAXLENGTH);
>         if (rc)
>                 return rc;
>
> @@ -2329,7 +2353,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
>         len = le32_to_cpu(buf[0]);
>
>         /* path component string */
> -       rc = str_read(&name, GFP_KERNEL, fp, len);
> +       rc = str_read(&name, GFP_KERNEL, fp, len, STR_UNCONSTRAINT, FILETRANSKEY_NAME_MAXLENGTH);
>         if (rc)
>                 return rc;
>
> @@ -2483,7 +2507,7 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
>                 if (!newgenfs)
>                         goto out;
>
> -               rc = str_read(&newgenfs->fstype, GFP_KERNEL, fp, len);
> +               rc = str_read_fsname(&newgenfs->fstype, GFP_KERNEL, fp, len);
>                 if (rc)
>                         goto out;
>
> @@ -2522,7 +2546,8 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
>                         if (!newc)
>                                 goto out;
>
> -                       rc = str_read(&newc->u.name, GFP_KERNEL, fp, len);
> +                       rc = str_read(&newc->u.name, GFP_KERNEL, fp, len,
> +                                     STR_UNCONSTRAINT, GENFS_PATH_MAXLENGTH);
>                         if (rc)
>                                 goto out;
>
> @@ -2625,7 +2650,7 @@ static int ocontext_read(struct policydb *p,
>                                         goto out;
>                                 len = le32_to_cpu(buf[0]);
>
> -                               rc = str_read(&c->u.name, GFP_KERNEL, fp, len);
> +                               rc = str_read_fsname(&c->u.name, GFP_KERNEL, fp, len);
>                                 if (rc)
>                                         goto out;
>
> @@ -2693,7 +2718,7 @@ static int ocontext_read(struct policydb *p,
>                                         goto out;
>
>                                 len = le32_to_cpu(buf[1]);
> -                               rc = str_read(&c->u.name, GFP_KERNEL, fp, len);
> +                               rc = str_read_fsname(&c->u.name, GFP_KERNEL, fp, len);
>                                 if (rc)
>                                         goto out;
>
> @@ -2759,7 +2784,9 @@ static int ocontext_read(struct policydb *p,
>                                 len = le32_to_cpu(buf[0]);
>
>                                 rc = str_read(&c->u.ibendport.dev_name,
> -                                             GFP_KERNEL, fp, len);
> +                                             GFP_KERNEL, fp, len,
> +                                             STR_UNCONSTRAINT,
> +                                             INFINIBAND_DEVNAME_MAXLENGTH);
>                                 if (rc)
>                                         goto out;
>
> @@ -2827,7 +2854,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
>                 goto bad;
>         }
>
> -       rc = str_read(&policydb_str, GFP_KERNEL, fp, len);
> +       rc = str_read(&policydb_str, GFP_KERNEL, fp, len,
> +                     STR_UNCONSTRAINT, strlen(POLICYDB_STRING));
>         if (rc) {
>                 if (rc == -ENOMEM) {
>                         pr_err("SELinux:  unable to allocate memory for policydb string of length %d\n",
> diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> index b4f0c1a754cf..e901ec648cbf 100644
> --- a/security/selinux/ss/policydb.h
> +++ b/security/selinux/ss/policydb.h
> @@ -27,6 +27,7 @@
>  #include "mls_types.h"
>  #include "context.h"
>  #include "constraint.h"
> +#include "limits.h"
>
>  /*
>   * A datum type is defined for each kind of symbol
> @@ -408,7 +409,55 @@ static inline bool val_is_boolean(u32 value)
>         return value == 0 || value == 1;
>  }
>
> -extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
> +#define STR_UNCONSTRAINT 0
> +#define STR_IDENTIFIER 1
> +extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len,
> +                   int kind, u32 max_len);
> +
> +static inline int str_read_bool(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, BOOLEAN_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_cat(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, CATEGORY_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_class(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, CLASS_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_perm(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, PERMISSION_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_role(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, ROLE_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_sens(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, SENSITIVITY_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_type(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, TYPE_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_user(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, USER_NAME_MAXLENGTH);
> +}
> +
> +static inline int str_read_fsname(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> +{
> +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, FILESYSTEM_NAME_MAXLENGTH);
> +}
>
>  extern u16 string_to_security_class(struct policydb *p, const char *name);
>  extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 14/14] selinux: harden MLS context string generation against overflows
  2025-05-11 17:30 ` [PATCH v3 14/14] selinux: harden MLS context string generation against overflows Christian Göttsche
@ 2025-05-14 19:53   ` Stephen Smalley
  0 siblings, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2025-05-14 19:53 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Thiébaud Weksteen, Casey Schaufler, Canfeng Guo,
	Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Check the length accumulator for the MLS component of security contexts
> does not overflow in mls_compute_context_len() resulting in
> out-of-buffer writes in mls_sid_to_context().
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> ---
> v3: add patch
> ---
>  security/selinux/ss/mls.c      | 18 +++++++++-----
>  security/selinux/ss/services.c | 43 +++++++++++++++++++++++++++++-----
>  2 files changed, 49 insertions(+), 12 deletions(-)
>
> diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c
> index 3cd36e2015fa..aa25724f0b0f 100644
> --- a/security/selinux/ss/mls.c
> +++ b/security/selinux/ss/mls.c
> @@ -42,7 +42,8 @@ int mls_compute_context_len(struct policydb *p, struct context *context)
>         len = 1; /* for the beginning ":" */
>         for (l = 0; l < 2; l++) {
>                 u32 index_sens = context->range.level[l].sens;
> -               len += strlen(sym_name(p, SYM_LEVELS, index_sens - 1));
> +               if (check_add_overflow(len, strlen(sym_name(p, SYM_LEVELS, index_sens - 1)), &len))
> +                       return -EOVERFLOW;

I don't believe these checks are necessary, especially once the other
limits on identifier lengths land.
The same applies throughout.

>
>                 /* categories */
>                 head = -2;
> @@ -54,24 +55,29 @@ int mls_compute_context_len(struct policydb *p, struct context *context)
>                                 /* one or more negative bits are skipped */
>                                 if (head != prev) {
>                                         nm = sym_name(p, SYM_CATS, prev);
> -                                       len += strlen(nm) + 1;
> +                                       if (check_add_overflow(len, strlen(nm) + 1, &len))
> +                                               return -EOVERFLOW;
>                                 }
>                                 nm = sym_name(p, SYM_CATS, i);
> -                               len += strlen(nm) + 1;
> +                               if (check_add_overflow(len, strlen(nm) + 1, &len))
> +                                       return -EOVERFLOW;
>                                 head = i;
>                         }
>                         prev = i;
>                 }
>                 if (prev != head) {
>                         nm = sym_name(p, SYM_CATS, prev);
> -                       len += strlen(nm) + 1;
> +                       if (check_add_overflow(len, strlen(nm) + 1, &len))
> +                               return -EOVERFLOW;
>                 }
>                 if (l == 0) {
>                         if (mls_level_eq(&context->range.level[0],
>                                          &context->range.level[1]))
>                                 break;
> -                       else
> -                               len++;
> +                       else {
> +                               if (check_add_overflow(len, 1, &len))
> +                                       return -EOVERFLOW;
> +                       }
>                 }
>         }
>
> diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> index 464a4663c993..dc6dce2eb7d2 100644
> --- a/security/selinux/ss/services.c
> +++ b/security/selinux/ss/services.c
> @@ -1247,10 +1247,12 @@ static int context_struct_to_string(struct policydb *p,
>                                     char **scontext, u32 *scontext_len)
>  {
>         char *scontextp;
> +       size_t len;
> +       int mls_len;
>
>         if (scontext)
>                 *scontext = NULL;
> -       *scontext_len = 0;
> +       len = 0;
>
>         if (context->len) {
>                 *scontext_len = context->len;
> @@ -1263,16 +1265,45 @@ static int context_struct_to_string(struct policydb *p,
>         }
>
>         /* Compute the size of the context. */
> -       *scontext_len += strlen(sym_name(p, SYM_USERS, context->user - 1)) + 1;
> -       *scontext_len += strlen(sym_name(p, SYM_ROLES, context->role - 1)) + 1;
> -       *scontext_len += strlen(sym_name(p, SYM_TYPES, context->type - 1)) + 1;
> -       *scontext_len += mls_compute_context_len(p, context);
> +       len += strlen(sym_name(p, SYM_USERS, context->user - 1)) + 1;
> +       len += strlen(sym_name(p, SYM_ROLES, context->role - 1)) + 1;
> +       len += strlen(sym_name(p, SYM_TYPES, context->type - 1)) + 1;
> +
> +       mls_len = mls_compute_context_len(p, context);
> +       if (unlikely(mls_len < 0)) {
> +               pr_warn_ratelimited("SELinux: %s:  MLS security context component too large [%s:%s:%s[:[%s:%d]-[%s:%d]]]\n",
> +                                   __func__,
> +                                   sym_name(p, SYM_USERS, context->user - 1),
> +                                   sym_name(p, SYM_ROLES, context->role - 1),
> +                                   sym_name(p, SYM_TYPES, context->type - 1),
> +                                   sym_name(p, SYM_LEVELS, context->range.level[0].sens - 1),
> +                                   ebitmap_length(&context->range.level[0].cat),
> +                                   sym_name(p, SYM_LEVELS, context->range.level[1].sens - 1),
> +                                   ebitmap_length(&context->range.level[1].cat));
> +               return -EOVERFLOW;
> +       }
> +
> +       if (unlikely(check_add_overflow(len, mls_len, &len) || len > CONTEXT_MAXLENGTH)) {
> +               pr_warn_ratelimited("SELinux: %s:  security context string of length %zu too large [%s:%s:%s[:[%s:%d]-[%s:%d]]]\n",
> +                                   __func__,
> +                                   len,
> +                                   sym_name(p, SYM_USERS, context->user - 1),
> +                                   sym_name(p, SYM_ROLES, context->role - 1),
> +                                   sym_name(p, SYM_TYPES, context->type - 1),
> +                                   sym_name(p, SYM_LEVELS, context->range.level[0].sens - 1),
> +                                   ebitmap_length(&context->range.level[0].cat),
> +                                   sym_name(p, SYM_LEVELS, context->range.level[1].sens - 1),
> +                                   ebitmap_length(&context->range.level[1].cat));
> +               return -EOVERFLOW;
> +       }
> +
> +       *scontext_len = len;
>
>         if (!scontext)
>                 return 0;
>
>         /* Allocate space for the context; caller must free this space. */
> -       scontextp = kmalloc(*scontext_len, GFP_ATOMIC);
> +       scontextp = kmalloc(len, GFP_ATOMIC);
>         if (!scontextp)
>                 return -ENOMEM;
>         *scontext = scontextp;
> --
> 2.49.0
>

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 13/14] selinux: restrict policy strings
  2025-05-14 19:40   ` Stephen Smalley
@ 2025-05-14 20:02     ` James Carter
  0 siblings, 0 replies; 37+ messages in thread
From: James Carter @ 2025-05-14 20:02 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: cgzones, selinux, Paul Moore, Ondrej Mosnacek, linux-kernel

On Wed, May 14, 2025 at 3:40 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
> <cgoettsche@seltendoof.de> wrote:
> >
> > From: Christian Göttsche <cgzones@googlemail.com>
> >
> > Validate the characters and the lengths of strings parsed from binary
> > policies.
> >
> >   * Disallow control characters
> >   * Limit characters of identifiers to alphanumeric, underscore, dash,
> >     and dot
> >   * Limit identifiers in length to 64, expect types to 1024,
> >     sensitivities to 32 and categories to 16, characters
> >     (excluding NUL-terminator)
>
> (added James Carter to explicit cc for comparison with any
> userspace-imposed restrictions)
>
> I think we could easily go lower than 1024 characters for type names.
>

CIL has a limit of 2,048 for identifiers. Checkpolicy has a limit of 8,192.
We definitely could have lower limits. I think that I would rather
have one limit rather than a bunch of different limits, but it
wouldn't be too hard to do different limits if there is a good reason
to do that.
Even a limit of 256 would seem to be sufficient (at least until AI takes over).
Jim

> >
> > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> > ---
> > v3:
> >   - introduce a central limits.h header
> >   - add limits for all kinds of string: filesystem names, filetrans
> >     keys, genfs paths, infiniband device names
> > v2:
> >   - add wrappers for str_read() to minimize the usage of magic numbers
> >   - limit sensitivities to a length of 32, to match categories,
> >     suggested by Daniel
> > ---
> >  security/selinux/include/limits.h | 90 +++++++++++++++++++++++++++++++
> >  security/selinux/ss/conditional.c |  5 +-
> >  security/selinux/ss/conditional.h |  2 -
> >  security/selinux/ss/constraint.h  |  2 -
> >  security/selinux/ss/policydb.c    | 78 ++++++++++++++++++---------
> >  security/selinux/ss/policydb.h    | 51 +++++++++++++++++-
> >  6 files changed, 196 insertions(+), 32 deletions(-)
> >  create mode 100644 security/selinux/include/limits.h
> >
> > diff --git a/security/selinux/include/limits.h b/security/selinux/include/limits.h
> > new file mode 100644
> > index 000000000000..d267c0c64f49
> > --- /dev/null
> > +++ b/security/selinux/include/limits.h
> > @@ -0,0 +1,90 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Limits for various policy database elements.
> > + */
> > +
> > +/*
> > + * Maximum supported depth of conditional expressions.
> > + */
> > +#define COND_EXPR_MAXDEPTH 10
> > +
> > +/*
> > + * Maximum supported depth for constraint expressions.
> > + */
> > +#define CEXPR_MAXDEPTH 5
> > +
> > +/*
> > + * Maximum supported identifier value.
> > + *
> > + * Reasoning: The most used symbols are types and they need to fit into
> > + *            an u16 for the avtab entries. Keep U16_MAX as special value
> > + *            and U16_MAX-1 to avoid accidental overflows into U16_MAX.
>
> This seems rather arbitrary and unnecessary to me? Unless userspace
> does the same?
>
> > + */
> > +#define IDENTIFIER_MAXVALUE (U16_MAX - 2)
> > +
> > +/*
> > + * Maximum supported length of security context strings.
> > + *
> > + * Reasoning: The string must fir into a PAGE_SIZE.
>
> s/fir/fit/
> s/into a/under/
>
> > + */
> > +#define CONTEXT_MAXLENGTH 4000
>
> Any particular reason to not just make it PAGE_SIZE then?
>
> > +
> > +/*
> > + * Maximum supported boolean name length.
> > + */
> > +#define BOOLEAN_NAME_MAXLENGTH 64
> > +
> > +/*
> > + * Maximum supported security class and common class name length.
> > + */
> > +#define CLASS_NAME_MAXLENGTH 64
> > +
> > +/*
> > + * Maximum supported permission name length.
> > + */
> > +#define PERMISSION_NAME_MAXLENGTH 64
> > +
> > +/*
> > + * Maximum supported user name length.
> > + */
> > +#define USER_NAME_MAXLENGTH 64
> > +
> > +/*
> > + * Maximum supported role name length.
> > + */
> > +#define ROLE_NAME_MAXLENGTH 64
> > +
> > +/*
> > + * Maximum supported type name length.
> > + */
> > +#define TYPE_NAME_MAXLENGTH 1024
>
> Would advocate for a lower limit unless we know of a policy that would
> exceed it.
>
> > +
> > +/*
> > + * Maximum supported sensitivity name length.
> > + */
> > +#define SENSITIVITY_NAME_MAXLENGTH 32
> > +
> > +/*
> > + * Maximum supported category name length.
> > + */
> > +#define CATEGORY_NAME_MAXLENGTH 16
> > +
> > +/*
> > + * Maximum supported path name length for keys in filename transitions.
> > + */
> > +#define FILETRANSKEY_NAME_MAXLENGTH 1024
>
> These are component names only, right, not multi-component pathnames?
> In that case open to lower limit or using something defined by fs layer.
>
> > +
> > +/*
> > + * Maximum supported filesystem name length.
>
> s/filesystem/filesystem type/
>
> > + */
> > +#define FILESYSTEM_NAME_MAXLENGTH 128
>
> If we can find a limit imposed by the kernel elsewhere for fstype
> names, we should just reuse that.
>
> > +
> > +/*
> > + * Maximum supported path prefix length for genfs statements.
> > + */
> > +#define GENFS_PATH_MAXLENGTH 1024
>
> Should just use PATH_MAX or similar definition from elsewhere.
>
> > +
> > +/*
> > + * Maximum supported Infiniband device name length.
> > + */
> > +#define INFINIBAND_DEVNAME_MAXLENGTH 256
>
> Would use a limit from infiniband subsystem if one exists.
>
> > diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
> > index ce0281cce739..c0a2814dafdb 100644
> > --- a/security/selinux/ss/conditional.c
> > +++ b/security/selinux/ss/conditional.c
> > @@ -245,7 +245,8 @@ int cond_index_bool(void *key, void *datum, void *datap)
> >         booldatum = datum;
> >         p = datap;
> >
> > -       if (!booldatum->value || booldatum->value > p->p_bools.nprim)
> > +       if (!booldatum->value || booldatum->value > p->p_bools.nprim ||
> > +           booldatum->value > IDENTIFIER_MAXVALUE)
> >                 return -EINVAL;
> >
> >         p->sym_val_to_name[SYM_BOOLS][booldatum->value - 1] = key;
> > @@ -280,7 +281,7 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
> >
> >         len = le32_to_cpu(buf[2]);
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_bool(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto err;
> >
> > diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
> > index 468e98ad3ea1..d5aefcbaa1eb 100644
> > --- a/security/selinux/ss/conditional.h
> > +++ b/security/selinux/ss/conditional.h
> > @@ -12,8 +12,6 @@
> >  #include "policydb.h"
> >  #include "../include/conditional.h"
> >
> > -#define COND_EXPR_MAXDEPTH 10
> > -
> >  /*
> >   * A conditional expression is a list of operators and operands
> >   * in reverse polish notation.
> > diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
> > index 1d75a8a044df..f986156de856 100644
> > --- a/security/selinux/ss/constraint.h
> > +++ b/security/selinux/ss/constraint.h
> > @@ -19,8 +19,6 @@
> >
> >  #include "ebitmap.h"
> >
> > -#define CEXPR_MAXDEPTH 5
> > -
> >  struct constraint_expr {
> >  #define CEXPR_NOT   1 /* not expr */
> >  #define CEXPR_AND   2 /* expr and expr */
> > diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> > index 2b098d9abf17..e64254985762 100644
> > --- a/security/selinux/ss/policydb.c
> > +++ b/security/selinux/ss/policydb.c
> > @@ -552,7 +552,8 @@ static int common_index(void *key, void *datum, void *datap)
> >
> >         comdatum = datum;
> >         p = datap;
> > -       if (!comdatum->value || comdatum->value > p->p_commons.nprim)
> > +       if (!comdatum->value || comdatum->value > p->p_commons.nprim ||
> > +           comdatum->value > IDENTIFIER_MAXVALUE)
> >                 return -EINVAL;
> >
> >         p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1] = key;
> > @@ -567,7 +568,8 @@ static int class_index(void *key, void *datum, void *datap)
> >
> >         cladatum = datum;
> >         p = datap;
> > -       if (!cladatum->value || cladatum->value > p->p_classes.nprim)
> > +       if (!cladatum->value || cladatum->value > p->p_classes.nprim ||
> > +           cladatum->value > IDENTIFIER_MAXVALUE)
> >                 return -EINVAL;
> >
> >         p->sym_val_to_name[SYM_CLASSES][cladatum->value - 1] = key;
> > @@ -583,6 +585,7 @@ static int role_index(void *key, void *datum, void *datap)
> >         role = datum;
> >         p = datap;
> >         if (!role->value || role->value > p->p_roles.nprim ||
> > +           role->value > IDENTIFIER_MAXVALUE ||
> >             role->bounds > p->p_roles.nprim)
> >                 return -EINVAL;
> >
> > @@ -601,6 +604,7 @@ static int type_index(void *key, void *datum, void *datap)
> >
> >         if (typdatum->primary) {
> >                 if (!typdatum->value || typdatum->value > p->p_types.nprim ||
> > +                   typdatum->value > IDENTIFIER_MAXVALUE ||
> >                     typdatum->bounds > p->p_types.nprim)
> >                         return -EINVAL;
> >                 p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key;
> > @@ -618,6 +622,7 @@ static int user_index(void *key, void *datum, void *datap)
> >         usrdatum = datum;
> >         p = datap;
> >         if (!usrdatum->value || usrdatum->value > p->p_users.nprim ||
> > +           usrdatum->value > IDENTIFIER_MAXVALUE ||
> >             usrdatum->bounds > p->p_users.nprim)
> >                 return -EINVAL;
> >
> > @@ -634,7 +639,8 @@ static int sens_index(void *key, void *datum, void *datap)
> >         levdatum = datum;
> >         p = datap;
> >
> > -       if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim)
> > +       if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim ||
> > +           levdatum->level.sens > IDENTIFIER_MAXVALUE)
> >                 return -EINVAL;
> >
> >         if (!levdatum->isalias)
> > @@ -651,7 +657,8 @@ static int cat_index(void *key, void *datum, void *datap)
> >         catdatum = datum;
> >         p = datap;
> >
> > -       if (!catdatum->value || catdatum->value > p->p_cats.nprim)
> > +       if (!catdatum->value || catdatum->value > p->p_cats.nprim ||
> > +           catdatum->value > IDENTIFIER_MAXVALUE)
> >                 return -EINVAL;
> >
> >         if (!catdatum->isalias)
> > @@ -1226,8 +1233,9 @@ static int context_read_and_validate(struct context *c, struct policydb *p,
> >   * binary representation file.
> >   */
> >
> > -int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len, int kind, u32 max_len)
> >  {
> > +       u32 i;
> >         int rc;
> >         char *str;
> >
> > @@ -1237,19 +1245,35 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> >         if (size_check(sizeof(char), len, fp))
> >                 return -EINVAL;
> >
> > +       if (len > max_len)
> > +               return -EINVAL;
> > +
> >         str = kmalloc(len + 1, flags | __GFP_NOWARN);
> >         if (!str)
> >                 return -ENOMEM;
> >
> >         rc = next_entry(str, fp, len);
> > -       if (rc) {
> > -               kfree(str);
> > -               return rc;
> > +       if (rc)
> > +               goto bad_str;
> > +
> > +       rc = -EINVAL;
> > +       for (i = 0; i < len; i++) {
> > +               if (iscntrl(str[i]))
> > +                       goto bad_str;
> > +
> > +               if (kind == STR_IDENTIFIER &&
> > +                   !(isalnum(str[i]) || str[i] == '_' || str[i] == '-' || str[i] == '.'))
> > +                       goto bad_str;
> > +
> >         }
> >
> >         str[len] = '\0';
> >         *strp = str;
> >         return 0;
> > +
> > +bad_str:
> > +       kfree(str);
> > +       return rc;
> >  }
> >
> >  static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
> > @@ -1274,7 +1298,7 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f
> >         if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
> >                 goto bad;
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_perm(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -1321,7 +1345,7 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
> >                 goto bad;
> >         comdatum->permissions.nprim = le32_to_cpu(buf[2]);
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_class(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -1559,12 +1583,12 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
> >
> >         ncons = le32_to_cpu(buf[5]);
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_class(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> >         if (len2) {
> > -               rc = str_read(&cladatum->comkey, GFP_KERNEL, fp, len2);
> > +               rc = str_read_class(&cladatum->comkey, GFP_KERNEL, fp, len2);
> >                 if (rc)
> >                         goto bad;
> >
> > @@ -1698,7 +1722,7 @@ static int role_read(struct policydb *p, struct symtab *s, struct policy_file *f
> >         if (p->policyvers >= POLICYDB_VERSION_BOUNDARY)
> >                 role->bounds = le32_to_cpu(buf[2]);
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_role(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -1765,7 +1789,7 @@ static int type_read(struct policydb *p, struct symtab *s, struct policy_file *f
> >                 typdatum->primary = le32_to_cpu(buf[2]);
> >         }
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_type(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -1829,7 +1853,7 @@ static int user_read(struct policydb *p, struct symtab *s, struct policy_file *f
> >         if (p->policyvers >= POLICYDB_VERSION_BOUNDARY)
> >                 usrdatum->bounds = le32_to_cpu(buf[2]);
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_user(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -1878,7 +1902,7 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f
> >                 goto bad;
> >         levdatum->isalias = val;
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_sens(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -1921,7 +1945,7 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp
> >                 goto bad;
> >         catdatum->isalias = val;
> >
> > -       rc = str_read(&key, GFP_KERNEL, fp, len);
> > +       rc = str_read_cat(&key, GFP_KERNEL, fp, len);
> >         if (rc)
> >                 goto bad;
> >
> > @@ -2230,7 +2254,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f
> >         len = le32_to_cpu(buf[0]);
> >
> >         /* path component string */
> > -       rc = str_read(&name, GFP_KERNEL, fp, len);
> > +       rc = str_read(&name, GFP_KERNEL, fp, len, STR_UNCONSTRAINT, FILETRANSKEY_NAME_MAXLENGTH);
> >         if (rc)
> >                 return rc;
> >
> > @@ -2329,7 +2353,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
> >         len = le32_to_cpu(buf[0]);
> >
> >         /* path component string */
> > -       rc = str_read(&name, GFP_KERNEL, fp, len);
> > +       rc = str_read(&name, GFP_KERNEL, fp, len, STR_UNCONSTRAINT, FILETRANSKEY_NAME_MAXLENGTH);
> >         if (rc)
> >                 return rc;
> >
> > @@ -2483,7 +2507,7 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
> >                 if (!newgenfs)
> >                         goto out;
> >
> > -               rc = str_read(&newgenfs->fstype, GFP_KERNEL, fp, len);
> > +               rc = str_read_fsname(&newgenfs->fstype, GFP_KERNEL, fp, len);
> >                 if (rc)
> >                         goto out;
> >
> > @@ -2522,7 +2546,8 @@ static int genfs_read(struct policydb *p, struct policy_file *fp)
> >                         if (!newc)
> >                                 goto out;
> >
> > -                       rc = str_read(&newc->u.name, GFP_KERNEL, fp, len);
> > +                       rc = str_read(&newc->u.name, GFP_KERNEL, fp, len,
> > +                                     STR_UNCONSTRAINT, GENFS_PATH_MAXLENGTH);
> >                         if (rc)
> >                                 goto out;
> >
> > @@ -2625,7 +2650,7 @@ static int ocontext_read(struct policydb *p,
> >                                         goto out;
> >                                 len = le32_to_cpu(buf[0]);
> >
> > -                               rc = str_read(&c->u.name, GFP_KERNEL, fp, len);
> > +                               rc = str_read_fsname(&c->u.name, GFP_KERNEL, fp, len);
> >                                 if (rc)
> >                                         goto out;
> >
> > @@ -2693,7 +2718,7 @@ static int ocontext_read(struct policydb *p,
> >                                         goto out;
> >
> >                                 len = le32_to_cpu(buf[1]);
> > -                               rc = str_read(&c->u.name, GFP_KERNEL, fp, len);
> > +                               rc = str_read_fsname(&c->u.name, GFP_KERNEL, fp, len);
> >                                 if (rc)
> >                                         goto out;
> >
> > @@ -2759,7 +2784,9 @@ static int ocontext_read(struct policydb *p,
> >                                 len = le32_to_cpu(buf[0]);
> >
> >                                 rc = str_read(&c->u.ibendport.dev_name,
> > -                                             GFP_KERNEL, fp, len);
> > +                                             GFP_KERNEL, fp, len,
> > +                                             STR_UNCONSTRAINT,
> > +                                             INFINIBAND_DEVNAME_MAXLENGTH);
> >                                 if (rc)
> >                                         goto out;
> >
> > @@ -2827,7 +2854,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp)
> >                 goto bad;
> >         }
> >
> > -       rc = str_read(&policydb_str, GFP_KERNEL, fp, len);
> > +       rc = str_read(&policydb_str, GFP_KERNEL, fp, len,
> > +                     STR_UNCONSTRAINT, strlen(POLICYDB_STRING));
> >         if (rc) {
> >                 if (rc == -ENOMEM) {
> >                         pr_err("SELinux:  unable to allocate memory for policydb string of length %d\n",
> > diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> > index b4f0c1a754cf..e901ec648cbf 100644
> > --- a/security/selinux/ss/policydb.h
> > +++ b/security/selinux/ss/policydb.h
> > @@ -27,6 +27,7 @@
> >  #include "mls_types.h"
> >  #include "context.h"
> >  #include "constraint.h"
> > +#include "limits.h"
> >
> >  /*
> >   * A datum type is defined for each kind of symbol
> > @@ -408,7 +409,55 @@ static inline bool val_is_boolean(u32 value)
> >         return value == 0 || value == 1;
> >  }
> >
> > -extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
> > +#define STR_UNCONSTRAINT 0
> > +#define STR_IDENTIFIER 1
> > +extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len,
> > +                   int kind, u32 max_len);
> > +
> > +static inline int str_read_bool(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, BOOLEAN_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_cat(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, CATEGORY_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_class(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, CLASS_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_perm(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, PERMISSION_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_role(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, ROLE_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_sens(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, SENSITIVITY_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_type(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, TYPE_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_user(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, USER_NAME_MAXLENGTH);
> > +}
> > +
> > +static inline int str_read_fsname(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
> > +{
> > +       return str_read(strp, flags, fp, len, STR_IDENTIFIER, FILESYSTEM_NAME_MAXLENGTH);
> > +}
> >
> >  extern u16 string_to_security_class(struct policydb *p, const char *name);
> >  extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name);
> > --
> > 2.49.0
> >

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 02/14] selinux: use u16 for security classes
  2025-05-13 19:44   ` Stephen Smalley
@ 2025-05-17 21:53     ` David Laight
  0 siblings, 0 replies; 37+ messages in thread
From: David Laight @ 2025-05-17 21:53 UTC (permalink / raw)
  To: Stephen Smalley
  Cc: cgzones, selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Thiébaud Weksteen, Casey Schaufler, Canfeng Guo,
	Takaya Saeki

On Tue, 13 May 2025 15:44:09 -0400
Stephen Smalley <stephen.smalley.work@gmail.com> wrote:

> On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
> <cgoettsche@seltendoof.de> wrote:
> >
> > From: Christian Göttsche <cgzones@googlemail.com>
> >
> > Security class identifiers are limited to 2^16, thus use the appropriate
> > type u16 consistently.
> >
> > Signed-off-by: Christian Göttsche <cgzones@googlemail.com>  
> 
> Historical footnote: originally security classes were _not_ limited to
> 2^16 but a later memory optimization of the avtab rendered them so.

The fact that the value is limited to 2^16 doesn't mean that the type
can't be larger.
You won't save anything by using u16 instead of u32, if anything the
code will get larger and there can be subtle changes because u16 gets
promoted to 'signed int' before being used.

	David

> 
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> 
> > ---
> > v3: only change type, move the validation (> U16_MAX) to the subsequent
> >     patch
> > ---
> >  security/selinux/ss/policydb.c |  5 +++--
> >  security/selinux/ss/policydb.h | 10 +++++-----
> >  security/selinux/ss/services.c |  2 +-
> >  3 files changed, 9 insertions(+), 8 deletions(-)
> >
> > diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> > index dc701a7f8652..f490556ddb5c 100644
> > --- a/security/selinux/ss/policydb.c
> > +++ b/security/selinux/ss/policydb.c
> > @@ -927,7 +927,7 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s)
> >         return 0;
> >  }
> >
> > -int policydb_class_isvalid(struct policydb *p, unsigned int class)
> > +int policydb_class_isvalid(struct policydb *p, u16 class)
> >  {
> >         if (!class || class > p->p_classes.nprim)
> >                 return 0;
> > @@ -2003,7 +2003,8 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp
> >         struct filename_trans_key *ft = NULL;
> >         struct filename_trans_datum **dst, *datum, *first = NULL;
> >         char *name = NULL;
> > -       u32 len, ttype, tclass, ndatum, i;
> > +       u32 len, ttype, ndatum, i;
> > +       u16 tclass;
> >         __le32 buf[3];
> >         int rc;
> >
> > diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h
> > index 25650224b6e7..0c423ad77fd9 100644
> > --- a/security/selinux/ss/policydb.h
> > +++ b/security/selinux/ss/policydb.h
> > @@ -48,7 +48,7 @@ struct common_datum {
> >
> >  /* Class attributes */
> >  struct class_datum {
> > -       u32 value; /* class value */
> > +       u16 value; /* class value */
> >         char *comkey; /* common name */
> >         struct common_datum *comdatum; /* common datum */
> >         struct symtab permissions; /* class-specific permission symbol table */
> > @@ -82,7 +82,7 @@ struct role_datum {
> >  struct role_trans_key {
> >         u32 role; /* current role */
> >         u32 type; /* program executable type, or new object type */
> > -       u32 tclass; /* process class, or new object class */
> > +       u16 tclass; /* process class, or new object class */
> >  };
> >
> >  struct role_trans_datum {
> > @@ -139,7 +139,7 @@ struct cat_datum {
> >  struct range_trans {
> >         u32 source_type;
> >         u32 target_type;
> > -       u32 target_class;
> > +       u16 target_class;
> >  };
> >
> >  /* Boolean data type */
> > @@ -195,7 +195,7 @@ struct ocontext {
> >                 } ibendport;
> >         } u;
> >         union {
> > -               u32 sclass; /* security class for genfs */
> > +               u16 sclass; /* security class for genfs */
> >                 u32 behavior; /* labeling behavior for fs_use */
> >         } v;
> >         struct context context[2]; /* security context(s) */
> > @@ -320,7 +320,7 @@ struct policy_file {
> >  extern void policydb_destroy(struct policydb *p);
> >  extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
> >  extern int policydb_context_isvalid(struct policydb *p, struct context *c);
> > -extern int policydb_class_isvalid(struct policydb *p, unsigned int class);
> > +extern int policydb_class_isvalid(struct policydb *p, u16 class);
> >  extern int policydb_type_isvalid(struct policydb *p, unsigned int type);
> >  extern int policydb_role_isvalid(struct policydb *p, unsigned int role);
> >  extern int policydb_read(struct policydb *p, struct policy_file *fp);
> > diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
> > index 7becf3808818..a2dd42e750fe 100644
> > --- a/security/selinux/ss/services.c
> > +++ b/security/selinux/ss/services.c
> > @@ -3387,7 +3387,7 @@ static int get_classes_callback(void *k, void *d, void *args)
> >  {
> >         struct class_datum *datum = d;
> >         char *name = k, **classes = args;
> > -       u32 value = datum->value - 1;
> > +       u16 value = datum->value - 1;
> >
> >         classes[value] = kstrdup(name, GFP_ATOMIC);
> >         if (!classes[value])
> > --
> > 2.49.0
> >  
> 


^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 11/14] selinux: more strict bounds check
  2025-05-11 17:30 ` [PATCH v3 11/14] selinux: more strict bounds check Christian Göttsche
  2025-05-14 19:11   ` Stephen Smalley
@ 2026-05-01 17:15   ` Stephen Smalley
  1 sibling, 0 replies; 37+ messages in thread
From: Stephen Smalley @ 2026-05-01 17:15 UTC (permalink / raw)
  To: cgzones
  Cc: selinux, Paul Moore, Ondrej Mosnacek, linux-kernel,
	Casey Schaufler, Thiébaud Weksteen, Takaya Saeki

On Sun, May 11, 2025 at 1:31 PM Christian Göttsche
<cgoettsche@seltendoof.de> wrote:
>
> From: Christian Göttsche <cgzones@googlemail.com>
>
> Validate the types used in bounds checks.
> Replace the usage of BUG(), to avoid halting the system on malformed
> polices.
>
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> ---
>  security/selinux/ss/policydb.c | 29 +++++++++++++++++++++++++++--
>  security/selinux/ss/policydb.h |  1 +
>  security/selinux/ss/services.c |  3 +++
>  3 files changed, 31 insertions(+), 2 deletions(-)
>
> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index 4559c8918134..7774f6da2ebe 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -1020,6 +1020,15 @@ bool policydb_class_isvalid(const struct policydb *p, u16 class)
>         return true;
>  }
>
> +bool policydb_user_isvalid(const struct policydb *p, u32 user)
> +{
> +       if (!user || user > p->p_roles.nprim)
> +               return false;

s/p_roles/p_users/

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 3/14] selinux: more strict policy parsing
  2025-05-11 17:30 ` [PATCH v3 03/14] selinux: more strict policy parsing Christian Göttsche
  2025-05-14 14:15   ` Stephen Smalley
@ 2026-05-06 23:43   ` Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Paul Moore @ 2026-05-06 23:43 UTC (permalink / raw)
  To: Christian Göttsche, selinux
  Cc: Christian Göttsche, Stephen Smalley, Ondrej Mosnacek,
	linux-kernel, Thiébaud Weksteen, Bram Bonné,
	Eric Suen, Casey Schaufler, Canfeng Guo, Takaya Saeki

On May 11, 2025 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> 
> Be more strict during parsing of policies and reject invalid values.
> 
> Add some error messages in the case of policy parse failures, to
> enhance debugging, either on a malformed policy or a too strict check.
> 
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
> v3:
>   - incorporate the overflow checks on security classes from the
>     previous patch, and permit U16_MAX as class ID
>   - minimize the usage of magic values, by using macros or trivial
>     helper functions
> v2:
>   accept unknown xperm specifiers to support backwards compatibility for
>   future ones, suggested by Thiébaud
> ---
>  security/selinux/include/security.h |   1 +
>  security/selinux/ss/avtab.c         |  35 ++++-
>  security/selinux/ss/avtab.h         |  13 ++
>  security/selinux/ss/conditional.c   |  18 +--
>  security/selinux/ss/constraint.h    |   1 +
>  security/selinux/ss/policydb.c      | 196 +++++++++++++++++++++++-----
>  security/selinux/ss/policydb.h      |  23 +++-
>  security/selinux/ss/services.c      |   6 +-
>  8 files changed, 233 insertions(+), 60 deletions(-)

I fixed the checkpatch.pl warning in pr_warn_once_policyload() but
otherwise this looks good to me, merged into selinux/dev.  Thanks!

--
paul-moore.com

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 7/14] selinux: check type attr map overflows
  2025-05-11 17:30 ` [PATCH v3 07/14] selinux: check type attr map overflows Christian Göttsche
  2025-05-14 18:15   ` Stephen Smalley
@ 2026-05-06 23:43   ` Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Paul Moore @ 2026-05-06 23:43 UTC (permalink / raw)
  To: Christian Göttsche, selinux
  Cc: Christian Göttsche, Stephen Smalley, Ondrej Mosnacek,
	linux-kernel, Eric Suen, Canfeng Guo

On May 11, 2025 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> 
> Validate that no types with an invalid too high ID are present in the
> attribute map.  Gaps are still not checked.
> 
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
> v3: squash with previous patch ("selinux: introduce
>     ebitmap_highest_set_bit()")
> ---
>  security/selinux/ss/ebitmap.c  | 27 +++++++++++++++++++++++++++
>  security/selinux/ss/ebitmap.h  |  1 +
>  security/selinux/ss/policydb.c |  5 +++++
>  3 files changed, 33 insertions(+)

I changed the name to ebitmap_get_highest_set_bit(), but otherwise this
looks good to me, merged to selinux/dev, thanks.

--
paul-moore.com

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 8/14] selinux: reorder policydb_index()
  2025-05-11 17:30 ` [PATCH v3 08/14] selinux: reorder policydb_index() Christian Göttsche
  2025-05-14 18:24   ` Stephen Smalley
@ 2026-05-06 23:43   ` Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Paul Moore @ 2026-05-06 23:43 UTC (permalink / raw)
  To: Christian Göttsche, selinux
  Cc: Christian Göttsche, Stephen Smalley, Ondrej Mosnacek, linux-kernel

On May 11, 2025 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> 
> Index as soon as possible to enable isvalid() checks to fail on gaps.
> 
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
>  security/selinux/ss/policydb.c | 11 ++++++-----
>  1 file changed, 6 insertions(+), 5 deletions(-)

Merged into selinux/dev, thanks.

--
paul-moore.com

^ permalink raw reply	[flat|nested] 37+ messages in thread

* Re: [PATCH v3 10/14] selinux: validate symbols
  2025-05-11 17:30 ` [PATCH v3 10/14] selinux: validate symbols Christian Göttsche
  2025-05-14 19:06   ` Stephen Smalley
@ 2026-05-06 23:43   ` Paul Moore
  1 sibling, 0 replies; 37+ messages in thread
From: Paul Moore @ 2026-05-06 23:43 UTC (permalink / raw)
  To: Christian Göttsche, selinux
  Cc: Christian Göttsche, Stephen Smalley, Ondrej Mosnacek,
	linux-kernel, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
	Justin Stitt, llvm

On May 11, 2025 =?UTF-8?q?Christian=20G=C3=B6ttsche?= <cgoettsche@seltendoof.de> wrote:
> 
> Some symbol tables need to be validated after indexing, since during
> indexing their referenced entries might not yet have been indexed.
> 
> Signed-off-by: Christian Göttsche <cgzones@googlemail.com>
> Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
> ---
>  security/selinux/ss/policydb.c | 94 ++++++++++++++++++++++++++++++++++
>  1 file changed, 94 insertions(+)

...

> diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
> index f8d6e993ce89..4559c8918134 100644
> --- a/security/selinux/ss/policydb.c
> +++ b/security/selinux/ss/policydb.c
> @@ -765,6 +843,16 @@ static int policydb_index(struct policydb *p)
>  		if (rc)
>  			goto out;
>  	}
> +
> +	for (i = 0; i < SYM_NUM; i++) {
> +		if (!validate_f[i])
> +			continue;
> +
> +		rc = hashtab_map(&p->symtab[i].table, validate_f[i], p);
> +		if (rc)
> +			goto out;
> +	}

Is there a reason why we need a second loop to do the validation?  Can we
simply do the validation in the indexing loop above this?

  for (i = 0; i < SYM_NUM; i++) {
    p->table[i] = kvcalloc(...);

    hashtab_map(p->table, index_f[i]);

    if (validate_f[i])
      hashtab_map(p->table, validate_f[i]);
  }

--
paul-moore.com

^ permalink raw reply	[flat|nested] 37+ messages in thread

end of thread, other threads:[~2026-05-06 23:43 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-05-11 17:30 [PATCH v3 01/14] selinux: avoid nontransitive comparison Christian Göttsche
2025-05-11 17:30 ` [PATCH v3 02/14] selinux: use u16 for security classes Christian Göttsche
2025-05-13 19:44   ` Stephen Smalley
2025-05-17 21:53     ` David Laight
2025-05-11 17:30 ` [PATCH v3 03/14] selinux: more strict policy parsing Christian Göttsche
2025-05-14 14:15   ` Stephen Smalley
2025-05-14 14:25     ` Stephen Smalley
2026-05-06 23:43   ` [PATCH v3 3/14] " Paul Moore
2025-05-11 17:30 ` [PATCH v3 04/14] selinux: check length fields in policies Christian Göttsche
2025-05-14 17:26   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 05/14] selinux: validate constraints Christian Göttsche
2025-05-14 17:51   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 06/14] selinux: pre-validate conditional expressions Christian Göttsche
2025-05-14 17:55   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 07/14] selinux: check type attr map overflows Christian Göttsche
2025-05-14 18:15   ` Stephen Smalley
2026-05-06 23:43   ` [PATCH v3 7/14] " Paul Moore
2025-05-11 17:30 ` [PATCH v3 08/14] selinux: reorder policydb_index() Christian Göttsche
2025-05-14 18:24   ` Stephen Smalley
2026-05-06 23:43   ` [PATCH v3 8/14] " Paul Moore
2025-05-11 17:30 ` [PATCH v3 09/14] selinux: beef up isvalid checks Christian Göttsche
2025-05-14 18:59   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 10/14] selinux: validate symbols Christian Göttsche
2025-05-14 19:06   ` Stephen Smalley
2026-05-06 23:43   ` Paul Moore
2025-05-11 17:30 ` [PATCH v3 11/14] selinux: more strict bounds check Christian Göttsche
2025-05-14 19:11   ` Stephen Smalley
2026-05-01 17:15   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 12/14] selinux: check for simple types Christian Göttsche
2025-05-14 19:22   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 13/14] selinux: restrict policy strings Christian Göttsche
2025-05-14 19:40   ` Stephen Smalley
2025-05-14 20:02     ` James Carter
2025-05-11 17:30 ` [PATCH v3 14/14] selinux: harden MLS context string generation against overflows Christian Göttsche
2025-05-14 19:53   ` Stephen Smalley
2025-05-11 17:30 ` [PATCH v3 00/14] selinux: harden against malformed policies Christian Göttsche
2025-05-13 19:40 ` [PATCH v3 01/14] selinux: avoid nontransitive comparison Stephen Smalley

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome