diff mbox

[3/8] libelf: loop safety: Call elf_iter_ok[_counted] in every loop

Message ID 1481298289-13546-4-git-send-email-ian.jackson@eu.citrix.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ian Jackson Dec. 9, 2016, 3:44 p.m. UTC
In every `for' or `while' loop, either call elf_iter_ok, or explain
why it's not necessary.  This is part of comprehensive defence against
out of control loops.

Signed-off-by: Ian Jackson <Ian.Jackson@eu.citrix.com>
---
 xen/common/libelf/libelf-dominfo.c | 22 +++++++++++++---------
 xen/common/libelf/libelf-loader.c  |  8 ++++----
 xen/common/libelf/libelf-tools.c   |  6 +++---
 3 files changed, 20 insertions(+), 16 deletions(-)

Comments

Jan Beulich Dec. 12, 2016, 3:12 p.m. UTC | #1
>>> On 09.12.16 at 16:44, <ian.jackson@eu.citrix.com> wrote:
> --- a/xen/common/libelf/libelf-dominfo.c
> +++ b/xen/common/libelf/libelf-dominfo.c
> @@ -43,10 +43,13 @@ elf_errorstatus elf_xen_parse_features(struct elf_binary *elf,
>      if ( features == NULL )
>          return 0;
>  
> -    for ( pos = 0; features[pos] != '\0'; pos += len )
> +    for ( pos = 0;
> +          elf_iter_ok_counted(elf, sizeof(feature)) &&
> +              features[pos] != '\0';

The two sides of the && want to align with one another.

> @@ -273,11 +276,12 @@ elf_errorstatus elf_xen_parse_guest_info(struct elf_binary *elf,
>  
>      h = parms->guest_info;
>  #define STAR(h) (elf_access_unsigned(elf, (h), 0, 1))
> -    while ( STAR(h) )
> +    while ( elf_iter_ok_counted(elf, sizeof(name) + sizeof(value)) &&
> +            STAR(h) )

So you're using libelf determined sizes here rather than image
properties. Perhaps that's not a big deal, but wouldn't it be more
reasonable to simply account the whole section's size just once?

> @@ -490,7 +490,7 @@ elf_errorstatus elf_load_binary(struct elf_binary *elf)
>      uint64_t remain_allow_copy = (uint64_t)elf->dest_size * 2;
>  
>      count = elf_phdr_count(elf);
> -    for ( i = 0; i < count; i++ )
> +    for ( i = 0; elf_iter_ok(elf) && i < count; i++ )

At the example of this, but likely applicable elsewhere - what use is
this check at this point in the series? Without peeking at later patches
it is not really clear how adding this makes any difference.

Overall I'm not entirely opposed to the approach, but I find these
extra checks rather unpleasant to have.

Jan
Ian Jackson Dec. 12, 2016, 3:38 p.m. UTC | #2
Thanks for your careful review.

Jan Beulich writes ("Re: [PATCH 3/8] libelf: loop safety: Call elf_iter_ok[_counted] in every loop"):
> On 09.12.16 at 16:44, <ian.jackson@eu.citrix.com> wrote:
> > +    for ( pos = 0;
> > +          elf_iter_ok_counted(elf, sizeof(feature)) &&
> > +              features[pos] != '\0';
> 
> The two sides of the && want to align with one another.

Sure, if you like it that way.

> > @@ -273,11 +276,12 @@ elf_errorstatus elf_xen_parse_guest_info(struct elf_binary *elf,
> >  
> >      h = parms->guest_info;
> >  #define STAR(h) (elf_access_unsigned(elf, (h), 0, 1))
> > -    while ( STAR(h) )
> > +    while ( elf_iter_ok_counted(elf, sizeof(name) + sizeof(value)) &&
> > +            STAR(h) )
> 
> So you're using libelf determined sizes here rather than image
> properties.

I'm not sure what you mean.  Each iteration of this loop contains some
calls to elf_memset_unchecked.  The rules say:

 *  - Stack local buffer variables containing information derived from
 *    the image (including structs, or byte buffers) must be
 *    completely zeroed, using elf_memset_unchecked (and an
 *    accompanying elf_iter_ok_counted) on entry to the function (or
 *    somewhere very obviously near there).

And by elf_*_unchecked:

 * Remember to call elf_iter_ok_counted nearby.

So the calls to elf_memset_unchecked, to zero name and value, imply
that there must be a call to elf_iter_ok_counted.  The count parameter
should be the actual work done.

There are two loops inside this outer loop.  They are textually but
not logically nested.  By the rules each of these loops needs a call
to elf_iter_ok, but: since they iterate over characters for name and
value respectively, they clearly do no more work than the memsets.

> Perhaps that's not a big deal, but wouldn't it be more
> reasonable to simply account the whole section's size just once?

You mean to call elf_iter_ok_counted on the size of the note section ?

But that would be wrong, because in principle almost each character in
the note section could cause a memset of all of name and a memset of
all of value.

Doing the iteration checks at a distance, and based on input image
properties rather than actual code flow, requires the kind of subtle
and complicated analysis I found too fragile.

> > @@ -490,7 +490,7 @@ elf_errorstatus elf_load_binary(struct elf_binary *elf)
> >      uint64_t remain_allow_copy = (uint64_t)elf->dest_size * 2;
> >  
> >      count = elf_phdr_count(elf);
> > -    for ( i = 0; i < count; i++ )
> > +    for ( i = 0; elf_iter_ok(elf) && i < count; i++ )
> 
> At the example of this, but likely applicable elsewhere - what use is
> this check at this point in the series? Without peeking at later patches
> it is not really clear how adding this makes any difference.
> 
> Overall I'm not entirely opposed to the approach, but I find these
> extra checks rather unpleasant to have.

I think you may need to read the big comment in patch 8.  I introduce
that at the end of the series because it doesn't become true before
then.

If you like I can move it to the front of the series, and introduce it
with a "may not be true" caveat which is later removed.

Ian.
Jan Beulich Dec. 12, 2016, 3:56 p.m. UTC | #3
>>> On 12.12.16 at 16:38, <ian.jackson@eu.citrix.com> wrote:
>> > @@ -273,11 +276,12 @@ elf_errorstatus elf_xen_parse_guest_info(struct elf_binary *elf,
>> >  
>> >      h = parms->guest_info;
>> >  #define STAR(h) (elf_access_unsigned(elf, (h), 0, 1))
>> > -    while ( STAR(h) )
>> > +    while ( elf_iter_ok_counted(elf, sizeof(name) + sizeof(value)) &&
>> > +            STAR(h) )
>> 
>> So you're using libelf determined sizes here rather than image
>> properties.
> 
> I'm not sure what you mean.  Each iteration of this loop contains some
> calls to elf_memset_unchecked.  The rules say:
> 
>  *  - Stack local buffer variables containing information derived from
>  *    the image (including structs, or byte buffers) must be
>  *    completely zeroed, using elf_memset_unchecked (and an
>  *    accompanying elf_iter_ok_counted) on entry to the function (or
>  *    somewhere very obviously near there).
> 
> And by elf_*_unchecked:
> 
>  * Remember to call elf_iter_ok_counted nearby.
> 
> So the calls to elf_memset_unchecked, to zero name and value, imply
> that there must be a call to elf_iter_ok_counted.  The count parameter
> should be the actual work done.

Hmm, if the rules say that, I'll then have to question the rules:
Shouldn't accounting be based on what the workload the image
causes us, instead of our own overhead?

>> Perhaps that's not a big deal, but wouldn't it be more
>> reasonable to simply account the whole section's size just once?
> 
> You mean to call elf_iter_ok_counted on the size of the note section ?
> 
> But that would be wrong, because in principle almost each character in
> the note section could cause a memset of all of name and a memset of
> all of value.

Well, as per above, different ways you and I think this should work
then, as it seems. I can see where you're coming from, but I'm not
convinced this is the right model when taking a client (image)
perspective.

>> > @@ -490,7 +490,7 @@ elf_errorstatus elf_load_binary(struct elf_binary *elf)
>> >      uint64_t remain_allow_copy = (uint64_t)elf->dest_size * 2;
>> >  
>> >      count = elf_phdr_count(elf);
>> > -    for ( i = 0; i < count; i++ )
>> > +    for ( i = 0; elf_iter_ok(elf) && i < count; i++ )
>> 
>> At the example of this, but likely applicable elsewhere - what use is
>> this check at this point in the series? Without peeking at later patches
>> it is not really clear how adding this makes any difference.
>> 
>> Overall I'm not entirely opposed to the approach, but I find these
>> extra checks rather unpleasant to have.
> 
> I think you may need to read the big comment in patch 8.  I introduce
> that at the end of the series because it doesn't become true before
> then.

Yeah, I still need to make it there (and maybe I should have looked
at that one first).

Jan
Ian Jackson Dec. 12, 2016, 4:02 p.m. UTC | #4
Jan Beulich writes ("Re: [PATCH 3/8] libelf: loop safety: Call elf_iter_ok[_counted] in every loop"):
> On 12.12.16 at 16:38, <ian.jackson@eu.citrix.com> wrote:
> > So the calls to elf_memset_unchecked, to zero name and value, imply
> > that there must be a call to elf_iter_ok_counted.  The count parameter
> > should be the actual work done.
> 
> Hmm, if the rules say that, I'll then have to question the rules:
> Shouldn't accounting be based on what the workload the image
> causes us, instead of our own overhead?

The purpose of the accounting is to prevent the image from causing us
to do lots of work.  The work calculation should be based on the
actual algorithm, not on some hypothetical other algorithm that might
be more efficient.

Otherwise if our algorithm is inefficient in some surprising way, when
faced with certain unusual images, that would be a DOS vulnerability.

I think it is easier to write these checks, in terms of the actual
work done, than attempt to construct a proof that the algorithm always
only does a reasonable amount of work.

Ian.
diff mbox

Patch

diff --git a/xen/common/libelf/libelf-dominfo.c b/xen/common/libelf/libelf-dominfo.c
index 7f4a6a0..b139e32 100644
--- a/xen/common/libelf/libelf-dominfo.c
+++ b/xen/common/libelf/libelf-dominfo.c
@@ -43,10 +43,13 @@  elf_errorstatus elf_xen_parse_features(struct elf_binary *elf,
     if ( features == NULL )
         return 0;
 
-    for ( pos = 0; features[pos] != '\0'; pos += len )
+    for ( pos = 0;
+          elf_iter_ok_counted(elf, sizeof(feature)) &&
+              features[pos] != '\0';
+          pos += len )
     {
         elf_memset_unchecked(feature, 0, sizeof(feature));
-        for ( len = 0;; len++ )
+        for ( len = 0;; len++ ) /* can't do more than sizeof(feature) */
         {
             if ( len >= sizeof(feature)-1 )
                 break;
@@ -60,7 +63,7 @@  elf_errorstatus elf_xen_parse_features(struct elf_binary *elf,
             feature[len] = features[pos + len];
         }
 
-        for ( i = 0; i < elf_xen_features; i++ )
+        for ( i = 0; elf_iter_ok(elf) && i < elf_xen_features; i++ )
         {
             if ( !elf_xen_feature_names[i] )
                 continue;
@@ -236,7 +239,7 @@  static unsigned elf_xen_parse_notes(struct elf_binary *elf,
     parms->elf_note_start = start;
     parms->elf_note_end   = end;
     for ( note = ELF_MAKE_HANDLE(elf_note, parms->elf_note_start);
-          ELF_HANDLE_PTRVAL(note) < parms->elf_note_end;
+          elf_iter_ok(elf) && ELF_HANDLE_PTRVAL(note) < parms->elf_note_end;
           note = elf_note_next(elf, note) )
     {
 #ifdef __XEN__
@@ -273,11 +276,12 @@  elf_errorstatus elf_xen_parse_guest_info(struct elf_binary *elf,
 
     h = parms->guest_info;
 #define STAR(h) (elf_access_unsigned(elf, (h), 0, 1))
-    while ( STAR(h) )
+    while ( elf_iter_ok_counted(elf, sizeof(name) + sizeof(value)) &&
+            STAR(h) )
     {
         elf_memset_unchecked(name, 0, sizeof(name));
         elf_memset_unchecked(value, 0, sizeof(value));
-        for ( len = 0;; len++, h++ )
+        for ( len = 0;; len++, h++ ) /* covered by iter_ok_counted above */
         {
             if ( len >= sizeof(name)-1 )
                 break;
@@ -291,7 +295,7 @@  elf_errorstatus elf_xen_parse_guest_info(struct elf_binary *elf,
             if ( STAR(h) == '=' )
             {
                 h++;
-                for ( len = 0;; len++, h++ )
+                for ( len = 0;; len++, h++ ) /* covered by iter_ok_counted */
                 {
                     if ( len >= sizeof(value)-1 )
                         break;
@@ -504,7 +508,7 @@  elf_errorstatus elf_xen_parse(struct elf_binary *elf,
 
     /* Find and parse elf notes. */
     count = elf_phdr_count(elf);
-    for ( i = 0; i < count; i++ )
+    for ( i = 0; elf_iter_ok(elf) && i < count; i++ )
     {
         phdr = elf_phdr_by_index(elf, i);
         if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) )
@@ -537,7 +541,7 @@  elf_errorstatus elf_xen_parse(struct elf_binary *elf,
     if ( xen_elfnotes == 0 )
     {
         count = elf_shdr_count(elf);
-        for ( i = 1; i < count; i++ )
+        for ( i = 1; elf_iter_ok(elf) && i < count; i++ )
         {
             shdr = elf_shdr_by_index(elf, i);
             if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
diff --git a/xen/common/libelf/libelf-loader.c b/xen/common/libelf/libelf-loader.c
index 00479af..68c9021 100644
--- a/xen/common/libelf/libelf-loader.c
+++ b/xen/common/libelf/libelf-loader.c
@@ -85,7 +85,7 @@  elf_errorstatus elf_init(struct elf_binary *elf, const char *image_input, size_t
 
     /* Find symbol table and symbol string table. */
     count = elf_shdr_count(elf);
-    for ( i = 1; i < count; i++ )
+    for ( i = 1; elf_iter_ok(elf) && i < count; i++ )
     {
         shdr = elf_shdr_by_index(elf, i);
         if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
@@ -425,7 +425,7 @@  do {                                                                \
      * NB: this _must_ be done one by one, and taking the bitness into account,
      * so that the guest can treat this as an array of type Elf{32/64}_Shdr.
      */
-    for ( i = 0; i < ELF_BSDSYM_SECTIONS; i++ )
+    for ( i = 0; elf_iter_ok(elf) && i < ELF_BSDSYM_SECTIONS; i++ )
     {
         rc = elf_load_image(elf, header_base + header_size + shdr_size * i,
                             ELF_REALPTR2PTRVAL(&header.elf_header.section[i]),
@@ -453,7 +453,7 @@  void elf_parse_binary(struct elf_binary *elf)
     unsigned i, count;
 
     count = elf_phdr_count(elf);
-    for ( i = 0; i < count; i++ )
+    for ( i = 0; elf_iter_ok(elf) && i < count; i++ )
     {
         phdr = elf_phdr_by_index(elf, i);
         if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) )
@@ -490,7 +490,7 @@  elf_errorstatus elf_load_binary(struct elf_binary *elf)
     uint64_t remain_allow_copy = (uint64_t)elf->dest_size * 2;
 
     count = elf_phdr_count(elf);
-    for ( i = 0; i < count; i++ )
+    for ( i = 0; elf_iter_ok(elf) && i < count; i++ )
     {
         phdr = elf_phdr_by_index(elf, i);
         if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) )
diff --git a/xen/common/libelf/libelf-tools.c b/xen/common/libelf/libelf-tools.c
index a9edb6a..56dab63 100644
--- a/xen/common/libelf/libelf-tools.c
+++ b/xen/common/libelf/libelf-tools.c
@@ -153,7 +153,7 @@  ELF_HANDLE_DECL(elf_shdr) elf_shdr_by_name(struct elf_binary *elf, const char *n
     ELF_HANDLE_DECL(elf_shdr) shdr;
     const char *sname;
 
-    for ( i = 1; i < count; i++ )
+    for ( i = 1; elf_iter_ok(elf) && i < count; i++ )
     {
         shdr = elf_shdr_by_index(elf, i);
         if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
@@ -214,7 +214,7 @@  const char *elf_strval(struct elf_binary *elf, elf_ptrval start)
         if ( !elf_access_unsigned(elf, start, length, 1) )
             /* ok */
             return ELF_UNSAFE_PTR(start);
-        if ( length >= ELF_MAX_STRING_LENGTH )
+        if ( !elf_iter_ok(elf) || length >= ELF_MAX_STRING_LENGTH )
         {
             elf_mark_broken(elf, "excessively long string");
             return NULL;
@@ -262,7 +262,7 @@  ELF_HANDLE_DECL(elf_sym) elf_sym_by_name(struct elf_binary *elf, const char *sym
     uint64_t info, name;
     const char *sym_name;
 
-    for ( ; ptr < end; ptr += elf_size(elf, sym) )
+    for ( ; elf_iter_ok(elf) && ptr < end; ptr += elf_size(elf, sym) )
     {
         sym = ELF_MAKE_HANDLE(elf_sym, ptr);
         info = elf_uval(elf, sym, st_info);