Commit 9c1cc4a7f792 for kernel
commit 9c1cc4a7f79275ef93746f6247685763b475bfb0
Author: Stephen Smalley <stephen.smalley.work@gmail.com>
Date: Tue Aug 4 09:57:00 2026 -0400
selinux: check level category sets once at load time
As reported by Jiri Vozar, commit 7edea6e8c8e8 ("selinux: beef up
isvalid checks") introduces a new loop in mls_level_isvalid() that
causes ~89-94% throughput regression in System V IPC message queue
operations (msgsnd/msgrcv).
Move the expensive part of the ebitmap checking to policy load time
instead as the reporter suggested.
Link: https://lore.kernel.org/selinux/CAMgFczCi2Z011dNf84Amc0Q-qnTt0+VUjWY+Y7zPyXdaH35Jvw@mail.gmail.com/
Fixes: 7edea6e8c8e8 ("selinux: beef up isvalid checks")
Reported-by: Jiri Vozar <jvozar@redhat.com>
Suggested-by: Jiri Vozar <jvozar@redhat.com>
Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c
index 3cd36e2015fa..40c62600650e 100644
--- a/security/selinux/ss/mls.c
+++ b/security/selinux/ss/mls.c
@@ -160,9 +160,6 @@ bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l)
{
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 false;
@@ -176,21 +173,14 @@ bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l)
return false;
/*
- * 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.
+ * l is valid iff every bit in l->cat is set in levdatum->level.cat
+ * and no bit in l->cat is larger than p->p_cats.nprim.
+ * policydb_index() has already verified that every bit set in
+ * levdatum->level.cat names a defined category, so containment is
+ * sufficient here.
*/
- 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;
+ return ebitmap_contains(&levdatum->level.cat, &l->cat,
+ p->p_cats.nprim);
}
bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r)
diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c
index d88713201be9..8a32666c0ba2 100644
--- a/security/selinux/ss/policydb.c
+++ b/security/selinux/ss/policydb.c
@@ -665,6 +665,23 @@ static int cat_index(void *key, void *datum, void *datap)
return 0;
}
+static int sens_cat_index_check(void *key, void *datum, void *datap)
+{
+ struct policydb *p = datap;
+ struct level_datum *levdatum = datum;
+ struct ebitmap_node *node;
+ u32 bit;
+
+ ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) {
+ if (bit >= p->p_cats.nprim || !sym_name(p, SYM_CATS, bit)) {
+ pr_err("SELinux: sensitivity %s allows undefined category %u\n",
+ (const char *)key, bit + 1);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
/* clang-format off */
static int (*const index_f[SYM_NUM])(void *key, void *datum, void *datap) = {
common_index,
@@ -788,6 +805,12 @@ static int policydb_index(struct policydb *p)
}
}
+ if (p->mls_enabled) {
+ rc = hashtab_map(&p->p_levels.table, sens_cat_index_check, p);
+ if (rc)
+ goto out;
+ }
+
rc = 0;
out:
return rc;