serge1 / ELFIO

ELFIO - ELF (Executable and Linkable Format) reader and producer implemented as a header only C++ library
http://serge1.github.io/ELFIO
MIT License
706 stars 152 forks source link

Out of Read at "elfio/elfio_section.hpp:289" #134

Closed fatihhcelik closed 4 months ago

fatihhcelik commented 5 months ago

Description:

While fuzzing the Elfio library, we discovered a crash due to an out-of-bounds read in the "load_data" function inside the "pstream->seekg" function. The crash occurs when reading an ELF file and calling the "load_data" function at line 289 in "elfio/elfio_section.hpp". In this function, the "pstream->seekg" method is passed a value larger than the file size, which is conveyed in "header.sh_offset", causing the program to crash.

As can be seen below, the "header.sh_offset" value undergoes a translation and is passed to "seekg" without size checking.

    bool load_data() const
    {
        Elf_Xword size = get_size();
        if ( nullptr == data && SHT_NULL != get_type() &&
             SHT_NOBITS != get_type() && size < get_stream_size() ) {
            data.reset( new ( std::nothrow ) char[size_t( size ) + 1] );
            if ( ( 0 != size ) && ( nullptr != data ) ) {
--->         pstream->seekg(( *translator )[( *convertor )( header.sh_offset )] );
                pstream->read( data.get(), size );
                if ( static_cast<Elf_Xword>( pstream->gcount() ) != size ) {
                    data = nullptr;
                    return false;
                }

Here is the backtrace of the crash:

(gdb) backtrace
#0  0x00007ffff7eac016 in std::istream::seekg(std::fpos<__mbstate_t>) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x000055555556bc88 in ELFIO::section_impl<ELFIO::Elf32_Shdr>::load_data (this=this@entry=0x555555790680) at ../../elfio/elfio_section.hpp:289
#2  0x000055555556bf7d in ELFIO::section_impl<ELFIO::Elf32_Shdr>::get_data (this=0x555555790680) at ../../elfio/elfio_section.hpp:128
#3  0x000055555555acc4 in ELFIO::symbol_section_accessor_template<ELFIO::section>::generic_get_symbol<ELFIO::Elf32_Sym> (other=<synthetic pointer>: <optimized out>, 
    section_index=<synthetic pointer>: <optimized out>, type=<synthetic pointer>: <optimized out>, bind=<synthetic pointer>: <optimized out>, size=<synthetic pointer>: <optimized out>, 
    value=<synthetic pointer>: <optimized out>, name="", index=0, this=0x7fffffffe140) at ../../elfio/elfio_symbols.hpp:444
#4  ELFIO::symbol_section_accessor_template<ELFIO::section>::get_symbol (other=<synthetic pointer>: <optimized out>, section_index=<synthetic pointer>: <optimized out>, 
    type=<synthetic pointer>: <optimized out>, bind=<synthetic pointer>: <optimized out>, size=<synthetic pointer>: <optimized out>, value=<synthetic pointer>: <optimized out>, name="", index=0, 
    this=0x7fffffffe140) at ../../elfio/elfio_symbols.hpp:79
#5  main (argc=<optimized out>, argv=<optimized out>) at tutorial.cpp:96
(gdb) b *ELFIO::section_impl<ELFIO::Elf32_Shdr>::load_data
Breakpoint 17 at 0x55555556b9b0: file ../../elfio/elfio_section.hpp, line 281.

Compiled with ASAN:

==3467348==ERROR: AddressSanitizer: SEGV on unknown address 0x00004b7fffe9 (pc 0x7f96eed57016 bp 0x000000000000 sp 0x7ffe45287340 T0)
==3467348==The signal is caused by a READ memory access.
    #0 0x7f96eed57016 in std::istream::seekg(std::fpos<__mbstate_t>) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x122016)
    #1 0x556801fe546d in ELFIO::section_impl<ELFIO::Elf32_Shdr>::load_data() const ../../elfio/elfio_section.hpp:289
    #2 0x556801fe5b67 in ELFIO::section_impl<ELFIO::Elf32_Shdr>::get_data() const ../../elfio/elfio_section.hpp:128
    #3 0x556801fb9954 in bool ELFIO::symbol_section_accessor_template<ELFIO::section>::generic_get_symbol<ELFIO::Elf32_Sym>(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, unsigned long&, unsigned long&, unsigned char&, unsigned char&, unsigned short&, unsigned char&) const ../../elfio/elfio_symbols.hpp:444
    #4 0x556801fb9954 in ELFIO::symbol_section_accessor_template<ELFIO::section>::get_symbol(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, unsigned long&, unsigned long&, unsigned char&, unsigned char&, unsigned short&, unsigned char&) const ../../elfio/elfio_symbols.hpp:79
    #5 0x556801fb9954 in main /home/ubuntu/targets/ELFIO/examples/tutorial/tutorial.cpp:96
    #6 0x7f96ee92ed8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #7 0x7f96ee92ee3f in __libc_start_main_impl ../csu/libc-start.c:392
    #8 0x556801fbbef4 in _start (/home/ubuntu/targets/ELFIO/examples/tutorial/tutorial+0xbef4)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib/x86_64-linux-gnu/libstdc++.so.6+0x122016) in std::istream::seekg(std::fpos<__mbstate_t>)
==3467348==ABORTING

Here is the Valgrind output:

...
==375246== 1 errors in context 19 of 19:
==375246== Invalid read of size 8
==375246==    at 0x4988013: std::istream::seekg(std::fpos<__mbstate_t>) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
==375246==    by 0x40CFA5: ELFIO::section_impl<ELFIO::Elf32_Shdr>::load_data() const (elfio/elfio_section.hpp:289)
==375246==    by 0x40C2D9: ELFIO::section_impl<ELFIO::Elf32_Shdr>::get_data() const (elfio/elfio_section.hpp:128)
==375246==    by 0x41308A: bool ELFIO::symbol_section_accessor_template<ELFIO::section>::generic_get_symbol<ELFIO::Elf32_Sym>(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, unsigned long&, unsigned long&, unsigned char&, unsigned char&, unsigned short&, unsigned char&) const (elfio/elfio_symbols.hpp:444)
==375246==    by 0x40480D: ELFIO::symbol_section_accessor_template<ELFIO::section>::get_symbol(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, unsigned long&, unsigned long&, unsigned char&, unsigned char&, unsigned short&, unsigned char&) const (elfio/elfio_symbols.hpp:79)
==375246==    by 0x403EA7: main (tutorial.cpp:96)
==375246==  Address 0x4dd9120 is 0 bytes inside a block of size 520 free'd
==375246==    at 0x484B8AF: operator delete(void*) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==375246==    by 0x40DE4E: std::default_delete<std::basic_ifstream<char, std::char_traits<char> > >::operator()(std::basic_ifstream<char, std::char_traits<char> >*) const (unique_ptr.h:85)
==375246==    by 0x40E622: std::__uniq_ptr_impl<std::basic_ifstream<char, std::char_traits<char> >, std::default_delete<std::basic_ifstream<char, std::char_traits<char> > > >::reset(std::basic_ifstream<char, std::char_traits<char> >*) (unique_ptr.h:182)
==375246==    by 0x40E42C: std::unique_ptr<std::basic_ifstream<char, std::char_traits<char> >, std::default_delete<std::basic_ifstream<char, std::char_traits<char> > > >::reset(std::basic_ifstream<char, std::char_traits<char> >*) (unique_ptr.h:456)
==375246==    by 0x4042F0: ELFIO::elfio::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) (elfio/elfio.hpp:149)
==375246==    by 0x40365D: main (tutorial.cpp:39)
==375246==  Block was alloc'd at
==375246==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==375246==    by 0x40DFA3: std::_MakeUniq<std::basic_ifstream<char, std::char_traits<char> > >::__single_object std::make_unique<std::basic_ifstream<char, std::char_traits<char> >>() (unique_ptr.h:962)
==375246==    by 0x4041E6: ELFIO::elfio::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) (elfio/elfio.hpp:140)
==375246==    by 0x40365D: main (tutorial.cpp:39)
==375246== 
==375246== ERROR SUMMARY: 19 errors from 19 contexts (suppressed: 0 from 0)

The elfio library does not check whether the values (header, size, offset etc.) it reads from an elf file exceed the file size. You can see some of the values below:

Screenshot 2024-02-29 at 23 02 36


We use the the "examples/tutorial/tutorial.cpp" file to test the library. Here is our crash file:

crash_elfio.zip

fatihhcelik commented 4 months ago

We did not have the opportunity to examine the crash in depth, but I think the seekg() function should not exceed the file size (Checks eofbit). Could the cause of this crash be a corrupted heap/stack? Also, the program does not crash every time it is run.

serge1 commented 4 months ago

Please give a try to commit a428b72.

Also, the program does not crash every time it is run.

I could not reproduce the crash with the provided file. So, please, confirm the fix. Thank you!

fatihhcelik commented 4 months ago

Thank you for your interest and solution. We could not trigger the same crash again with the files we had.

serge1 commented 4 months ago

Thank you for confirmation. I am closing the issue for now