diff mbox series

checkpolicy, libsepol: Fix potential double free of mls_level_t

Message ID 20240213205605.830719-1-jwcart2@gmail.com (mailing list archive)
State Superseded
Delegated to: Petr Lautrbach
Headers show
Series checkpolicy, libsepol: Fix potential double free of mls_level_t | expand

Commit Message

James Carter Feb. 13, 2024, 8:56 p.m. UTC
In checkpolicy, sensitivities that have aliases will temporarily
share the mls_level_t structure until a level statement defines the
categories for the level and the alias is updated to have its own
mls_level_t structure. Currently, this does not cause a problem
because checkpolicy does very little clean-up before exiting when
an error is detected. But if the policydb is destroyed before exiting
due to an error after a sensitivity and its alias is declared, but
before a level statement involving either of them, then a double
free of the shared mls_level_t will occur.

The defined field of the level_datum_t is set after a level statement
is processed for the level_datum_t. This means that we know the alias
has its own mls_level_t if the defined field is set. This means that
the defined field can be used to determine whether or not the
mls_level_t pointed to by an alias level_datum_t should be destroyed.

Since the defined field is not set when reading or expanding a policy,
update libsepol to set the defined field.

Signed-off-by: James Carter <jwcart2@gmail.com>
---
 checkpolicy/policy_define.c | 11 +++++++----
 libsepol/src/expand.c       |  1 +
 libsepol/src/policydb.c     |  7 +++++--
 3 files changed, 13 insertions(+), 6 deletions(-)

Comments

Christian Göttsche Feb. 15, 2024, 6:41 p.m. UTC | #1
On Tue, 13 Feb 2024 at 21:56, James Carter <jwcart2@gmail.com> wrote:
>
> In checkpolicy, sensitivities that have aliases will temporarily
> share the mls_level_t structure until a level statement defines the
> categories for the level and the alias is updated to have its own
> mls_level_t structure. Currently, this does not cause a problem
> because checkpolicy does very little clean-up before exiting when
> an error is detected. But if the policydb is destroyed before exiting
> due to an error after a sensitivity and its alias is declared, but
> before a level statement involving either of them, then a double
> free of the shared mls_level_t will occur.
>
> The defined field of the level_datum_t is set after a level statement
> is processed for the level_datum_t. This means that we know the alias
> has its own mls_level_t if the defined field is set. This means that
> the defined field can be used to determine whether or not the
> mls_level_t pointed to by an alias level_datum_t should be destroyed.
>
> Since the defined field is not set when reading or expanding a policy,
> update libsepol to set the defined field.

I tried to avoid touching anything related to the `defined` member in
the checkpolicy patchset, since my plan was to remove the member in a
couple months, when the fuzzer has verified it is redundant after the
new member `copy` was introduced.
Currently the member `defined` is only checked once in the entire code
base: in a sanity check in checkpolicy that I never saw triggered.
So it is unused during binary policy parsing and CIL policy
compilation (and also unnecessary for correct cleanup there, since the
two active fuzzers have not found any related use-after-free issue or
leak).
Thus my preference is to have in the end only the `copy` member from
my patch 10/15, which does not need to be set everywhere manually
since the default calloc'ed value of 0 is the correct default, and
it's only going to be used in three places:
libsepol/src/policydb.c:sens_destroy(),
checkpolicy/policy_define.c:define_sens() and
checkpolicy/policy_define.c:clone_level().

> Signed-off-by: James Carter <jwcart2@gmail.com>
> ---
>  checkpolicy/policy_define.c | 11 +++++++----
>  libsepol/src/expand.c       |  1 +
>  libsepol/src/policydb.c     |  7 +++++--
>  3 files changed, 13 insertions(+), 6 deletions(-)
>
> diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> index 260e609d..542bb978 100644
> --- a/checkpolicy/policy_define.c
> +++ b/checkpolicy/policy_define.c
> @@ -1006,9 +1006,10 @@ static int clone_level(hashtab_key_t key __attribute__ ((unused)), hashtab_datum
>         mls_level_t *level = (mls_level_t *) arg, *newlevel;
>
>         if (levdatum->level == level) {
> -               levdatum->defined = 1;
> -               if (!levdatum->isalias)
> +               if (!levdatum->isalias) {
> +                       levdatum->defined = 1;
>                         return 0;
> +               }
>                 newlevel = (mls_level_t *) malloc(sizeof(mls_level_t));
>                 if (!newlevel)
>                         return -1;
> @@ -1017,6 +1018,7 @@ static int clone_level(hashtab_key_t key __attribute__ ((unused)), hashtab_datum
>                         return -1;
>                 }
>                 levdatum->level = newlevel;
> +               levdatum->defined = 1;
>         }
>         return 0;
>  }
> @@ -1057,8 +1059,6 @@ int define_level(void)
>         }
>         free(id);
>
> -       levdatum->defined = 1;
> -
>         while ((id = queue_remove(id_queue))) {
>                 cat_datum_t *cdatum;
>                 int range_start, range_end, i;
> @@ -1121,6 +1121,9 @@ int define_level(void)
>                 free(id);
>         }
>
> +       if (!levdatum->isalias)
> +               levdatum->defined = 1;
> +
>         if (hashtab_map
>             (policydbp->p_levels.table, clone_level, levdatum->level)) {
>                 yyerror("out of memory");
> diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> index e63414b1..0e16c502 100644
> --- a/libsepol/src/expand.c
> +++ b/libsepol/src/expand.c
> @@ -1191,6 +1191,7 @@ static int sens_copy_callback(hashtab_key_t key, hashtab_datum_t datum,
>                 goto out_of_mem;
>         }
>         new_level->isalias = level->isalias;
> +       new_level->defined = 1;
>         state->out->p_levels.nprim++;
>
>         if (hashtab_insert(state->out->p_levels.table,
> diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> index f10a8a95..0c950bf1 100644
> --- a/libsepol/src/policydb.c
> +++ b/libsepol/src/policydb.c
> @@ -1390,8 +1390,10 @@ static int sens_destroy(hashtab_key_t key, hashtab_datum_t datum, void *p
>         if (key)
>                 free(key);
>         levdatum = (level_datum_t *) datum;
> -       mls_level_destroy(levdatum->level);
> -       free(levdatum->level);
> +       if (!levdatum->isalias || levdatum->defined) {
> +               mls_level_destroy(levdatum->level);
> +               free(levdatum->level);
> +       }
>         level_datum_destroy(levdatum);
>         free(levdatum);
>         return 0;
> @@ -3357,6 +3359,7 @@ static int sens_read(policydb_t * p
>                 goto bad;
>
>         levdatum->isalias = le32_to_cpu(buf[1]);
> +       levdatum->defined = 1;
>
>         levdatum->level = malloc(sizeof(mls_level_t));
>         if (!levdatum->level || mls_read_level(levdatum->level, fp))
> --
> 2.43.0
>
James Carter Feb. 21, 2024, 9:12 p.m. UTC | #2
On Thu, Feb 15, 2024 at 1:41 PM Christian Göttsche
<cgzones@googlemail.com> wrote:
>
> On Tue, 13 Feb 2024 at 21:56, James Carter <jwcart2@gmail.com> wrote:
> >
> > In checkpolicy, sensitivities that have aliases will temporarily
> > share the mls_level_t structure until a level statement defines the
> > categories for the level and the alias is updated to have its own
> > mls_level_t structure. Currently, this does not cause a problem
> > because checkpolicy does very little clean-up before exiting when
> > an error is detected. But if the policydb is destroyed before exiting
> > due to an error after a sensitivity and its alias is declared, but
> > before a level statement involving either of them, then a double
> > free of the shared mls_level_t will occur.
> >
> > The defined field of the level_datum_t is set after a level statement
> > is processed for the level_datum_t. This means that we know the alias
> > has its own mls_level_t if the defined field is set. This means that
> > the defined field can be used to determine whether or not the
> > mls_level_t pointed to by an alias level_datum_t should be destroyed.
> >
> > Since the defined field is not set when reading or expanding a policy,
> > update libsepol to set the defined field.
>
> I tried to avoid touching anything related to the `defined` member in
> the checkpolicy patchset, since my plan was to remove the member in a
> couple months, when the fuzzer has verified it is redundant after the
> new member `copy` was introduced.
> Currently the member `defined` is only checked once in the entire code
> base: in a sanity check in checkpolicy that I never saw triggered.
> So it is unused during binary policy parsing and CIL policy
> compilation (and also unnecessary for correct cleanup there, since the
> two active fuzzers have not found any related use-after-free issue or
> leak).
> Thus my preference is to have in the end only the `copy` member from
> my patch 10/15, which does not need to be set everywhere manually
> since the default calloc'ed value of 0 is the correct default, and
> it's only going to be used in three places:
> libsepol/src/policydb.c:sens_destroy(),
> checkpolicy/policy_define.c:define_sens() and
> checkpolicy/policy_define.c:clone_level().
>

I like the idea of not having to set the value outside of checkpolicy.
I don't like the idea of having both a "defined" and a "copy" field in
the struct.
Checkpolicy needs the "defined" field to check if a sensitivity has a
level statement associated with it.

I sent v2 of the patch where I change the field to "notdefined". It is
a bit awkward, but it does mean that the "notdefined" field is never
set except for in checkpolicy.

Thanks for the comments,
Jim


> > Signed-off-by: James Carter <jwcart2@gmail.com>
> > ---
> >  checkpolicy/policy_define.c | 11 +++++++----
> >  libsepol/src/expand.c       |  1 +
> >  libsepol/src/policydb.c     |  7 +++++--
> >  3 files changed, 13 insertions(+), 6 deletions(-)
> >
> > diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
> > index 260e609d..542bb978 100644
> > --- a/checkpolicy/policy_define.c
> > +++ b/checkpolicy/policy_define.c
> > @@ -1006,9 +1006,10 @@ static int clone_level(hashtab_key_t key __attribute__ ((unused)), hashtab_datum
> >         mls_level_t *level = (mls_level_t *) arg, *newlevel;
> >
> >         if (levdatum->level == level) {
> > -               levdatum->defined = 1;
> > -               if (!levdatum->isalias)
> > +               if (!levdatum->isalias) {
> > +                       levdatum->defined = 1;
> >                         return 0;
> > +               }
> >                 newlevel = (mls_level_t *) malloc(sizeof(mls_level_t));
> >                 if (!newlevel)
> >                         return -1;
> > @@ -1017,6 +1018,7 @@ static int clone_level(hashtab_key_t key __attribute__ ((unused)), hashtab_datum
> >                         return -1;
> >                 }
> >                 levdatum->level = newlevel;
> > +               levdatum->defined = 1;
> >         }
> >         return 0;
> >  }
> > @@ -1057,8 +1059,6 @@ int define_level(void)
> >         }
> >         free(id);
> >
> > -       levdatum->defined = 1;
> > -
> >         while ((id = queue_remove(id_queue))) {
> >                 cat_datum_t *cdatum;
> >                 int range_start, range_end, i;
> > @@ -1121,6 +1121,9 @@ int define_level(void)
> >                 free(id);
> >         }
> >
> > +       if (!levdatum->isalias)
> > +               levdatum->defined = 1;
> > +
> >         if (hashtab_map
> >             (policydbp->p_levels.table, clone_level, levdatum->level)) {
> >                 yyerror("out of memory");
> > diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
> > index e63414b1..0e16c502 100644
> > --- a/libsepol/src/expand.c
> > +++ b/libsepol/src/expand.c
> > @@ -1191,6 +1191,7 @@ static int sens_copy_callback(hashtab_key_t key, hashtab_datum_t datum,
> >                 goto out_of_mem;
> >         }
> >         new_level->isalias = level->isalias;
> > +       new_level->defined = 1;
> >         state->out->p_levels.nprim++;
> >
> >         if (hashtab_insert(state->out->p_levels.table,
> > diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> > index f10a8a95..0c950bf1 100644
> > --- a/libsepol/src/policydb.c
> > +++ b/libsepol/src/policydb.c
> > @@ -1390,8 +1390,10 @@ static int sens_destroy(hashtab_key_t key, hashtab_datum_t datum, void *p
> >         if (key)
> >                 free(key);
> >         levdatum = (level_datum_t *) datum;
> > -       mls_level_destroy(levdatum->level);
> > -       free(levdatum->level);
> > +       if (!levdatum->isalias || levdatum->defined) {
> > +               mls_level_destroy(levdatum->level);
> > +               free(levdatum->level);
> > +       }
> >         level_datum_destroy(levdatum);
> >         free(levdatum);
> >         return 0;
> > @@ -3357,6 +3359,7 @@ static int sens_read(policydb_t * p
> >                 goto bad;
> >
> >         levdatum->isalias = le32_to_cpu(buf[1]);
> > +       levdatum->defined = 1;
> >
> >         levdatum->level = malloc(sizeof(mls_level_t));
> >         if (!levdatum->level || mls_read_level(levdatum->level, fp))
> > --
> > 2.43.0
> >
diff mbox series

Patch

diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
index 260e609d..542bb978 100644
--- a/checkpolicy/policy_define.c
+++ b/checkpolicy/policy_define.c
@@ -1006,9 +1006,10 @@  static int clone_level(hashtab_key_t key __attribute__ ((unused)), hashtab_datum
 	mls_level_t *level = (mls_level_t *) arg, *newlevel;
 
 	if (levdatum->level == level) {
-		levdatum->defined = 1;
-		if (!levdatum->isalias)
+		if (!levdatum->isalias) {
+			levdatum->defined = 1;
 			return 0;
+		}
 		newlevel = (mls_level_t *) malloc(sizeof(mls_level_t));
 		if (!newlevel)
 			return -1;
@@ -1017,6 +1018,7 @@  static int clone_level(hashtab_key_t key __attribute__ ((unused)), hashtab_datum
 			return -1;
 		}
 		levdatum->level = newlevel;
+		levdatum->defined = 1;
 	}
 	return 0;
 }
@@ -1057,8 +1059,6 @@  int define_level(void)
 	}
 	free(id);
 
-	levdatum->defined = 1;
-
 	while ((id = queue_remove(id_queue))) {
 		cat_datum_t *cdatum;
 		int range_start, range_end, i;
@@ -1121,6 +1121,9 @@  int define_level(void)
 		free(id);
 	}
 
+	if (!levdatum->isalias)
+		levdatum->defined = 1;
+
 	if (hashtab_map
 	    (policydbp->p_levels.table, clone_level, levdatum->level)) {
 		yyerror("out of memory");
diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
index e63414b1..0e16c502 100644
--- a/libsepol/src/expand.c
+++ b/libsepol/src/expand.c
@@ -1191,6 +1191,7 @@  static int sens_copy_callback(hashtab_key_t key, hashtab_datum_t datum,
 		goto out_of_mem;
 	}
 	new_level->isalias = level->isalias;
+	new_level->defined = 1;
 	state->out->p_levels.nprim++;
 
 	if (hashtab_insert(state->out->p_levels.table,
diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
index f10a8a95..0c950bf1 100644
--- a/libsepol/src/policydb.c
+++ b/libsepol/src/policydb.c
@@ -1390,8 +1390,10 @@  static int sens_destroy(hashtab_key_t key, hashtab_datum_t datum, void *p
 	if (key)
 		free(key);
 	levdatum = (level_datum_t *) datum;
-	mls_level_destroy(levdatum->level);
-	free(levdatum->level);
+	if (!levdatum->isalias || levdatum->defined) {
+		mls_level_destroy(levdatum->level);
+		free(levdatum->level);
+	}
 	level_datum_destroy(levdatum);
 	free(levdatum);
 	return 0;
@@ -3357,6 +3359,7 @@  static int sens_read(policydb_t * p
 		goto bad;
 
 	levdatum->isalias = le32_to_cpu(buf[1]);
+	levdatum->defined = 1;
 
 	levdatum->level = malloc(sizeof(mls_level_t));
 	if (!levdatum->level || mls_read_level(levdatum->level, fp))