Re: [PATCH 2/2] libsepol: rework saturation check

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Nov 1, 2023 at 12:39 PM Christian Göttsche
<cgzones@xxxxxxxxxxxxxx> wrote:
>
> Several values while parsing kernel policies, like symtab sizes or
> string lengths, are checked for saturation.  They may not be set to the
> maximum value, to avoid overflows or occupying a reserved value, and
> many of those sizes must not be 0.  This is currently handled via the
> two macros is_saturated() and zero_or_saturated().
>
> Both macros are tweaked for the fuzzer, because the fuzzer can create
> input with huge sizes.  While there is no subsequent data to provide
> the announced sizes, which will be caught later, memory of the requested
> size is allocated, which would lead to OOM reports.  Thus the sizes for
> the fuzzer are limited to 2^16.  This has the drawback of the fuzzer
> not checking the complete input space.
>
> Rework the saturation checks to actually check if there is enough data
> available for the announced size before allocating actual memory.
> This only works for input via mapped memory, since the remaining size
> for streams is not easily available.
>
> Application like setools do currently not benefit from this change,
> since they load the policy via a stream.  There are currently multiple
> interfaces to load a policy, so reworking them to use mapped memory by
> default might be subject for future work.
>
> Signed-off-by: Christian Göttsche <cgzones@xxxxxxxxxxxxxx>
> ---
>  libsepol/src/avtab.c         |  2 +-
>  libsepol/src/context.c       |  2 +-
>  libsepol/src/module_to_cil.c |  2 +-
>  libsepol/src/policydb.c      | 16 ++++++++--------
>  libsepol/src/private.h       | 22 ++++++++++++++++------
>  libsepol/src/services.c      |  2 +-
>  6 files changed, 28 insertions(+), 18 deletions(-)
>
> diff --git a/libsepol/src/avtab.c b/libsepol/src/avtab.c
> index 6ab49c5e..f379d8d8 100644
> --- a/libsepol/src/avtab.c
> +++ b/libsepol/src/avtab.c
> @@ -601,7 +601,7 @@ int avtab_read(avtab_t * a, struct policy_file *fp, uint32_t vers)
>                 goto bad;
>         }
>         nel = le32_to_cpu(buf[0]);
> -       if (!nel) {
> +       if (zero_or_saturated(nel, fp, sizeof(uint32_t) * 3)) {

I think that I would prefer zero_or_staturated() and is_saturated() to
be left alone and just add the additional check where it can be used.

Like:
if (zero_or_saturated(nel) || exceeds_available_bytes(nel, fp,
sizeof(unit32_t)*3))) {


>                 ERR(fp->handle, "table is empty");
>                 goto bad;
>         }
> diff --git a/libsepol/src/context.c b/libsepol/src/context.c
> index 5cc90afb..5ee21724 100644
> --- a/libsepol/src/context.c
> +++ b/libsepol/src/context.c
> @@ -297,7 +297,7 @@ int context_from_string(sepol_handle_t * handle,
>         char *con_cpy = NULL;
>         sepol_context_t *ctx_record = NULL;
>
> -       if (zero_or_saturated(con_str_len)) {
> +       if (con_str_len == 0 || con_str_len == SIZE_MAX) {

This doesn't need to change if my suggestion above is followed.

>                 ERR(handle, "Invalid context length");
>                 goto err;
>         }
> diff --git a/libsepol/src/module_to_cil.c b/libsepol/src/module_to_cil.c
> index d2868019..1d0a507c 100644
> --- a/libsepol/src/module_to_cil.c
> +++ b/libsepol/src/module_to_cil.c
> @@ -122,7 +122,7 @@ static int get_line(char **start, char *end, char **line)
>
>         for (len = 0; p < end && *p != '\n' && *p != '\0'; p++, len++);
>
> -       if (zero_or_saturated(len)) {
> +       if (len == 0 || len == SIZE_MAX) {

This wouldn't have to change either.

>                 rc = 0;
>                 goto exit;
>         }
> diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
> index f608aba4..00a986e8 100644
> --- a/libsepol/src/policydb.c
> +++ b/libsepol/src/policydb.c
> @@ -2854,7 +2854,7 @@ static int ocontext_read_xen(const struct policydb_compat_info *info,
>                                 if (rc < 0)
>                                         return -1;
>                                 c->sid[0] = le32_to_cpu(buf[0]);
> -                               if (is_saturated(c->sid[0]))
> +                               if (c->sid[0] > 127)

Where does the 127 come from? Practically, 127 is way more than we
would probably ever have, but I don't think there is a limit.

There might be a case to be made that we should set some reasonable
limits for things like initial sids.

>                                         return -1;
>                                 if (context_read_and_validate
>                                     (&c->context[0], p, fp))
> @@ -2960,7 +2960,7 @@ static int ocontext_read_selinux(const struct policydb_compat_info *info,
>                                 if (rc < 0)
>                                         return -1;
>                                 c->sid[0] = le32_to_cpu(buf[0]);
> -                               if (is_saturated(c->sid[0]))
> +                               if (c->sid[0] > 127)

Same comment here.

>                                         return -1;
>                                 if (context_read_and_validate
>                                     (&c->context[0], p, fp))
> @@ -3857,7 +3857,7 @@ static int scope_index_read(scope_index_t * scope_index,
>         if (rc < 0)
>                 return -1;
>         scope_index->class_perms_len = le32_to_cpu(buf[0]);
> -       if (is_saturated(scope_index->class_perms_len))
> +       if (is_saturated(scope_index->class_perms_len, fp, sizeof(uint32_t) * 3))

So:
if (is_saturated(scope_index->class_perms_len) ||
exceeds_available_bytes(scope_index->class_perms_len, fp,
sizeof(uint32_t) * 3)) {

>                 return -1;
>         if (scope_index->class_perms_len == 0) {
>                 scope_index->class_perms_map = NULL;
> @@ -3913,7 +3913,7 @@ static int avrule_decl_read(policydb_t * p, avrule_decl_t * decl,
>                 if (rc < 0)
>                         return -1;
>                 nprim = le32_to_cpu(buf[0]);
> -               if (is_saturated(nprim))
> +               if (nprim == UINT32_MAX)

This can then be unchanged.

>                         return -1;
>                 nel = le32_to_cpu(buf[1]);
>                 for (j = 0; j < nel; j++) {
> @@ -4036,7 +4036,7 @@ static int scope_read(policydb_t * p, int symnum, struct policy_file *fp)
>                 goto cleanup;
>         scope->scope = le32_to_cpu(buf[0]);
>         scope->decl_ids_len = le32_to_cpu(buf[1]);
> -       if (zero_or_saturated(scope->decl_ids_len)) {
> +       if (zero_or_saturated(scope->decl_ids_len, fp, sizeof(uint32_t))) {

Again:
if (zero_or_staturated(...) || exceeds_available_bytes()) {

And so on below.

Thanks,
Jim


>                 ERR(fp->handle, "invalid scope with no declaration");
>                 goto cleanup;
>         }
> @@ -4120,8 +4120,8 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
>  {
>
>         unsigned int i, j, r_policyvers;
> -       uint32_t buf[5];
> -       size_t len, nprim, nel;
> +       uint32_t buf[5], nprim;
> +       size_t len, nel;
>         char *policydb_str;
>         const struct policydb_compat_info *info;
>         unsigned int policy_type, bufindex;
> @@ -4315,7 +4315,7 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
>                 if (rc < 0)
>                         goto bad;
>                 nprim = le32_to_cpu(buf[0]);
> -               if (is_saturated(nprim))
> +               if (is_saturated(nprim, fp, sizeof(uint32_t) * 3))
>                         goto bad;
>                 nel = le32_to_cpu(buf[1]);
>                 if (nel && !nprim) {
> diff --git a/libsepol/src/private.h b/libsepol/src/private.h
> index 1833b497..d1fe66b6 100644
> --- a/libsepol/src/private.h
> +++ b/libsepol/src/private.h
> @@ -44,13 +44,23 @@
>
>  #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
>
> -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
> -# define is_saturated(x) (x == (typeof(x))-1 || (x) > (1U << 16))
> -#else
> -# define is_saturated(x) (x == (typeof(x))-1)
> -#endif
> +static inline int exceeds_available_bytes(size_t x, const struct policy_file *fp, size_t req_elem_size)
> +{
> +       size_t req_size;
> +
> +       /* Remaining input size is only available for mmap'ed memory */
> +       if (fp->type != PF_USE_MEMORY)
> +               return 0;
> +
> +       if (__builtin_mul_overflow(x, req_elem_size, &req_size))
> +               return 1;
> +
> +       return req_size > fp->len;
> +}
> +
> +#define is_saturated(x, fp, req_elem_size) ((x) == (typeof(x))-1 || exceeds_available_bytes(x, fp, req_elem_size))
>
> -#define zero_or_saturated(x) ((x == 0) || is_saturated(x))
> +#define zero_or_saturated(x, fp, req_elem_size) ((x) == 0 || is_saturated(x, fp, req_elem_size))
>
>  #define spaceship_cmp(a, b) (((a) > (b)) - ((a) < (b)))
>
> diff --git a/libsepol/src/services.c b/libsepol/src/services.c
> index 51bd56a0..f9280d89 100644
> --- a/libsepol/src/services.c
> +++ b/libsepol/src/services.c
> @@ -1748,7 +1748,7 @@ int str_read(char **strp, struct policy_file *fp, size_t len)
>         int rc;
>         char *str;
>
> -       if (zero_or_saturated(len)) {
> +       if (zero_or_saturated(len, fp, sizeof(char))) {
>                 errno = EINVAL;
>                 return -1;
>         }
> --
> 2.42.0
>




[Index of Archives]     [Selinux Refpolicy]     [Linux SGX]     [Fedora Users]     [Fedora Desktop]     [Yosemite Photos]     [Yosemite Camping]     [Yosemite Campsites]     [KDE Users]     [Gnome Users]

  Powered by Linux