* [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating
@ 2026-01-22 16:06 James Carter
2026-01-22 16:06 ` [PATCH 2/3] libsepol: Check for an unset sensitivity in module_to_cil James Carter
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: James Carter @ 2026-01-22 16:06 UTC (permalink / raw)
To: selinux; +Cc: James Carter
Because of various corner cases, the checks for MLS semantic
levels and ranges and the checks for MLS levels and ranges do not
give an error when the value for the sensitivity is 0 (which occurs
because it has not been set).
The corner cases only apply to users and, even then, not to all
policy types, so user's MLS portions are not being checked as
strictly as possible. Range transitions are also not getting checked
as strictly as they could be either.
For user datums:
- Only check the MLS portions for MLS policies.
- Do not allow unset sensitivities in semantic levels and ranges for
a modular policy.
- Do not allow unset sensitivities in levels and ranges for a
kernel policy.
For range transitions:
- Never allow unset sensitivities.
Reported-by: oss-fuzz (issues 471456886 and 471525113)
Signed-off-by: James Carter <jwcart2@gmail.com>
---
libsepol/src/policydb_validate.c | 50 +++++++++++++++++---------------
1 file changed, 27 insertions(+), 23 deletions(-)
diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
index 5afbfa49..9ee71bf2 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -661,9 +661,9 @@ bad:
return -1;
}
-static int validate_mls_semantic_level(const mls_semantic_level_t *level, const validate_t *sens, const validate_t *cats)
+static int validate_mls_semantic_level(const mls_semantic_level_t *level, const validate_t *sens, const validate_t *cats, int allow_unset)
{
- if (level->sens == 0)
+ if (allow_unset && level->sens == 0)
return 0;
if (validate_value(level->sens, sens))
goto bad;
@@ -676,11 +676,11 @@ bad:
return -1;
}
-static int validate_mls_semantic_range(const mls_semantic_range_t *range, const validate_t *sens, const validate_t *cats)
+static int validate_mls_semantic_range(const mls_semantic_range_t *range, const validate_t *sens, const validate_t *cats, int allow_unset)
{
- if (validate_mls_semantic_level(&range->level[0], sens, cats))
+ if (validate_mls_semantic_level(&range->level[0], sens, cats, allow_unset))
goto bad;
- if (validate_mls_semantic_level(&range->level[1], sens, cats))
+ if (validate_mls_semantic_level(&range->level[1], sens, cats, allow_unset))
goto bad;
return 0;
@@ -689,9 +689,9 @@ bad:
return -1;
}
-static int validate_mls_level(const mls_level_t *level, const validate_t *sens, const validate_t *cats)
+static int validate_mls_level(const mls_level_t *level, const validate_t *sens, const validate_t *cats, int allow_unset)
{
- if (level->sens == 0)
+ if (allow_unset && level->sens == 0)
return 0;
if (validate_value(level->sens, sens))
goto bad;
@@ -712,7 +712,7 @@ static int validate_level_datum(sepol_handle_t *handle, const level_datum_t *lev
if (level->level->sens == 0)
goto bad;
- if (validate_mls_level(level->level, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
+ if (validate_mls_level(level->level, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
goto bad;
if (level->isalias) {
@@ -740,11 +740,11 @@ static int validate_level_datum_wrapper(__attribute__ ((unused)) hashtab_key_t k
return validate_level_datum(margs->handle, d, margs->flavors, margs->policy);
}
-static int validate_mls_range(const mls_range_t *range, const validate_t *sens, const validate_t *cats)
+static int validate_mls_range(const mls_range_t *range, const validate_t *sens, const validate_t *cats, int allow_unset)
{
- if (validate_mls_level(&range->level[0], sens, cats))
+ if (validate_mls_level(&range->level[0], sens, cats, allow_unset))
goto bad;
- if (validate_mls_level(&range->level[1], sens, cats))
+ if (validate_mls_level(&range->level[1], sens, cats, allow_unset))
goto bad;
return 0;
@@ -759,14 +759,19 @@ static int validate_user_datum(sepol_handle_t *handle, const user_datum_t *user,
goto bad;
if (validate_role_set(&user->roles, &flavors[SYM_ROLES]))
goto bad;
- if (validate_mls_semantic_range(&user->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
- goto bad;
- if (validate_mls_semantic_level(&user->dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
- goto bad;
- if (p->mls && p->policy_type != POLICY_MOD && validate_mls_range(&user->exp_range, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
- goto bad;
- if (p->mls && p->policy_type != POLICY_MOD && validate_mls_level(&user->exp_dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
- goto bad;
+ if (p->mls) {
+ int allow_unset = (p->policy_type != POLICY_MOD);
+ if (validate_mls_semantic_range(&user->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
+ goto bad;
+ if (validate_mls_semantic_level(&user->dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
+ goto bad;
+
+ allow_unset = (p->policy_type != POLICY_KERN);
+ if (validate_mls_range(&user->exp_range, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
+ goto bad;
+ if (validate_mls_level(&user->exp_dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
+ goto bad;
+ }
if (user->bounds && validate_value(user->bounds, &flavors[SYM_USERS]))
goto bad;
@@ -1323,7 +1328,7 @@ static int validate_context(const context_struct_t *con, validate_t flavors[], i
return -1;
if (validate_value(con->type, &flavors[SYM_TYPES]))
return -1;
- if (mls && validate_mls_range(&con->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
+ if (mls && validate_mls_range(&con->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
return -1;
return 0;
@@ -1484,7 +1489,7 @@ static int validate_range_trans_rules(sepol_handle_t *handle, const range_trans_
goto bad;
if (validate_ebitmap(&range_trans->tclasses, &flavors[SYM_CLASSES]))
goto bad;
- if (validate_mls_semantic_range(&range_trans->trange, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
+ if (validate_mls_semantic_range(&range_trans->trange, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
goto bad;
}
@@ -1663,8 +1668,7 @@ static int validate_range_transition(hashtab_key_t key, hashtab_datum_t data, vo
goto bad;
if (validate_value(rt->target_class, &flavors[SYM_CLASSES]))
goto bad;
-
- if (validate_mls_range(r, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
+ if (validate_mls_range(r, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
goto bad;
return 0;
--
2.52.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/3] libsepol: Check for an unset sensitivity in module_to_cil
2026-01-22 16:06 [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating James Carter
@ 2026-01-22 16:06 ` James Carter
2026-01-22 16:06 ` [PATCH 3/3] libsepol: Handled required users " James Carter
2026-01-26 1:48 ` [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating Jason Zaman
2 siblings, 0 replies; 4+ messages in thread
From: James Carter @ 2026-01-22 16:06 UTC (permalink / raw)
To: selinux; +Cc: James Carter
It is possible for a base policy to have sensitivities for user
levels and ranges that are not set if the user is used in a require
block.
Check for an unset sensitivity before using it to look up the
name of the sensitivity to prevent a memory access error.
Signed-off-by: James Carter <jwcart2@gmail.com>
---
libsepol/src/module_to_cil.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
index 41c2b051..56ebd9c8 100644
--- a/libsepol/src/module_to_cil.c
+++ b/libsepol/src/module_to_cil.c
@@ -520,6 +520,9 @@ static int semantic_level_to_cil(struct policydb *pdb, struct mls_semantic_level
{
struct mls_semantic_cat *cat;
+ if (level->sens == 0)
+ return -1;
+
cil_printf("(%s ", pdb->p_sens_val_to_name[level->sens - 1]);
if (level->cat != NULL) {
@@ -2499,6 +2502,9 @@ static int level_to_cil(struct policydb *pdb, struct mls_level *level)
{
struct ebitmap *map = &level->cat;
+ if (level->sens == 0)
+ return -1;
+
cil_printf("(%s", pdb->p_sens_val_to_name[level->sens - 1]);
if (!ebitmap_is_empty(map)) {
--
2.52.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 3/3] libsepol: Handled required users in module_to_cil
2026-01-22 16:06 [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating James Carter
2026-01-22 16:06 ` [PATCH 2/3] libsepol: Check for an unset sensitivity in module_to_cil James Carter
@ 2026-01-22 16:06 ` James Carter
2026-01-26 1:48 ` [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating Jason Zaman
2 siblings, 0 replies; 4+ messages in thread
From: James Carter @ 2026-01-22 16:06 UTC (permalink / raw)
To: selinux; +Cc: James Carter
In module_to_cil, required users are being treated like a
declared user. This causes a user that only appears in a require
block to be declared in the resulting CIL policy.
Instead, use the strategy used for types and roles and add the
required user to a userattribute rule. This will cause the
required user to be used and if it is not declared elsewhere the
optional block will be disabled.
Signed-off-by: James Carter <jwcart2@gmail.com>
---
libsepol/src/module_to_cil.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
index 56ebd9c8..89cd0b9b 100644
--- a/libsepol/src/module_to_cil.c
+++ b/libsepol/src/module_to_cil.c
@@ -2332,13 +2332,21 @@ static int user_to_cil(int indent, struct policydb *pdb, struct avrule_block *UN
struct ebitmap_node *node;
uint32_t i;
- if (scope == SCOPE_DECL) {
- cil_println(indent, "(user %s)", key);
- // object_r is implicit in checkmodule, but not with CIL, create it
- // as part of base
- cil_println(indent, "(userrole %s " DEFAULT_OBJECT ")", key);
+ if (scope == SCOPE_REQ) {
+ // if a user is in the REQ scope, then it could cause an
+ // optional block to fail, even if it is never used. However in CIL,
+ // symbols must be used in order to cause an optional block to fail. So
+ // for symbols in the REQ scope, add them to a userattribute as a way
+ // to 'use' them in the optional without affecting the resulting policy.
+ cil_println(indent, "(userattributeset " GEN_REQUIRE_ATTR " %s)", key);
+ return 0;
}
+ cil_println(indent, "(user %s)", key);
+ // object_r is implicit in checkmodule, but not with CIL, create it
+ // as part of base
+ cil_println(indent, "(userrole %s " DEFAULT_OBJECT ")", key);
+
ebitmap_for_each_positive_bit(&roles, node, i) {
cil_println(indent, "(userrole %s %s)", key, pdb->p_role_val_to_name[i]);
}
--
2.52.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating
2026-01-22 16:06 [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating James Carter
2026-01-22 16:06 ` [PATCH 2/3] libsepol: Check for an unset sensitivity in module_to_cil James Carter
2026-01-22 16:06 ` [PATCH 3/3] libsepol: Handled required users " James Carter
@ 2026-01-26 1:48 ` Jason Zaman
2 siblings, 0 replies; 4+ messages in thread
From: Jason Zaman @ 2026-01-26 1:48 UTC (permalink / raw)
To: James Carter; +Cc: selinux
On Thu, Jan 22, 2026 at 11:06:00AM -0500, James Carter wrote:
> Because of various corner cases, the checks for MLS semantic
> levels and ranges and the checks for MLS levels and ranges do not
> give an error when the value for the sensitivity is 0 (which occurs
> because it has not been set).
>
> The corner cases only apply to users and, even then, not to all
> policy types, so user's MLS portions are not being checked as
> strictly as possible. Range transitions are also not getting checked
> as strictly as they could be either.
>
> For user datums:
> - Only check the MLS portions for MLS policies.
> - Do not allow unset sensitivities in semantic levels and ranges for
> a modular policy.
> - Do not allow unset sensitivities in levels and ranges for a
> kernel policy.
>
> For range transitions:
> - Never allow unset sensitivities.
>
> Reported-by: oss-fuzz (issues 471456886 and 471525113)
> Signed-off-by: James Carter <jwcart2@gmail.com>
Signed-off-by: Jason Zaman <jason@perfinion.com>
Thanks,
Applied this whole series
> ---
> libsepol/src/policydb_validate.c | 50 +++++++++++++++++---------------
> 1 file changed, 27 insertions(+), 23 deletions(-)
>
> diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
> index 5afbfa49..9ee71bf2 100644
> --- a/libsepol/src/policydb_validate.c
> +++ b/libsepol/src/policydb_validate.c
> @@ -661,9 +661,9 @@ bad:
> return -1;
> }
>
> -static int validate_mls_semantic_level(const mls_semantic_level_t *level, const validate_t *sens, const validate_t *cats)
> +static int validate_mls_semantic_level(const mls_semantic_level_t *level, const validate_t *sens, const validate_t *cats, int allow_unset)
> {
> - if (level->sens == 0)
> + if (allow_unset && level->sens == 0)
> return 0;
> if (validate_value(level->sens, sens))
> goto bad;
> @@ -676,11 +676,11 @@ bad:
> return -1;
> }
>
> -static int validate_mls_semantic_range(const mls_semantic_range_t *range, const validate_t *sens, const validate_t *cats)
> +static int validate_mls_semantic_range(const mls_semantic_range_t *range, const validate_t *sens, const validate_t *cats, int allow_unset)
> {
> - if (validate_mls_semantic_level(&range->level[0], sens, cats))
> + if (validate_mls_semantic_level(&range->level[0], sens, cats, allow_unset))
> goto bad;
> - if (validate_mls_semantic_level(&range->level[1], sens, cats))
> + if (validate_mls_semantic_level(&range->level[1], sens, cats, allow_unset))
> goto bad;
>
> return 0;
> @@ -689,9 +689,9 @@ bad:
> return -1;
> }
>
> -static int validate_mls_level(const mls_level_t *level, const validate_t *sens, const validate_t *cats)
> +static int validate_mls_level(const mls_level_t *level, const validate_t *sens, const validate_t *cats, int allow_unset)
> {
> - if (level->sens == 0)
> + if (allow_unset && level->sens == 0)
> return 0;
> if (validate_value(level->sens, sens))
> goto bad;
> @@ -712,7 +712,7 @@ static int validate_level_datum(sepol_handle_t *handle, const level_datum_t *lev
> if (level->level->sens == 0)
> goto bad;
>
> - if (validate_mls_level(level->level, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> + if (validate_mls_level(level->level, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
> goto bad;
>
> if (level->isalias) {
> @@ -740,11 +740,11 @@ static int validate_level_datum_wrapper(__attribute__ ((unused)) hashtab_key_t k
> return validate_level_datum(margs->handle, d, margs->flavors, margs->policy);
> }
>
> -static int validate_mls_range(const mls_range_t *range, const validate_t *sens, const validate_t *cats)
> +static int validate_mls_range(const mls_range_t *range, const validate_t *sens, const validate_t *cats, int allow_unset)
> {
> - if (validate_mls_level(&range->level[0], sens, cats))
> + if (validate_mls_level(&range->level[0], sens, cats, allow_unset))
> goto bad;
> - if (validate_mls_level(&range->level[1], sens, cats))
> + if (validate_mls_level(&range->level[1], sens, cats, allow_unset))
> goto bad;
>
> return 0;
> @@ -759,14 +759,19 @@ static int validate_user_datum(sepol_handle_t *handle, const user_datum_t *user,
> goto bad;
> if (validate_role_set(&user->roles, &flavors[SYM_ROLES]))
> goto bad;
> - if (validate_mls_semantic_range(&user->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> - goto bad;
> - if (validate_mls_semantic_level(&user->dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> - goto bad;
> - if (p->mls && p->policy_type != POLICY_MOD && validate_mls_range(&user->exp_range, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> - goto bad;
> - if (p->mls && p->policy_type != POLICY_MOD && validate_mls_level(&user->exp_dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> - goto bad;
> + if (p->mls) {
> + int allow_unset = (p->policy_type != POLICY_MOD);
> + if (validate_mls_semantic_range(&user->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
> + goto bad;
> + if (validate_mls_semantic_level(&user->dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
> + goto bad;
> +
> + allow_unset = (p->policy_type != POLICY_KERN);
> + if (validate_mls_range(&user->exp_range, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
> + goto bad;
> + if (validate_mls_level(&user->exp_dfltlevel, &flavors[SYM_LEVELS], &flavors[SYM_CATS], allow_unset))
> + goto bad;
> + }
> if (user->bounds && validate_value(user->bounds, &flavors[SYM_USERS]))
> goto bad;
>
> @@ -1323,7 +1328,7 @@ static int validate_context(const context_struct_t *con, validate_t flavors[], i
> return -1;
> if (validate_value(con->type, &flavors[SYM_TYPES]))
> return -1;
> - if (mls && validate_mls_range(&con->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> + if (mls && validate_mls_range(&con->range, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
> return -1;
>
> return 0;
> @@ -1484,7 +1489,7 @@ static int validate_range_trans_rules(sepol_handle_t *handle, const range_trans_
> goto bad;
> if (validate_ebitmap(&range_trans->tclasses, &flavors[SYM_CLASSES]))
> goto bad;
> - if (validate_mls_semantic_range(&range_trans->trange, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> + if (validate_mls_semantic_range(&range_trans->trange, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
> goto bad;
> }
>
> @@ -1663,8 +1668,7 @@ static int validate_range_transition(hashtab_key_t key, hashtab_datum_t data, vo
> goto bad;
> if (validate_value(rt->target_class, &flavors[SYM_CLASSES]))
> goto bad;
> -
> - if (validate_mls_range(r, &flavors[SYM_LEVELS], &flavors[SYM_CATS]))
> + if (validate_mls_range(r, &flavors[SYM_LEVELS], &flavors[SYM_CATS], 0))
> goto bad;
>
> return 0;
> --
> 2.52.0
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-01-26 1:48 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-22 16:06 [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating James Carter
2026-01-22 16:06 ` [PATCH 2/3] libsepol: Check for an unset sensitivity in module_to_cil James Carter
2026-01-22 16:06 ` [PATCH 3/3] libsepol: Handled required users " James Carter
2026-01-26 1:48 ` [PATCH 1/3] libsepol: Tighten checks on MLS range and level when validating Jason Zaman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox