selinux/stable-7.2 PR 20260805

-----BEGIN PGP SIGNATURE-----
 
 iQJIBAABCgAyFiEES0KozwfymdVUl37v6iDy2pc3iXMFAmpzc6gUHHBhdWxAcGF1
 bC1tb29yZS5jb20ACgkQ6iDy2pc3iXPZug//SUeo33WvIYrLhlMYwPaVh9jqaR7c
 TG/bdUUofqPHxCXAMdmdqBVv3p3xU+7h6ds4CHFmgiWCUJ53ApbH+tl+lPxOzxTC
 awIWed5tJbFBiZ9ZQNc2hwaY5ETM0wQsVoZDAqWBZeu+Yf76ynjJN5fwxXRKBtcO
 7DJR/NpPj1lHdp6AR3rfB/RQUVhg/0CnlFYb04Ef5aIwZ8fdlFh99GaZsQtMZvfO
 do6Yud/iNyEsL42lp7Zf21Ejlq8ubToLUCKEMygT2iDhKBq69DbOgMABkH0m6Bco
 A98s1KYARddzxT98bzRZkhzxiun0r9QVprYWzFPGi+pMkXi5eG6qgEMq/tkST313
 kD3GsQPPQxY5k/AklD57/CoeI9rU7ioeZH/ZfFxDHupl5ExogI26VJ3h2obkdq3A
 aMZlr/rl5PPJD9qxjw8qFQd7Wn8A7Q1p041Bbf3pi1xxeVvdvP2z/e/MU9RQ7Xpr
 UIQNqd2QU3/uVstY743oximLRNQnRL7Aqq8r5KrbqeP9mlX6r+eEv2q0nBuqIAcR
 7FOnkIn86ptl6prbTbG1FEYeRVypxAGuJrA6dDv8h2r4c4BuRc1Za3vVqluXH2Vg
 OxExgrP2Dg1TMQFAse4dTQlGOltLl1FvTd9JBm1t3kxpi0pbby2mUcHg3xjC/Cbm
 8Dbxe9fEmsK+/jc=
 =YbZ/
 -----END PGP SIGNATURE-----

Merge tag 'selinux-pr-20260805' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux

Pull selinux fixes from Paul Moore:

 - Continue to improve the validation of SELinux policies during load

 - Fix a SELinux regression caused by bpffs changes in v7.2-rc1

 - Fix a SELinux preformance regression caused by SELinux changes in
   v7.2-rc1

* tag 'selinux-pr-20260805' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux:
  selinux: check level category sets once at load time
  selinux: require every boolean value to be defined
  selinux: reject an unclaimed class value in security_get_classes()
  selinux: require a class's permission values to cover its permission count
  selinux: do not cancel a policy conversion that never started
  selinux: bpf: check SBLABEL_MNT before isec init
  selinux: reject a class permission count below its inherited common
  selinux: reject a permission value exceeding the class permission count
This commit is contained in:
Linus Torvalds 2026-08-05 13:40:11 -07:00
commit c5096fec0c
4 changed files with 136 additions and 32 deletions

View File

@ -2974,6 +2974,10 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
sbsec = selinux_superblock(dir->i_sb);
if (!selinux_initialized() ||
!(sbsec->flags & SBLABEL_MNT))
return -EOPNOTSUPP;
newsid = crsec->create_sid;
newsclass = inode_mode_to_security_class(inode->i_mode);
rc = selinux_determine_inode_label(crsec, dir, qstr, newsclass, &newsid);
@ -2988,10 +2992,6 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
isec->initialized = LABEL_INITIALIZED;
}
if (!selinux_initialized() ||
!(sbsec->flags & SBLABEL_MNT))
return -EOPNOTSUPP;
xattr = lsm_get_xattr_slot(xattrs, xattr_count);
if (xattr) {
rc = security_sid_to_context_force(newsid,

View File

@ -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)

View File

@ -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,
@ -719,6 +736,7 @@ static inline void symtab_hash_eval(struct symtab *s)
static int policydb_index(struct policydb *p)
{
int i, rc;
u32 v;
if (p->mls_enabled)
pr_debug(
@ -769,6 +787,30 @@ static int policydb_index(struct policydb *p)
if (rc)
goto out;
}
/*
* A sparse class value is absorbed by policydb_class_isvalid() and
* its siblings, but no such predicate exists for booleans: every
* user of bool_val_to_struct[] walks it by index and dereferences
* each entry -- cond_evaluate_expr(), the two getters and
* security_set_bools() -- so an unclaimed one has no consumer that
* can tolerate it.
*/
for (v = 0; v < p->p_bools.nprim; v++) {
if (!p->bool_val_to_struct[v]) {
pr_err("SELinux: boolean %u is declared but not defined\n",
v + 1);
rc = -EINVAL;
goto out;
}
}
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;
@ -1154,7 +1196,18 @@ int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
return 0;
}
static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
/*
* Bitmap of the permission values a symtab has claimed. Values are 1-based
* and bounded by SEL_VEC_MAX, the width of an access vector, so the whole set
* fits in a u32 and the callers reject an nprim past that width.
*/
static u32 perm_claimed_mask(u32 nprim)
{
return nprim ? U32_MAX >> (SEL_VEC_MAX - nprim) : 0;
}
static int perm_read(struct policydb *p, struct symtab *s,
struct policy_file *fp, u32 *claimed)
{
char *key = NULL;
struct perm_datum *perdatum;
@ -1175,6 +1228,13 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f
rc = -EINVAL;
if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
goto bad;
/* indexes an nprim-sized array in security_get_permissions() */
if (perdatum->value > s->nprim)
goto bad;
/* two permissions cannot share one slot of that array */
if (*claimed & (1U << (perdatum->value - 1)))
goto bad;
*claimed |= 1U << (perdatum->value - 1);
rc = str_read(&key, GFP_KERNEL, fp, len);
if (rc)
@ -1195,7 +1255,7 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
char *key = NULL;
struct common_datum *comdatum;
__le32 buf[4];
u32 i, len, nel;
u32 i, len, nel, claimed = 0;
int rc;
comdatum = kzalloc_obj(*comdatum);
@ -1222,17 +1282,28 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file
if (rc)
goto bad;
comdatum->permissions.nprim = le32_to_cpu(buf[2]);
/* no permission value can reach a slot past SEL_VEC_MAX */
rc = -EINVAL;
if (comdatum->permissions.nprim > SEL_VEC_MAX)
goto bad;
rc = str_read(&key, GFP_KERNEL, fp, len);
if (rc)
goto bad;
for (i = 0; i < nel; i++) {
rc = perm_read(p, &comdatum->permissions, fp);
rc = perm_read(p, &comdatum->permissions, fp, &claimed);
if (rc)
goto bad;
}
rc = -EINVAL;
if (claimed != perm_claimed_mask(comdatum->permissions.nprim)) {
pr_err("SELinux: common %s does not define every permission it declares\n",
key);
goto bad;
}
hash_eval(&comdatum->permissions.table, "common_permissions", key);
rc = symtab_insert(s, key, comdatum);
@ -1366,7 +1437,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, val;
u32 i, len, len2, ncons, nel, val, claimed = 0, inherited = 0;
int rc;
cladatum = kzalloc_obj(*cladatum);
@ -1399,6 +1470,10 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
if (rc)
goto bad;
cladatum->permissions.nprim = le32_to_cpu(buf[3]);
/* no permission value can reach a slot past SEL_VEC_MAX */
rc = -EINVAL;
if (cladatum->permissions.nprim > SEL_VEC_MAX)
goto bad;
ncons = le32_to_cpu(buf[5]);
@ -1419,13 +1494,36 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file *
cladatum->comkey);
goto bad;
}
/*
* security_get_permissions() maps the common's permissions
* into an array sized by this class's nprim, so a class must
* declare at least as many as the common it inherits.
*/
if (cladatum->permissions.nprim <
cladatum->comdatum->permissions.nprim) {
pr_err("SELinux: class %s has fewer permissions than common %s\n",
key, cladatum->comkey);
goto bad;
}
}
for (i = 0; i < nel; i++) {
rc = perm_read(p, &cladatum->permissions, fp);
rc = perm_read(p, &cladatum->permissions, fp, &claimed);
if (rc)
goto bad;
}
/* the class's own permissions must claim the slots the common leaves */
if (cladatum->comdatum)
inherited = cladatum->comdatum->permissions.nprim;
rc = -EINVAL;
if (claimed != (perm_claimed_mask(cladatum->permissions.nprim) &
~perm_claimed_mask(inherited))) {
pr_err("SELinux: class %s does not define every permission it declares\n",
key);
goto bad;
}
hash_eval(&cladatum->permissions.table, "class_permissions", key);
rc = read_cons_helper(p, &cladatum->constraints, ncons, 0, fp);

View File

@ -2221,7 +2221,9 @@ void selinux_policy_cancel(struct selinux_load_state *load_state)
oldpolicy = rcu_dereference_protected(state->policy,
lockdep_is_held(&state->policy_mutex));
sidtab_cancel_convert(oldpolicy->sidtab);
/* a first load has no outgoing policy and converted nothing */
if (oldpolicy)
sidtab_cancel_convert(oldpolicy->sidtab);
selinux_policy_free(load_state->policy);
kfree(load_state->convert_data);
}
@ -3302,6 +3304,7 @@ int security_get_classes(struct selinux_policy *policy,
char ***classes, u32 *nclasses)
{
struct policydb *policydb;
u32 i;
int rc;
policydb = &policy->policydb;
@ -3314,16 +3317,29 @@ int security_get_classes(struct selinux_policy *policy,
rc = hashtab_map(&policydb->p_classes.table, get_classes_callback,
*classes);
if (rc) {
u32 i;
if (rc)
goto err;
for (i = 0; i < *nclasses; i++)
kfree((*classes)[i]);
kfree(*classes);
/*
* The class symtab may be sparse, which policydb_class_isvalid() exists
* to absorb; the callback fills this array by value, so an unclaimed
* one leaves a NULL that sel_make_classes() hands to sel_make_dir().
*/
for (i = 0; i < *nclasses; i++) {
if (!(*classes)[i]) {
rc = -EINVAL;
goto err;
}
}
out:
return rc;
err:
for (i = 0; i < *nclasses; i++)
kfree((*classes)[i]);
kfree(*classes);
return rc;
}
static int get_permissions_callback(void *k, void *d, void *args)