|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH 16/22] libelf: check loops for running away
On 07/06/13 19:27, Ian Jackson wrote:
> Ensure that libelf does not have any loops which can run away
> indefinitely even if the input is bogus. (Grepped for \bfor, \bwhile
> and \bgoto in libelf and xc_dom_*loader*.c.)
>
> Changes needed:
> * elf_note_next uses the note's unchecked alleged length, which might
> wrap round. If it does, return ELF_MAX_PTRVAL (0xfff..fff) instead,
> which will be beyond the end of the section and so terminate the
> caller's loop. Also check that the returned psuedopointer is sane.
> * In various loops over section and program headers, check that the
> calculated header pointer is still within the image, and quit the
> loop if it isn't.
> * Some fixed limits to avoid potentially O(image_size^2) loops:
> - maximum length of strings: 4K (longer ones ignored totally)
> - maximum total number of ELF notes: 65536 (any more are ignored)
> * Check that the total program contents (text, data) we copy or
> initialise doesn't exceed twice the output image area size.
> * Remove an entirely useless loop from elf_xen_parse (!)
> * Replace a nested search loop in in xc_dom_load_elf_symtab in
> xc_dom_elfloader.c by a precomputation of a bitmap of referenced
> symtabs.
>
> We have not changed loops which might, in principle, iterate over the
> whole image - even if they might do so one byte at a time with a
> nontrivial access check function in the middle.
>
> This is part of the fix to a security issue, XSA-55.
>
> Signed-off-by: Ian Jackson <ian.jackson@xxxxxxxxxxxxx>
>
> v5: Fix regression due to wrong image size loop limit calculation.
> Check return value from xc_dom_malloc.
>
> v4: Fix regression due to misplacement of test in elf_shdr_by_name
> (uninitialised variable).
> Introduce fixed limits.
> Avoid O(size^2) loops.
> Check returned psuedopointer from elf_note_next is correct.
> A few style fixes.
>
> v3: Fix a whitespace error.
>
> v2: BUGFIX: elf_shdr_by_name, elf_note_next: Reject new <= old, not just <.
> elf_shdr_by_name: Change order of checks to be a bit clearer.
> elf_load_bsdsyms: shdr loop check, improve chance of brokenness detection.
> Style fixes.
> ---
> tools/libxc/xc_dom_elfloader.c | 33 ++++++++++++++++++------
> xen/common/libelf/libelf-dominfo.c | 48
> ++++++++++++++++++++++++------------
> xen/common/libelf/libelf-loader.c | 47 +++++++++++++++++++++++++++++++++-
> xen/common/libelf/libelf-tools.c | 28 +++++++++++++++++++-
> xen/include/xen/libelf.h | 3 ++
> 5 files changed, 130 insertions(+), 29 deletions(-)
>
> diff --git a/tools/libxc/xc_dom_elfloader.c b/tools/libxc/xc_dom_elfloader.c
> index eb2e3d2..81c2519 100644
> --- a/tools/libxc/xc_dom_elfloader.c
> +++ b/tools/libxc/xc_dom_elfloader.c
> @@ -28,6 +28,7 @@
>
> #include "xg_private.h"
> #include "xc_dom.h"
> +#include "xc_bitops.h"
>
> #define XEN_VER "xen-3.0"
>
> @@ -118,6 +119,7 @@ static elf_errorstatus xc_dom_load_elf_symtab(struct
> xc_dom_image *dom,
> ELF_PTRVAL_CHAR hdr;
> size_t size;
> unsigned h, count, type, i, tables = 0;
> + unsigned long *strtab_referenced = NULL;
>
> if ( elf_swap(elf) )
> {
> @@ -218,22 +220,35 @@ static elf_errorstatus xc_dom_load_elf_symtab(struct
> xc_dom_image *dom,
> symtab, maxaddr);
>
> count = elf_shdr_count(&syms);
> + /* elf_shdr_count guarantees that count is reasonable */
> +
> + strtab_referenced = xc_dom_malloc(dom, bitmap_size(count));
> + if ( strtab_referenced == NULL )
> + return -1;
> + bitmap_clear(strtab_referenced, count);
> + /* Note the symtabs @h linked to by any strtab @i. */
> + for ( i = 0; i < count; i++ )
> + {
> + shdr2 = elf_shdr_by_index(&syms, i);
> + if ( elf_uval(&syms, shdr2, sh_type) == SHT_SYMTAB )
> + {
> + h = elf_uval(&syms, shdr2, sh_link);
> + if (h < count)
> + set_bit(h, strtab_referenced);
> + }
> + }
> +
> for ( h = 0; h < count; h++ )
> {
> shdr = ELF_OBSOLETE_VOIDP_CAST elf_shdr_by_index(&syms, h);
> + if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
> + /* input has an insane section header count field */
> + break;
> type = elf_uval(&syms, shdr, sh_type);
> if ( type == SHT_STRTAB )
> {
> - /* Look for a strtab @i linked to symtab @h. */
> - for ( i = 0; i < count; i++ )
> - {
> - shdr2 = elf_shdr_by_index(&syms, i);
> - if ( (elf_uval(&syms, shdr2, sh_type) == SHT_SYMTAB) &&
> - (elf_uval(&syms, shdr2, sh_link) == h) )
> - break;
> - }
> /* Skip symtab @h if we found no corresponding strtab @i. */
> - if ( i == count )
> + if ( !test_bit(h, strtab_referenced) )
> {
> if ( elf_64bit(&syms) )
> elf_store_field(elf, shdr, e64.sh_offset, 0);
> diff --git a/xen/common/libelf/libelf-dominfo.c
> b/xen/common/libelf/libelf-dominfo.c
> index 0b07002..b0ba4d8 100644
> --- a/xen/common/libelf/libelf-dominfo.c
> +++ b/xen/common/libelf/libelf-dominfo.c
> @@ -221,7 +221,8 @@ elf_errorstatus elf_xen_parse_note(struct elf_binary *elf,
> static unsigned elf_xen_parse_notes(struct elf_binary *elf,
> struct elf_dom_parms *parms,
> ELF_PTRVAL_CONST_VOID start,
> - ELF_PTRVAL_CONST_VOID end)
> + ELF_PTRVAL_CONST_VOID end,
> + unsigned *total_note_count)
> {
> unsigned xen_elfnotes = 0;
> ELF_HANDLE_DECL(elf_note) note;
> @@ -233,6 +234,12 @@ static unsigned elf_xen_parse_notes(struct elf_binary
> *elf,
> ELF_HANDLE_PTRVAL(note) < parms->elf_note_end;
> note = elf_note_next(elf, note) )
> {
> + if ( *total_note_count >= ELF_MAX_TOTAL_NOTE_COUNT )
> + {
> + elf_mark_broken(elf, "too many ELF notes");
> + break;
> + }
> + (*total_note_count)++;
> note_name = elf_note_name(elf, note);
> if ( note_name == NULL )
> continue;
elf_note_name() will currently actually return note->desc if namesz is 0
or overflows.
Confusingly, the reference I am using
(http://www.skyfree.org/linux/references/ELF_Format.pdf) indicates that
namesz = 0 and name[0]='\0' is reserved for system use, yet in
contradition to the statement that padding is only required if
necessary. The arguably more-real libelf from elf-utils appears to
ignore the edge case listed above.
Unfortunately, it appears that very little in this loop is resilient to
stupidly-large namesz or descsz. the note_name == NULL check looks to do
the wrong thing if there "note" points to a malformed note.
> @@ -473,6 +480,7 @@ elf_errorstatus elf_xen_parse(struct elf_binary *elf,
> ELF_HANDLE_DECL(elf_phdr) phdr;
> unsigned xen_elfnotes = 0;
> unsigned i, count, more_notes;
> + unsigned total_note_count = 0;
>
> elf_memset_unchecked(parms, 0, sizeof(*parms));
> parms->virt_base = UNSET_ADDR;
> @@ -487,6 +495,13 @@ elf_errorstatus elf_xen_parse(struct elf_binary *elf,
> for ( i = 0; i < count; i++ )
> {
> phdr = elf_phdr_by_index(elf, i);
> + /*
> + * This test also arranges for the loop to terminate if the
> + * input file has a ridiculous value for the header count: The
> + * first putative header outside the input image will appear
> + * to have type 0 (since out-of-range accesses read as 0) and
> + * PT_NOTE != 0.
> + */
> if ( elf_uval(elf, phdr, p_type) != PT_NOTE )
> continue;
>
> @@ -499,7 +514,8 @@ elf_errorstatus elf_xen_parse(struct elf_binary *elf,
>
> more_notes = elf_xen_parse_notes(elf, parms,
> elf_segment_start(elf, phdr),
> - elf_segment_end(elf, phdr));
> + elf_segment_end(elf, phdr),
> + &total_note_count);
> if ( more_notes == ELF_NOTE_INVALID )
> return -1;
>
> @@ -517,12 +533,17 @@ elf_errorstatus elf_xen_parse(struct elf_binary *elf,
> {
> shdr = elf_shdr_by_index(elf, i);
>
> + /*
> + * See above re guarantee of loop termination.
> + * SHT_NOTE != 0.
> + */
> if ( elf_uval(elf, shdr, sh_type) != SHT_NOTE )
> continue;
>
> more_notes = elf_xen_parse_notes(elf, parms,
> elf_section_start(elf, shdr),
> - elf_section_end(elf, shdr));
> + elf_section_end(elf, shdr),
> + &total_note_count);
>
> if ( more_notes == ELF_NOTE_INVALID )
> return -1;
> @@ -540,20 +561,15 @@ elf_errorstatus elf_xen_parse(struct elf_binary *elf,
> */
> if ( xen_elfnotes == 0 )
> {
> - count = elf_shdr_count(elf);
> - for ( i = 0; i < count; i++ )
> + shdr = elf_shdr_by_name(elf, "__xen_guest");
> + if ( ELF_HANDLE_VALID(shdr) )
> {
> - shdr = elf_shdr_by_name(elf, "__xen_guest");
> - if ( ELF_HANDLE_VALID(shdr) )
> - {
> - parms->guest_info = elf_section_start(elf, shdr);
> - parms->elf_note_start = ELF_INVALID_PTRVAL;
> - parms->elf_note_end = ELF_INVALID_PTRVAL;
> - elf_msg(elf, "%s: __xen_guest: \"%s\"\n", __FUNCTION__,
> - elf_strfmt(elf, parms->guest_info));
> - elf_xen_parse_guest_info(elf, parms);
> - break;
> - }
> + parms->guest_info = elf_section_start(elf, shdr);
> + parms->elf_note_start = ELF_INVALID_PTRVAL;
> + parms->elf_note_end = ELF_INVALID_PTRVAL;
> + elf_msg(elf, "%s: __xen_guest: \"%s\"\n", __FUNCTION__,
> + elf_strfmt(elf, parms->guest_info));
> + elf_xen_parse_guest_info(elf, parms);
> }
> }
>
> diff --git a/xen/common/libelf/libelf-loader.c
> b/xen/common/libelf/libelf-loader.c
> index 937c99b..47957aa 100644
> --- a/xen/common/libelf/libelf-loader.c
> +++ b/xen/common/libelf/libelf-loader.c
> @@ -75,6 +75,9 @@ elf_errorstatus elf_init(struct elf_binary *elf, const char
> *image_input, size_t
> for ( i = 0; i < count; i++ )
> {
> shdr = elf_shdr_by_index(elf, i);
> + if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
> + /* input has an insane section header count field */
> + break;
> if ( elf_uval(elf, shdr, sh_type) != SHT_SYMTAB )
> continue;
> elf->sym_tab = shdr;
> @@ -170,6 +173,9 @@ void elf_parse_bsdsyms(struct elf_binary *elf, uint64_t
> pstart)
> for ( i = 0; i < elf_shdr_count(elf); i++ )
> {
> shdr = elf_shdr_by_index(elf, i);
> + if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
> + /* input has an insane section header count field */
> + break;
> type = elf_uval(elf, shdr, sh_type);
> if ( (type == SHT_STRTAB) || (type == SHT_SYMTAB) )
> sz = elf_round_up(elf, sz + elf_uval(elf, shdr, sh_size));
> @@ -224,6 +230,9 @@ do { \
>
> for ( i = 0; i < elf_shdr_count(elf); i++ )
> {
> + elf_ptrval old_shdr_p;
> + elf_ptrval new_shdr_p;
> +
> type = elf_uval(elf, shdr, sh_type);
> if ( (type == SHT_STRTAB) || (type == SHT_SYMTAB) )
> {
> @@ -235,8 +244,16 @@ do { \
> elf_hdr_elm(elf, shdr, sh_offset, maxva - symtab_addr);
> maxva = ELF_OBSOLETE_VOIDP_CAST elf_round_up(elf, (unsigned
> long)maxva + sz);
> }
> - shdr = ELF_MAKE_HANDLE(elf_shdr, ELF_HANDLE_PTRVAL(shdr) +
> - (unsigned long)elf_uval(elf, elf->ehdr,
> e_shentsize));
> + old_shdr_p = ELF_HANDLE_PTRVAL(shdr);
> + new_shdr_p = old_shdr_p + elf_uval(elf, elf->ehdr, e_shentsize);
> + if ( new_shdr_p <= old_shdr_p ) /* wrapped or stuck */
> + {
> + elf_mark_broken(elf, "bad section header length");
> + break;
> + }
> + if ( !elf_access_ok(elf, new_shdr_p, 1) ) /* outside image */
> + break;
> + shdr = ELF_MAKE_HANDLE(elf_shdr, new_shdr_p);
> }
>
> /* Write down the actual sym size. */
> @@ -256,6 +273,9 @@ void elf_parse_binary(struct elf_binary *elf)
> for ( i = 0; i < count; i++ )
> {
> phdr = elf_phdr_by_index(elf, i);
> + if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) )
> + /* input has an insane program header count field */
> + break;
> if ( !elf_phdr_is_loadable(elf, phdr) )
> continue;
> paddr = elf_uval(elf, phdr, p_paddr);
> @@ -278,11 +298,20 @@ elf_errorstatus elf_load_binary(struct elf_binary *elf)
> ELF_HANDLE_DECL(elf_phdr) phdr;
> uint64_t i, count, paddr, offset, filesz, memsz;
> ELF_PTRVAL_VOID dest;
> + /*
> + * Let bizarre ELFs write the output image up to twice; this
> + * calculation is just to ensure our copying loop is no worse than
> + * O(domain_size).
> + */
> + uint64_t remain_allow_copy = (uint64_t)elf->dest_size * 2;
>
> count = elf_uval(elf, elf->ehdr, e_phnum);
> for ( i = 0; i < count; i++ )
> {
> phdr = elf_phdr_by_index(elf, i);
> + if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(phdr), 1) )
> + /* input has an insane program header count field */
> + break;
> if ( !elf_phdr_is_loadable(elf, phdr) )
> continue;
> paddr = elf_uval(elf, phdr, p_paddr);
> @@ -290,6 +319,20 @@ elf_errorstatus elf_load_binary(struct elf_binary *elf)
> filesz = elf_uval(elf, phdr, p_filesz);
> memsz = elf_uval(elf, phdr, p_memsz);
> dest = elf_get_ptr(elf, paddr);
> +
> + /*
> + * We need to check that the input image doesn't have us copy
> + * the whole image zillions of times, as that could lead to
> + * O(n^2) time behaviour and possible DoS by a malicous ELF.
> + */
> + if ( remain_allow_copy < memsz )
> + {
> + elf_mark_broken(elf, "program segments total to more"
> + " than the input image size");
> + break;
> + }
> + remain_allow_copy -= memsz;
> +
> elf_msg(elf, "%s: phdr %" PRIu64 " at 0x%"ELF_PRPTRVAL" ->
> 0x%"ELF_PRPTRVAL"\n",
> __func__, i, dest, (ELF_PTRVAL_VOID)(dest + filesz));
> if ( elf_load_image(elf, dest, ELF_IMAGE_BASE(elf) + offset, filesz,
> memsz) != 0 )
> diff --git a/xen/common/libelf/libelf-tools.c
> b/xen/common/libelf/libelf-tools.c
> index 6543f33..ef13b0d 100644
> --- a/xen/common/libelf/libelf-tools.c
> +++ b/xen/common/libelf/libelf-tools.c
> @@ -131,7 +131,16 @@ uint64_t elf_round_up(struct elf_binary *elf, uint64_t
> addr)
>
> unsigned elf_shdr_count(struct elf_binary *elf)
> {
> - return elf_uval(elf, elf->ehdr, e_shnum);
> + unsigned count = elf_uval(elf, elf->ehdr, e_shnum);
> + uint64_t max = elf->size / sizeof(Elf32_Shdr);
> + if (max > ~(unsigned)0)
> + max = ~(unsigned)0; /* Xen doesn't have limits.h :-/ */
> + if (count > max)
> + {
> + elf_mark_broken(elf, "far too many section headers");
> + count = max;
> + }
> + return count;
> }
>
> unsigned elf_phdr_count(struct elf_binary *elf)
> @@ -149,6 +158,9 @@ ELF_HANDLE_DECL(elf_shdr) elf_shdr_by_name(struct
> elf_binary *elf, const char *n
> for ( i = 0; i < count; i++ )
> {
> shdr = elf_shdr_by_index(elf, i);
> + if ( !elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), 1) )
> + /* input has an insane section header count field */
> + break;
It occurs to me here that elf_access_ok appears to checks the range
[&shdr, &shdr+1] (&shdr relative to start of elf).
Should this not be be elf_access_ok(elf, ELF_HANDLE_PTRVAL(shdr), sizeof
*shdr) ?
(It is getting quite late so I might be completely confused on this point)
> sname = elf_section_name(elf, shdr);
> if ( sname && !strcmp(sname, name) )
> return shdr;
> @@ -204,6 +216,11 @@ 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 )
> + {
> + elf_mark_broken(elf, "excessively long string");
> + return NULL;
> + }
> }
> }
>
> @@ -327,7 +344,14 @@ ELF_HANDLE_DECL(elf_note) elf_note_next(struct
> elf_binary *elf, ELF_HANDLE_DECL(
> unsigned namesz = (elf_uval(elf, note, namesz) + 3) & ~3;
> unsigned descsz = (elf_uval(elf, note, descsz) + 3) & ~3;
>
> - return ELF_MAKE_HANDLE(elf_note, ELF_HANDLE_PTRVAL(note) + elf_size(elf,
> note) + namesz + descsz);
> + elf_ptrval ptrval = ELF_HANDLE_PTRVAL(note)
> + + elf_size(elf, note) + namesz + descsz;
> +
> + if ( ( ptrval <= ELF_HANDLE_PTRVAL(note) || /* wrapped or stuck */
> + !elf_access_ok(elf, ELF_HANDLE_PTRVAL(note), 1) ) )
> + ptrval = ELF_MAX_PTRVAL; /* terminate caller's loop */
> +
> + return ELF_MAKE_HANDLE(elf_note, ptrval);
The ptrval check is still suscpetable to a double overflow from
slightly-larger-than-half-UINT_MAX namesz and descsz. Also, the
rounding up at the top is susceptible to overflow.
> }
>
> /* ------------------------------------------------------------------------
> */
> diff --git a/xen/include/xen/libelf.h b/xen/include/xen/libelf.h
> index e4588de..4ca6b8f 100644
> --- a/xen/include/xen/libelf.h
> +++ b/xen/include/xen/libelf.h
> @@ -51,6 +51,9 @@ typedef void elf_log_callback(struct elf_binary*, void
> *caller_data,
>
> #endif
>
> +#define ELF_MAX_STRING_LENGTH 4096
> +#define ELF_MAX_TOTAL_NOTE_COUNT 65536
> +
With a view to stupidly-long elf notes, should probably have
ELF_MAX_NOTE_{NAME,DESC}_LENGTH here.
~Andrew
> /* ------------------------------------------------------------------------
> */
>
> /* Macros for accessing the input image and output area. */
> --
> 1.7.2.5
>
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |