From 5adb9195e091ee16617418123ce603cf38104acd Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 31 May 2026 19:58:52 +0300 Subject: [PATCH 01/11] selinux: hooks: use kmalloc() to allocate path buffer selinux_genfs_get_sid() allocates memory for a path with __get_free_page(). Such usage does not require a "page" and the size of the buffer should actually be PATH_MAX which may be less than PAGE_SIZE on some architectures. Replace __get_free_page() for allocation of a path buffer with kmalloc() and make it explicit that the buffer size is PATH_MAX. Signed-off-by: Mike Rapoport (Microsoft) Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 1a713d96206f..d1f089917a82 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1336,11 +1336,11 @@ static int selinux_genfs_get_sid(struct dentry *dentry, struct super_block *sb = dentry->d_sb; char *buffer, *path; - buffer = (char *)__get_free_page(GFP_KERNEL); + buffer = kmalloc(PATH_MAX, GFP_KERNEL); if (!buffer) return -ENOMEM; - path = dentry_path_raw(dentry, buffer, PAGE_SIZE); + path = dentry_path_raw(dentry, buffer, PATH_MAX); if (IS_ERR(path)) rc = PTR_ERR(path); else { @@ -1361,7 +1361,7 @@ static int selinux_genfs_get_sid(struct dentry *dentry, rc = 0; } } - free_page((unsigned long)buffer); + kfree(buffer); return rc; } From dc67c654bc9c9a742e82b5f19acb2f3a00b44cf5 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Mon, 22 Jun 2026 22:37:22 +0800 Subject: [PATCH 02/11] selinux: clean up selinuxfs resources on init failure init_sel_fs() creates the selinuxfs mount point and registers the filesystem before mounting selinuxfs internally. If kern_mount() or the subsequent lookup of the null file fails, the function returns without undoing the resources that were already registered. Add the missing error unwinding so the internal mount, filesystem registration, and sysfs mount point are released as appropriate. Signed-off-by: Haoxiang Li Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/selinuxfs.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 5aaaf69410bb..c7d91476971c 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -1984,17 +1984,15 @@ int __init init_sel_fs(void) return err; err = register_filesystem(&sel_fs_type); - if (err) { - sysfs_remove_mount_point(fs_kobj, "selinux"); - return err; - } + if (err) + goto err_remove_mount_point; selinux_null.mnt = kern_mount(&sel_fs_type); if (IS_ERR(selinux_null.mnt)) { pr_err("selinuxfs: could not mount!\n"); err = PTR_ERR(selinux_null.mnt); selinux_null.mnt = NULL; - return err; + goto err_unregister_fs; } selinux_null.dentry = try_lookup_noperm(&null_name, @@ -2003,7 +2001,7 @@ int __init init_sel_fs(void) pr_err("selinuxfs: could not lookup null!\n"); err = PTR_ERR(selinux_null.dentry); selinux_null.dentry = NULL; - return err; + goto err_unmount; } /* @@ -2012,5 +2010,14 @@ int __init init_sel_fs(void) */ (void) selinux_kernel_status_page(); + return 0; + +err_unmount: + kern_unmount(selinux_null.mnt); + selinux_null.mnt = NULL; +err_unregister_fs: + unregister_filesystem(&sel_fs_type); +err_remove_mount_point: + sysfs_remove_mount_point(fs_kobj, "selinux"); return err; } From 64740a0588ffb045be442dcb4222675e0a165a1b Mon Sep 17 00:00:00 2001 From: Kalevi Kolttonen Date: Mon, 22 Jun 2026 20:42:18 +0300 Subject: [PATCH 03/11] selinux: convert int flags to bool flags in ss/services.c Update security_sid_to_context_core(), security_context_to_sid_core(), and their callers to pass boolean flags via bool rather than int. No functional change. Signed-off-by: Kalevi Kolttonen Acked-by: Stephen Smalley [PM: minor subject and description tweaks] Signed-off-by: Paul Moore --- security/selinux/ss/services.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2d828548f3db..c03d3acc0a7c 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1355,8 +1355,8 @@ const char *security_get_initial_sid_context(u32 sid) } static int security_sid_to_context_core(u32 sid, char **scontext, - u32 *scontext_len, int force, - int only_invalid) + u32 *scontext_len, bool force, + bool only_invalid) { struct selinux_policy *policy; struct policydb *policydb; @@ -1439,14 +1439,14 @@ out_unlock: int security_sid_to_context(u32 sid, char **scontext, u32 *scontext_len) { return security_sid_to_context_core(sid, scontext, - scontext_len, 0, 0); + scontext_len, false, false); } int security_sid_to_context_force(u32 sid, char **scontext, u32 *scontext_len) { return security_sid_to_context_core(sid, scontext, - scontext_len, 1, 0); + scontext_len, true, false); } /** @@ -1466,7 +1466,7 @@ int security_sid_to_context_inval(u32 sid, char **scontext, u32 *scontext_len) { return security_sid_to_context_core(sid, scontext, - scontext_len, 1, 1); + scontext_len, true, true); } /* @@ -1552,7 +1552,7 @@ out: static int security_context_to_sid_core(const char *scontext, u32 scontext_len, u32 *sid, u32 def_sid, gfp_t gfp_flags, - int force) + bool force) { struct selinux_policy *policy; struct policydb *policydb; @@ -1641,7 +1641,7 @@ int security_context_to_sid(const char *scontext, u32 scontext_len, u32 *sid, gfp_t gfp) { return security_context_to_sid_core(scontext, scontext_len, - sid, SECSID_NULL, gfp, 0); + sid, SECSID_NULL, gfp, false); } int security_context_str_to_sid(const char *scontext, u32 *sid, gfp_t gfp) @@ -1673,14 +1673,14 @@ int security_context_to_sid_default(const char *scontext, u32 scontext_len, u32 *sid, u32 def_sid, gfp_t gfp_flags) { return security_context_to_sid_core(scontext, scontext_len, - sid, def_sid, gfp_flags, 1); + sid, def_sid, gfp_flags, true); } int security_context_to_sid_force(const char *scontext, u32 scontext_len, u32 *sid) { return security_context_to_sid_core(scontext, scontext_len, - sid, SECSID_NULL, GFP_KERNEL, 1); + sid, SECSID_NULL, GFP_KERNEL, true); } static int compute_sid_handle_invalid_context( From d3e4784c2bfda39fb78b037bac3785f4effc1743 Mon Sep 17 00:00:00 2001 From: Kalevi Kolttonen Date: Mon, 22 Jun 2026 23:39:12 +0300 Subject: [PATCH 04/11] selinux: drop unnecessary goto and label from avc_alloc_node() Since avc_alloc_node() doesn't need to perform any cleanup on its exit paths, there is no benefit to keeping the out label and goto statement. Drop it. Signed-off-by: Kalevi Kolttonen Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 813e82bcfc27..a9401d6c2e5f 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -497,7 +497,7 @@ static struct avc_node *avc_alloc_node(void) node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT); if (!node) - goto out; + return NULL; INIT_HLIST_NODE(&node->list); avc_cache_stats_incr(allocations); @@ -506,7 +506,6 @@ static struct avc_node *avc_alloc_node(void) selinux_avc.avc_cache_threshold) avc_reclaim_node(); -out: return node; } From ef0740b4b75fe48fb411b2a76aafbab0cdd6b0ba Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Thu, 7 May 2026 12:16:34 -0400 Subject: [PATCH 05/11] selinux: tighten type validation during policy load Tighten type validation during policy load to improve robustness in the face of ill-formed policies. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index ead504a639e3..ba1c9bd9e29f 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -604,10 +604,15 @@ static int type_index(void *key, void *datum, void *datap) typdatum = datum; p = datap; + if (!typdatum->value || typdatum->value > p->p_types.nprim || + typdatum->bounds > p->p_types.nprim) { + pr_err("SELinux: type %s had value %u bounds %u nprim %u\n", + (char *)key, typdatum->value, typdatum->bounds, + p->p_types.nprim); + return -EINVAL; + } + if (typdatum->primary) { - if (!typdatum->value || typdatum->value > p->p_types.nprim || - typdatum->bounds > p->p_types.nprim) - return -EINVAL; p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key; p->type_val_to_struct[typdatum->value - 1] = typdatum; } From e7a614c008efb2f4a5db980be3a4b13b6a170a50 Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Thu, 9 Jul 2026 14:31:28 +0800 Subject: [PATCH 06/11] selinux: suppress warning flood for retired DCCP netlink messages When deploying linux-6.18.y stable kernel to production servers, we observed kernel dmesg being flooded with SELinux warnings when running `ss -l`: SELinux: unrecognized netlink message: protocol=4 nlmsg_type=19 \ sclass=netlink_tcpdiag_socket pid=188945 comm=ss The root cause is that DCCP support was retired in commit 2a63dd0edf38 ("net: Retire DCCP socket."). Consequently, DCCPDIAG_GETSOCK was removed from nlmsg_tcpdiag_perms. This causes nlmsg_perm() to return -EINVAL, triggering the SELinux warning for every `ss -l` invocation [0]. Use pr_warn_once() for the retired DCCPDIAG_GETSOCK to prevent message flooding. Link: https://github.com/iproute2/iproute2/blob/main/misc/ss.c#L3901 [0] Fixes: 2a63dd0edf38 ("net: Retire DCCP socket.") Suggested-by: Paul Moore Signed-off-by: Yafang Shao Cc: Kuniyuki Iwashima Cc: Stephen Smalley Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d1f089917a82..f4a4edbff2bd 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -94,6 +94,7 @@ #include #include #include +#include #include "initcalls.h" #include "avc.h" @@ -6272,12 +6273,17 @@ static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb) return rc; } else if (rc == -EINVAL) { /* -EINVAL is a missing msg/perm mapping */ - pr_warn_ratelimited("SELinux: unrecognized netlink" - " message: protocol=%hu nlmsg_type=%hu sclass=%s" - " pid=%d comm=%s\n", - sk->sk_protocol, nlh->nlmsg_type, - secclass_map[sclass - 1].name, - task_pid_nr(current), current->comm); + if (sclass == SECCLASS_NETLINK_TCPDIAG_SOCKET && + nlh->nlmsg_type == DCCPDIAG_GETSOCK) + pr_warn_once("SELinux: DCCP has been removed, pid=%d comm=%s\n", + task_pid_nr(current), current->comm); + else + pr_warn_ratelimited("SELinux: unrecognized netlink" + " message: protocol=%hu nlmsg_type=%hu sclass=%s" + " pid=%d comm=%s\n", + sk->sk_protocol, nlh->nlmsg_type, + secclass_map[sclass - 1].name, + task_pid_nr(current), current->comm); if (enforcing_enabled() && !security_get_allow_unknown()) return rc; From 00f73e2a48548b1410a2403cd2d268a7d5ab0618 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Fri, 26 Jun 2026 10:57:10 -0500 Subject: [PATCH 07/11] selinux: replace strlcat() with seq_buf in selinux_ima_collect_state() In preparation for removing the deprecated strlcat() API[1], replace the strscpy()/strlcat() chain in selinux_ima_collect_state() with a struct seq_buf, which tracks the write position and remaining space internally. Each field is written with seq_buf_printf() using a "=%d;" format, which removes the open-coded "=1;"/"=0;" constants. The seven per-append WARN_ON(rc >= buf_len) truncation checks are replaced by a single seq_buf_has_overflowed() check after the string is built. Link: https://github.com/KSPP/linux/issues/370 [1] Signed-off-by: Ian Bridges Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ima.c | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/security/selinux/ima.c b/security/selinux/ima.c index aa34da9b0aeb..cb0efa2fc1ad 100644 --- a/security/selinux/ima.c +++ b/security/selinux/ima.c @@ -9,6 +9,7 @@ */ #include #include +#include #include "security.h" #include "ima.h" @@ -20,46 +21,31 @@ */ static char *selinux_ima_collect_state(void) { - const char *on = "=1;", *off = "=0;"; + struct seq_buf s; char *buf; - int buf_len, len, i, rc; + int buf_len, suffix_len, i; buf_len = strlen("initialized=0;enforcing=0;checkreqprot=0;") + 1; + suffix_len = strlen("=0;"); - len = strlen(on); for (i = 0; i < __POLICYDB_CAP_MAX; i++) - buf_len += strlen(selinux_policycap_names[i]) + len; + buf_len += strlen(selinux_policycap_names[i]) + suffix_len; buf = kzalloc(buf_len, GFP_KERNEL); if (!buf) return NULL; - rc = strscpy(buf, "initialized", buf_len); - WARN_ON(rc < 0); + seq_buf_init(&s, buf, buf_len); - rc = strlcat(buf, selinux_initialized() ? on : off, buf_len); - WARN_ON(rc >= buf_len); + seq_buf_printf(&s, "initialized=%d;enforcing=%d;checkreqprot=%d;", + selinux_initialized(), enforcing_enabled(), + checkreqprot_get()); - rc = strlcat(buf, "enforcing", buf_len); - WARN_ON(rc >= buf_len); + for (i = 0; i < __POLICYDB_CAP_MAX; i++) + seq_buf_printf(&s, "%s=%d;", selinux_policycap_names[i], + selinux_state.policycap[i]); - rc = strlcat(buf, enforcing_enabled() ? on : off, buf_len); - WARN_ON(rc >= buf_len); - - rc = strlcat(buf, "checkreqprot", buf_len); - WARN_ON(rc >= buf_len); - - rc = strlcat(buf, checkreqprot_get() ? on : off, buf_len); - WARN_ON(rc >= buf_len); - - for (i = 0; i < __POLICYDB_CAP_MAX; i++) { - rc = strlcat(buf, selinux_policycap_names[i], buf_len); - WARN_ON(rc >= buf_len); - - rc = strlcat(buf, selinux_state.policycap[i] ? on : off, - buf_len); - WARN_ON(rc >= buf_len); - } + WARN_ON(seq_buf_has_overflowed(&s)); return buf; } From c0b6a5b89d1aa31574ea68bfb1fed1dee46e5ec6 Mon Sep 17 00:00:00 2001 From: Ian Bridges Date: Mon, 13 Jul 2026 23:09:54 -0500 Subject: [PATCH 08/11] selinux: compute the IMA configuration settings string length once at boot selinux_ima_collect_state() builds a string of the current SELinux configuration settings. The string lists each setting as a name and one digit. The length of the string therefore never changes, but is still recomputed on every call. Add selinux_ima_config_len_init() to compute the length once during selinux_init(). Update selinux_ima_collect_state() to use the stored length. Suggested-by: Paul Moore Link: https://lore.kernel.org/r/df755e0282dab3b932d19aceab71b7d7@paul-moore.com Signed-off-by: Ian Bridges Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 3 +++ security/selinux/ima.c | 33 ++++++++++++++++++++++++--------- security/selinux/include/ima.h | 4 ++++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index f4a4edbff2bd..1ead2eee1944 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -107,6 +107,7 @@ #include "netlabel.h" #include "audit.h" #include "avc_ss.h" +#include "ima.h" #define SELINUX_INODE_INIT_XATTRS 1 @@ -7856,6 +7857,8 @@ static __init int selinux_init(void) hashtab_cache_init(); + selinux_ima_config_len_init(); + security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), &selinux_lsmid); diff --git a/security/selinux/ima.c b/security/selinux/ima.c index cb0efa2fc1ad..cda68122e032 100644 --- a/security/selinux/ima.c +++ b/security/selinux/ima.c @@ -13,6 +13,27 @@ #include "security.h" #include "ima.h" +static int selinux_ima_config_len __ro_after_init; + +/* + * selinux_ima_config_len_init - Compute the configuration settings string length + * + * The string is fixed text plus one digit per setting, so its length + * is known at boot. + */ +void __init selinux_ima_config_len_init(void) +{ + int buf_len, suffix_len, i; + + buf_len = strlen("initialized=0;enforcing=0;checkreqprot=0;") + 1; + suffix_len = strlen("=0;"); + + for (i = 0; i < __POLICYDB_CAP_MAX; i++) + buf_len += strlen(selinux_policycap_names[i]) + suffix_len; + + selinux_ima_config_len = buf_len; +} + /* * selinux_ima_collect_state - Read selinux configuration settings * @@ -23,19 +44,13 @@ static char *selinux_ima_collect_state(void) { struct seq_buf s; char *buf; - int buf_len, suffix_len, i; + int i; - buf_len = strlen("initialized=0;enforcing=0;checkreqprot=0;") + 1; - suffix_len = strlen("=0;"); - - for (i = 0; i < __POLICYDB_CAP_MAX; i++) - buf_len += strlen(selinux_policycap_names[i]) + suffix_len; - - buf = kzalloc(buf_len, GFP_KERNEL); + buf = kzalloc(selinux_ima_config_len, GFP_KERNEL); if (!buf) return NULL; - seq_buf_init(&s, buf, buf_len); + seq_buf_init(&s, buf, selinux_ima_config_len); seq_buf_printf(&s, "initialized=%d;enforcing=%d;checkreqprot=%d;", selinux_initialized(), enforcing_enabled(), diff --git a/security/selinux/include/ima.h b/security/selinux/include/ima.h index 38ab302f5946..d7d18f030d3d 100644 --- a/security/selinux/include/ima.h +++ b/security/selinux/include/ima.h @@ -14,9 +14,13 @@ #include "security.h" #ifdef CONFIG_IMA +void __init selinux_ima_config_len_init(void); extern void selinux_ima_measure_state(void); extern void selinux_ima_measure_state_locked(void); #else +static inline void selinux_ima_config_len_init(void) +{ +} static inline void selinux_ima_measure_state(void) { } From fd6e2388a3ea55e58cbbbef840c1d8aa2067dbb3 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Mon, 27 Jul 2026 10:44:38 -0400 Subject: [PATCH 09/11] selinux: validate constraint expression attr and op at load time read_cons_helper() validates the expression type and stack depth of each constraint node but leaves e->attr and e->op unchecked, so a policy with an invalid operator or attribute value is accepted at load and only detected when the constraint is evaluated. constraint_expr_eval() handles such unrecognized cases with BUG() so the first permission check that reaches such a node oopses in the context of the checking process or panics with panic_on_oops. Reject these expresssions when the policy is loaded, matching what the libsepol validator already does. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index ba1c9bd9e29f..c4a81bd31e05 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1332,6 +1332,27 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep, if (depth == (CEXPR_MAXDEPTH - 1)) return -EINVAL; depth++; + switch (e->attr) { + case CEXPR_USER: + case CEXPR_TYPE: + if (e->op != CEXPR_EQ && + e->op != CEXPR_NEQ) + return -EINVAL; + break; + case CEXPR_ROLE: + case CEXPR_L1L2: + case CEXPR_L1H2: + case CEXPR_H1L2: + case CEXPR_H1H2: + case CEXPR_L1H1: + case CEXPR_L2H2: + if (e->op < CEXPR_EQ || + e->op > CEXPR_INCOMP) + return -EINVAL; + break; + default: + return -EINVAL; + } break; case CEXPR_NAMES: if (!allowxtarget && (e->attr & CEXPR_XTARGET)) @@ -1339,6 +1360,20 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep, if (depth == (CEXPR_MAXDEPTH - 1)) return -EINVAL; depth++; + switch (e->attr & + ~(CEXPR_TARGET|CEXPR_XTARGET)) { + case CEXPR_USER: + case CEXPR_ROLE: + case CEXPR_TYPE: + break; + default: + return -EINVAL; + } + if ((e->attr & (CEXPR_TARGET|CEXPR_XTARGET)) == + (CEXPR_TARGET|CEXPR_XTARGET)) + return -EINVAL; + if (e->op != CEXPR_EQ && e->op != CEXPR_NEQ) + return -EINVAL; rc = ebitmap_read(&e->names, fp); if (rc) return rc; From d14b5d0e97fccd27974fedc03b903408872907fd Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 27 Jul 2026 20:30:58 -0500 Subject: [PATCH 10/11] selinux: reject a permission value exceeding the class permission count perm_read() bounds a permission value by SEL_VEC_MAX but never by the nprim of the owning class or common, which is taken verbatim from the policy image. security_get_permissions() then writes perms[value - 1] into an nprim-sized kcalloc() array, so a class declaring fewer permissions than its largest permission value drives an out-of-bounds heap write. The top-level symbol tables are validated this way; the nested per-class permission table is not. Reject a permission whose value exceeds nprim, which is already set when perm_read() runs. Well-formed policies are unaffected. Cc: stable@vger.kernel.org Fixes: 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley [PM: tweak comment for line length] Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index ead504a639e3..5ff4d095ec2e 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1175,6 +1175,9 @@ 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; rc = str_read(&key, GFP_KERNEL, fp, len); if (rc) From 9a82dcd98b6e6e11cfd162410967951f12152528 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 27 Jul 2026 20:30:59 -0500 Subject: [PATCH 11/11] selinux: reject a class permission count below its inherited common security_get_permissions() maps an inherited common's permissions into an array sized by the class's own permissions.nprim, but class_read() takes that nprim verbatim from the policy image and never checks that it covers the common. A class that inherits a common of N permissions while declaring a smaller nprim is accepted, and on load the common's permissions are written past the class-sized array -- an out-of-bounds heap write. Reject a class whose permission count is below its inherited common's. Well-formed policies, where the class count already includes the inherited permissions, are unaffected. Cc: stable@vger.kernel.org Fixes: 55fcf09b3fe4 ("selinux: add support for querying object classes and permissions from the running policy") Signed-off-by: Bryam Vargas Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 5ff4d095ec2e..69777e885ae7 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1422,6 +1422,18 @@ 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);