diff mbox series

[1/6] libsepol/cil: fix out-of-bound read of a file context pattern ending with "\"

Message ID 20210314201651.474432-1-nicolas.iooss@m4x.org (mailing list archive)
State Superseded
Headers show
Series [1/6] libsepol/cil: fix out-of-bound read of a file context pattern ending with "\" | expand

Commit Message

Nicolas Iooss March 14, 2021, 8:16 p.m. UTC
OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying to
compile the following policy:

    (sid SID)
    (sidorder(SID))
    (filecon "\" any ())
    (filecon "" any ())

When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
terminator of the string. Fix this by returning when '\0' is read after
a backslash.

Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
---
 libsepol/cil/src/cil_post.c | 6 ++++++
 1 file changed, 6 insertions(+)

Comments

James Carter March 15, 2021, 9:02 p.m. UTC | #1
On Sun, Mar 14, 2021 at 4:23 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
>
> OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying to
> compile the following policy:
>
>     (sid SID)
>     (sidorder(SID))
>     (filecon "\" any ())
>     (filecon "" any ())
>
> When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
> terminator of the string. Fix this by returning when '\0' is read after
> a backslash.
>
> Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
> Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
> ---
>  libsepol/cil/src/cil_post.c | 6 ++++++
>  1 file changed, 6 insertions(+)
>
> diff --git a/libsepol/cil/src/cil_post.c b/libsepol/cil/src/cil_post.c
> index a55df1ea5bb0..5f9cf4efd242 100644
> --- a/libsepol/cil/src/cil_post.c
> +++ b/libsepol/cil/src/cil_post.c
> @@ -179,6 +179,12 @@ void cil_post_fc_fill_data(struct fc_data *fc, char *path)
>                         break;
>                 case '\\':
>                         c++;

The patch below is fine, but I can't figure out the reason for the
line above. I guess it means that fc->str_len++ will be skipped, but
if that is the purpose, it is not very clear. Does anyone know if this
is correct?

Jim


> +                       if (path[c] == '\0') {
> +                               if (!fc->meta) {
> +                                       fc->stem_len++;
> +                               }
> +                               return;
> +                       }
>                         /* FALLTHRU */
>                 default:
>                         if (!fc->meta) {
> --
> 2.30.2
>
Nicolas Iooss March 15, 2021, 9:34 p.m. UTC | #2
On Mon, Mar 15, 2021 at 10:02 PM James Carter <jwcart2@gmail.com> wrote:
>
> On Sun, Mar 14, 2021 at 4:23 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
> >
> > OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying to
> > compile the following policy:
> >
> >     (sid SID)
> >     (sidorder(SID))
> >     (filecon "\" any ())
> >     (filecon "" any ())
> >
> > When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
> > terminator of the string. Fix this by returning when '\0' is read after
> > a backslash.
> >
> > Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
> > Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
> > ---
> >  libsepol/cil/src/cil_post.c | 6 ++++++
> >  1 file changed, 6 insertions(+)
> >
> > diff --git a/libsepol/cil/src/cil_post.c b/libsepol/cil/src/cil_post.c
> > index a55df1ea5bb0..5f9cf4efd242 100644
> > --- a/libsepol/cil/src/cil_post.c
> > +++ b/libsepol/cil/src/cil_post.c
> > @@ -179,6 +179,12 @@ void cil_post_fc_fill_data(struct fc_data *fc, char *path)
> >                         break;
> >                 case '\\':
> >                         c++;
>
> The patch below is fine, but I can't figure out the reason for the
> line above. I guess it means that fc->str_len++ will be skipped, but
> if that is the purpose, it is not very clear. Does anyone know if this
> is correct?

Which line? "break;" ? In case you and/or other people are confused
about the code in cil_post_fc_fill_data, this "break;" exits the
switch(path[c]) block but still executes the lines right after
("fc->str_len++;" and "c++;"):

while (path[c] != '\0') {
    switch (path[c]) {
    case '.':
    /* ... */
    case '{':
        fc->meta = 1;
        break;
    case '\\':
        c++;
        /* FALLTHRU */
    default:
// This code is executed for every character before a special one
// (while "meta" is false)
// and "\c" counts as a single character, for c being anything.
        if (!fc->meta) {
            fc->stem_len++;
        }
        break;
    }
// These lines are executed for every character.
// "str_len" counts the number of unescaped characters
// ("\c" counts as a single character)
    fc->str_len++;
    c++;
}

In my opinion, the code looks correct, but this could be verified with
a new unit test which could computes str_len and stem_len for some
strings.

Cheers,
Nicolas
James Carter March 16, 2021, 1:34 p.m. UTC | #3
On Mon, Mar 15, 2021 at 5:34 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
>
> On Mon, Mar 15, 2021 at 10:02 PM James Carter <jwcart2@gmail.com> wrote:
> >
> > On Sun, Mar 14, 2021 at 4:23 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
> > >
> > > OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying to
> > > compile the following policy:
> > >
> > >     (sid SID)
> > >     (sidorder(SID))
> > >     (filecon "\" any ())
> > >     (filecon "" any ())
> > >
> > > When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
> > > terminator of the string. Fix this by returning when '\0' is read after
> > > a backslash.
> > >
> > > Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
> > > Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
> > > ---
> > >  libsepol/cil/src/cil_post.c | 6 ++++++
> > >  1 file changed, 6 insertions(+)
> > >
> > > diff --git a/libsepol/cil/src/cil_post.c b/libsepol/cil/src/cil_post.c
> > > index a55df1ea5bb0..5f9cf4efd242 100644
> > > --- a/libsepol/cil/src/cil_post.c
> > > +++ b/libsepol/cil/src/cil_post.c
> > > @@ -179,6 +179,12 @@ void cil_post_fc_fill_data(struct fc_data *fc, char *path)
> > >                         break;
> > >                 case '\\':
> > >                         c++;
> >
> > The patch below is fine, but I can't figure out the reason for the
> > line above. I guess it means that fc->str_len++ will be skipped, but
> > if that is the purpose, it is not very clear. Does anyone know if this
> > is correct?
>
> Which line? "break;" ? In case you and/or other people are confused
> about the code in cil_post_fc_fill_data, this "break;" exits the
> switch(path[c]) block but still executes the lines right after
> ("fc->str_len++;" and "c++;"):
>

Sorry, I wasn't very clear. I am wondering what the "c++" is doing
here because after the switch statement there is another "c++" (after
"fc->str_length++"), so this skips the character after the "/". Why
would one do that? My only thought is that maybe "/" is not supposed
to count towards the string length and the author thought not counting
the next character works just as well? Except, of course, it doesn't
if there is no next character.

Jim

> while (path[c] != '\0') {
>     switch (path[c]) {
>     case '.':
>     /* ... */
>     case '{':
>         fc->meta = 1;
>         break;
>     case '\\':
>         c++;
>         /* FALLTHRU */
>     default:
> // This code is executed for every character before a special one
> // (while "meta" is false)
> // and "\c" counts as a single character, for c being anything.
>         if (!fc->meta) {
>             fc->stem_len++;
>         }
>         break;
>     }
> // These lines are executed for every character.
> // "str_len" counts the number of unescaped characters
> // ("\c" counts as a single character)
>     fc->str_len++;
>     c++;
> }
>
> In my opinion, the code looks correct, but this could be verified with
> a new unit test which could computes str_len and stem_len for some
> strings.
>
> Cheers,
> Nicolas
>
Nicolas Iooss March 17, 2021, 7:45 a.m. UTC | #4
On Tue, Mar 16, 2021 at 2:34 PM James Carter <jwcart2@gmail.com> wrote:
>
> On Mon, Mar 15, 2021 at 5:34 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
> >
> > On Mon, Mar 15, 2021 at 10:02 PM James Carter <jwcart2@gmail.com> wrote:
> > >
> > > On Sun, Mar 14, 2021 at 4:23 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
> > > >
> > > > OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying to
> > > > compile the following policy:
> > > >
> > > >     (sid SID)
> > > >     (sidorder(SID))
> > > >     (filecon "\" any ())
> > > >     (filecon "" any ())
> > > >
> > > > When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
> > > > terminator of the string. Fix this by returning when '\0' is read after
> > > > a backslash.
> > > >
> > > > Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
> > > > Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
> > > > ---
> > > >  libsepol/cil/src/cil_post.c | 6 ++++++
> > > >  1 file changed, 6 insertions(+)
> > > >
> > > > diff --git a/libsepol/cil/src/cil_post.c b/libsepol/cil/src/cil_post.c
> > > > index a55df1ea5bb0..5f9cf4efd242 100644
> > > > --- a/libsepol/cil/src/cil_post.c
> > > > +++ b/libsepol/cil/src/cil_post.c
> > > > @@ -179,6 +179,12 @@ void cil_post_fc_fill_data(struct fc_data *fc, char *path)
> > > >                         break;
> > > >                 case '\\':
> > > >                         c++;
> > >
> > > The patch below is fine, but I can't figure out the reason for the
> > > line above. I guess it means that fc->str_len++ will be skipped, but
> > > if that is the purpose, it is not very clear. Does anyone know if this
> > > is correct?
> >
> > Which line? "break;" ? In case you and/or other people are confused
> > about the code in cil_post_fc_fill_data, this "break;" exits the
> > switch(path[c]) block but still executes the lines right after
> > ("fc->str_len++;" and "c++;"):
> >
>
> Sorry, I wasn't very clear. I am wondering what the "c++" is doing
> here because after the switch statement there is another "c++" (after
> "fc->str_length++"), so this skips the character after the "/". Why
> would one do that? My only thought is that maybe "/" is not supposed
> to count towards the string length and the author thought not counting
> the next character works just as well? Except, of course, it doesn't
> if there is no next character.

The matched character is a backslash ("\"), not a slash ("/"). I
understand that this implementation of "c++; without fc->str_len++;
nor fc->stem_len++;" is there in order to count sequences such as
"\(", "\.", "\["... as a single character. More precisely, when for
example the two-character sequence "\." is encountered in a path
(which happens often, as it is the way to escape dots in file context
patterns):

* c is increased twice ("c++;" in present twice in the while loop), in
order to go to the character next to the sequence ;
* fc->str_len is increased once (this sequence counts as a single
non-special character) ;
* if fc->meta is false (i.e. if no meta character such as ".", "(",
"["... has been encountered yet), fc->stem_len is increased once (this
sequence counts as a single non-special character in the "stem" of the
path)

The code I added in my patch made fc->stem_len increase when a path
ends with "\" (the character after the backslash character is a NUL
string terminator), before exiting cil_post_fc_fill_data. Now I am
wondering whether fc->str_len should also be increased, in order to
"count the backslash". In fact, finishing a path pattern with an
incomplete escape sequence is weird and I do not precisely know the
semantic of the length counters in such a case. What do you think?

Nicolas

> > while (path[c] != '\0') {
> >     switch (path[c]) {
> >     case '.':
> >     /* ... */
> >     case '{':
> >         fc->meta = 1;
> >         break;
> >     case '\\':
> >         c++;
> >         /* FALLTHRU */
> >     default:
> > // This code is executed for every character before a special one
> > // (while "meta" is false)
> > // and "\c" counts as a single character, for c being anything.
> >         if (!fc->meta) {
> >             fc->stem_len++;
> >         }
> >         break;
> >     }
> > // These lines are executed for every character.
> > // "str_len" counts the number of unescaped characters
> > // ("\c" counts as a single character)
> >     fc->str_len++;
> >     c++;
> > }
> >
> > In my opinion, the code looks correct, but this could be verified with
> > a new unit test which could computes str_len and stem_len for some
> > strings.
> >
> > Cheers,
> > Nicolas
> >
James Carter March 17, 2021, 2:35 p.m. UTC | #5
On Wed, Mar 17, 2021 at 3:45 AM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
>
> On Tue, Mar 16, 2021 at 2:34 PM James Carter <jwcart2@gmail.com> wrote:
> >
> > On Mon, Mar 15, 2021 at 5:34 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
> > >
> > > On Mon, Mar 15, 2021 at 10:02 PM James Carter <jwcart2@gmail.com> wrote:
> > > >
> > > > On Sun, Mar 14, 2021 at 4:23 PM Nicolas Iooss <nicolas.iooss@m4x.org> wrote:
> > > > >
> > > > > OSS-Fuzz found a Heap-buffer-overflow in the CIL compiler when trying to
> > > > > compile the following policy:
> > > > >
> > > > >     (sid SID)
> > > > >     (sidorder(SID))
> > > > >     (filecon "\" any ())
> > > > >     (filecon "" any ())
> > > > >
> > > > > When cil_post_fc_fill_data() processes "\", it goes beyond the NUL
> > > > > terminator of the string. Fix this by returning when '\0' is read after
> > > > > a backslash.
> > > > >
> > > > > Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28484
> > > > > Signed-off-by: Nicolas Iooss <nicolas.iooss@m4x.org>
> > > > > ---
> > > > >  libsepol/cil/src/cil_post.c | 6 ++++++
> > > > >  1 file changed, 6 insertions(+)
> > > > >
> > > > > diff --git a/libsepol/cil/src/cil_post.c b/libsepol/cil/src/cil_post.c
> > > > > index a55df1ea5bb0..5f9cf4efd242 100644
> > > > > --- a/libsepol/cil/src/cil_post.c
> > > > > +++ b/libsepol/cil/src/cil_post.c
> > > > > @@ -179,6 +179,12 @@ void cil_post_fc_fill_data(struct fc_data *fc, char *path)
> > > > >                         break;
> > > > >                 case '\\':
> > > > >                         c++;
> > > >
> > > > The patch below is fine, but I can't figure out the reason for the
> > > > line above. I guess it means that fc->str_len++ will be skipped, but
> > > > if that is the purpose, it is not very clear. Does anyone know if this
> > > > is correct?
> > >
> > > Which line? "break;" ? In case you and/or other people are confused
> > > about the code in cil_post_fc_fill_data, this "break;" exits the
> > > switch(path[c]) block but still executes the lines right after
> > > ("fc->str_len++;" and "c++;"):
> > >
> >
> > Sorry, I wasn't very clear. I am wondering what the "c++" is doing
> > here because after the switch statement there is another "c++" (after
> > "fc->str_length++"), so this skips the character after the "/". Why
> > would one do that? My only thought is that maybe "/" is not supposed
> > to count towards the string length and the author thought not counting
> > the next character works just as well? Except, of course, it doesn't
> > if there is no next character.
>
> The matched character is a backslash ("\"), not a slash ("/"). I

I am an idiot. The code makes a lot more sense now. You only want to
count an escaped character as one character.

> understand that this implementation of "c++; without fc->str_len++;
> nor fc->stem_len++;" is there in order to count sequences such as
> "\(", "\.", "\["... as a single character. More precisely, when for
> example the two-character sequence "\." is encountered in a path
> (which happens often, as it is the way to escape dots in file context
> patterns):
>
> * c is increased twice ("c++;" in present twice in the while loop), in
> order to go to the character next to the sequence ;
> * fc->str_len is increased once (this sequence counts as a single
> non-special character) ;
> * if fc->meta is false (i.e. if no meta character such as ".", "(",
> "["... has been encountered yet), fc->stem_len is increased once (this
> sequence counts as a single non-special character in the "stem" of the
> path)
>
> The code I added in my patch made fc->stem_len increase when a path
> ends with "\" (the character after the backslash character is a NUL
> string terminator), before exiting cil_post_fc_fill_data. Now I am
> wondering whether fc->str_len should also be increased, in order to
> "count the backslash". In fact, finishing a path pattern with an
> incomplete escape sequence is weird and I do not precisely know the
> semantic of the length counters in such a case. What do you think?
>

I think that it is an invalid path, so maybe I need to add some sort
of verification to the path at some point.

I finally found the source of this code in
refpolicy/support/fc_sort.py where the function compute_diffdata() is
very similar. That function would increment str_len in this case, so
to be consistent with that fc->str_len should also be incremented.

Jim


> Nicolas
>
> > > while (path[c] != '\0') {
> > >     switch (path[c]) {
> > >     case '.':
> > >     /* ... */
> > >     case '{':
> > >         fc->meta = 1;
> > >         break;
> > >     case '\\':
> > >         c++;
> > >         /* FALLTHRU */
> > >     default:
> > > // This code is executed for every character before a special one
> > > // (while "meta" is false)
> > > // and "\c" counts as a single character, for c being anything.
> > >         if (!fc->meta) {
> > >             fc->stem_len++;
> > >         }
> > >         break;
> > >     }
> > > // These lines are executed for every character.
> > > // "str_len" counts the number of unescaped characters
> > > // ("\c" counts as a single character)
> > >     fc->str_len++;
> > >     c++;
> > > }
> > >
> > > In my opinion, the code looks correct, but this could be verified with
> > > a new unit test which could computes str_len and stem_len for some
> > > strings.
> > >
> > > Cheers,
> > > Nicolas
> > >
>
diff mbox series

Patch

diff --git a/libsepol/cil/src/cil_post.c b/libsepol/cil/src/cil_post.c
index a55df1ea5bb0..5f9cf4efd242 100644
--- a/libsepol/cil/src/cil_post.c
+++ b/libsepol/cil/src/cil_post.c
@@ -179,6 +179,12 @@  void cil_post_fc_fill_data(struct fc_data *fc, char *path)
 			break;
 		case '\\':
 			c++;
+			if (path[c] == '\0') {
+				if (!fc->meta) {
+					fc->stem_len++;
+				}
+				return;
+			}
 			/* FALLTHRU */
 		default:
 			if (!fc->meta) {