ddiakopoulos / libnyquist

:microphone: Cross platform C++11 library for decoding audio (mp3, wav, ogg, opus, flac, etc)
BSD 2-Clause "Simplified" License
540 stars 66 forks source link

heap buffer overflow in VorbisDecoderInternal::readInternal #40

Open ekse opened 5 years ago

ekse commented 5 years ago

Hi,

I have discovered a security issue in the vorbis support code that is likely exploitable. How would prefer that I share the details with you?

Sébastien

ekse commented 5 years ago

heap overflow in VorbisDecoderInternal::readInternal

crash input: crash-7f190cd04b5fbf6f813db4447b5010e63867fe6a.ogg

For reference, the fuzzer can be found on my fuzzing branch. The provided sample also crashes the sample libnyquist-examples that is provided with libnyquist.

https://github.com/ekse/libnyquist/tree/fuzzing

Detailed analysis

libnyquist can write past the capacity of samples in AudioData. With the provided sample, this happens when totalFramesRead reaches the value 19840.

In VorbisDecoderInternal::readInternal, the code loops over the following code. The write overflow happens in d->samples[totalFramesRead] = buffer[ch][i].

            for (int i = 0; i < framesRead; ++i)
            {
                for(int ch = 0; ch < d->channelCount; ch++)
                {
                    d->samples[totalFramesRead] = buffer[ch][i];
                    totalFramesRead++;
                }
            }
        }

The size of samples is set in VorbisDecoderInternal::loadAudioData.

    auto totalSamples = size_t(getTotalSamples());

    d->samples.resize(totalSamples * d->channelCount);

getTotalSamples is defined as follows.

inline int64_t getTotalSamples() const { return int64_t(ov_pcm_total(const_cast<OggVorbis_File *>(fileHandle), -1)); }

In the crash sample, totalSamples is 9920, d->channelCount is 2, so samples is set to size 19840.

AddressSanitizer report:

==12481==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x631000027e00 at pc 0x000000822064 bp 0x7ffcb604acd0 sp 0x7ffcb604acc8
WRITE of size 4 at 0x631000027e00 thread T0
    #0 0x822063 in VorbisDecoderInternal::readInternal(unsigned long, unsigned long) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:105:49
    #1 0x820788 in VorbisDecoderInternal::loadAudioData(void*, ov_callbacks) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:248:14
    #2 0x81ef9a in VorbisDecoderInternal::VorbisDecoderInternal(nqr::AudioData*, std::vector<unsigned char, std::allocator<unsigned char> > const&) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:56:13
    #3 0x81ea87 in nqr::VorbisDecoder::LoadFromBuffer(nqr::AudioData*, std::vector<unsigned char, std::allocator<unsigned char> > const&) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:264:27
    #4 0x5347bc in nqr::NyquistIO::Load(nqr::AudioData*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<unsigned char, std::allocator<unsigned char> > const&) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/Common.cpp:133:22
    #5 0x52a6b3 in Fuzz_Decoder(unsigned char const*, unsigned long) /home/ekse/git/libnyquist/builds/Fuzzing/../../fuzzers/FuzzNyquist.cpp:20:12
    #6 0x52ad5b in LLVMFuzzerTestOneInput /home/ekse/git/libnyquist/builds/Fuzzing/../../fuzzers/FuzzNyquist.cpp:28:5
    #7 0x43231a in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x43231a)
    #8 0x424c5c in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x424c5c)
    #9 0x42a0e1 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x42a0e1)
    #10 0x44c702 in main (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x44c702)
    #11 0x7fadf79f1b6a in __libc_start_main /build/glibc-KRRWSm/glibc-2.29/csu/../csu/libc-start.c:308:16
    #12 0x423539 in _start (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x423539)

0x631000027e00 is located 0 bytes to the right of 79360-byte region [0x631000014800,0x631000027e00)
allocated by thread T0 here:
    #0 0x527512 in operator new(unsigned long) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x527512)
    #1 0x56ae67 in __gnu_cxx::new_allocator<float>::allocate(unsigned long, void const*) /usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/ext/new_allocator.h:111:27
    #2 0x56ad6c in std::allocator_traits<std::allocator<float> >::allocate(std::allocator<float>&, unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/alloc_traits.h:436:20
    #3 0x56a409 in std::_Vector_base<float, std::allocator<float> >::_M_allocate(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/stl_vector.h:296:20
    #4 0x5696b5 in std::vector<float, std::allocator<float> >::_M_default_append(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/vector.tcc:604:34
    #5 0x566e8c in std::vector<float, std::allocator<float> >::resize(unsigned long) /usr/bin/../lib/gcc/x86_64-linux-gnu/8/../../../../include/c++/8/bits/stl_vector.h:827:4
    #6 0x82076f in VorbisDecoderInternal::loadAudioData(void*, ov_callbacks) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:246:20
    #7 0x81ef9a in VorbisDecoderInternal::VorbisDecoderInternal(nqr::AudioData*, std::vector<unsigned char, std::allocator<unsigned char> > const&) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:56:13
    #8 0x81ea87 in nqr::VorbisDecoder::LoadFromBuffer(nqr::AudioData*, std::vector<unsigned char, std::allocator<unsigned char> > const&) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:264:27
    #9 0x5347bc in nqr::NyquistIO::Load(nqr::AudioData*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<unsigned char, std::allocator<unsigned char> > const&) /home/ekse/git/libnyquist/builds/Fuzzing/../../src/Common.cpp:133:22
    #10 0x52a6b3 in Fuzz_Decoder(unsigned char const*, unsigned long) /home/ekse/git/libnyquist/builds/Fuzzing/../../fuzzers/FuzzNyquist.cpp:20:12
    #11 0x52ad5b in LLVMFuzzerTestOneInput /home/ekse/git/libnyquist/builds/Fuzzing/../../fuzzers/FuzzNyquist.cpp:28:5
    #12 0x43231a in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x43231a)
    #13 0x424c5c in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x424c5c)
    #14 0x42a0e1 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x42a0e1)
    #15 0x44c702 in main (/home/ekse/git/libnyquist/builds/Fuzzing/fuzzers/FuzzNyquist+0x44c702)
    #16 0x7fadf79f1b6a in __libc_start_main /build/glibc-KRRWSm/glibc-2.29/csu/../csu/libc-start.c:308:16

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ekse/git/libnyquist/builds/Fuzzing/../../src/VorbisDecoder.cpp:105:49 in VorbisDecoderInternal::readInternal(unsigned long, unsigned long)
Shadow bytes around the buggy address:
  0x0c627fffcf70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c627fffcf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c627fffcf90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c627fffcfa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c627fffcfb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c627fffcfc0:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627fffcfd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627fffcfe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627fffcff0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627fffd000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627fffd010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==12481==ABORTING
ddiakopoulos commented 5 years ago

Thank you very much @ekse - i'll take a closer look at this soon.