diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-05 13:40:11 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-05 13:40:11 -0700 |
| commit | c5096fec0c58a4f4d2475d4d88697c505800e50e (patch) | |
| tree | 16f09c30b7c001765a5b7d55e493973e33f8b368 /security | |
| parent | 0d839570765118029aa8bf4a95444c6a11aacf85 (diff) | |
| parent | 9c1cc4a7f79275ef93746f6247685763b475bfb0 (diff) | |
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
Diffstat (limited to 'security')
| -rw-r--r-- | security/selinux/hooks.c | 8 | ||||
| -rw-r--r-- | security/selinux/ss/mls.c | 24 | ||||
| -rw-r--r-- | security/selinux/ss/policydb.c | 108 | ||||
| -rw-r--r-- | security/selinux/ss/services.c | 28 |
4 files changed, 136 insertions, 32 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8d6945edae7a..18dd28b2bb13 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -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, 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 ead504a639e3..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, @@ -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); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2d828548f3db..7afce975436e 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -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) |
