Open-source Libraries Denial of Service Vulnerabilities Series

Introduction

In my recent research, I  investigated six open-source libraries for previously undiscovered vulnerabilities. I was surprised how I could uncover vulnerabilities in most of them. The vulnerabilities result in a DoS (Denial of Service) as worst impact and are rated “Moderately Critical”. This write-up provides a closer look at each of the vulnerabilities.

libarchive “lha_read_file_header_1()” Out-Of-Bounds Memory Access Denial of Service Vulnerability [CVE-2017-5601]

Summary

The vulnerability is caused due to an error in the “lha_read_file_header_1()” function (archive_read_support_format_lha.c), which can be exploited by malicious people to trigger an out-of-bounds read memory access via a specially crafted archive file. The vulnerability is confirmed in version 3.2.2 and has been fixed in version 3.3.1 [1].

Technical Details

The root cause of the problem resides in the way how LHA external sections of the input files are processed by the library. With a specially crafted archive file this can end up in the “lha->compsize” variable being wrongly set to a negative value. Let us successively track the computing of the value during processing a PoC file:

lha->compsize = archive_le32dec(p + H1_COMP_SIZE_OFFSET);     // lha->compsize is set directly from the input file, so we can easily set it to e.g. 4197884248
…
lha->compsize = archive_le64dec(extdheader);                  // lha->compsize is recalculated to 48
…
lha->compsize -= extdsize - 2;                                // since extdsize is bigger than lha->compsize, lha->compsize becomes a negative value; -86 in our case

This value is then propagated to the “lzh_br_fillup()” function as “strm->avail_in” variable. Because the function expects the value to be a positive number, this ends up with looping until one of the dereferences triggers an out-of-bounds read memory access:

for (;;) {
     const int x = n >> 3;
     if (strm->avail_in >= x) {
          switch (x) {
     …
     if (strm->avail_in == 0) {
          /* There is not enough compressed data to fill up the cache buffer. */
          return (0);
     }
     br->cache_buffer = (br->cache_buffer << 8) | *strm->next_in++;        // * crash *//
     strm->avail_in--;

The fix [2] is a test to make sure that after all the calculations the “lha->compsize” value remains positive. If otherwise, the file is considered invalid.

LibRAW “parse_tiff_ifd()” Memory Corruption Vulnerability [CVE-2017-6886]

Summary

The vulnerability is caused due to an error in the “parse_tiff_ifd()” function (internal/dcraw_common.cpp), which can be exploited by malicious people to corrupt memory. The vulnerability is confirmed in version 0.18.1 and has been fixed in version 0.18.2 [3].

Technical Details

Very straightforward vulnerability with an easy fix [4]. With a specially crafted image file it was possible to reach the following assignment with the “len” variable set to 0:

cbuf[len-1] = 0;

That of course causes the offset of “cbuf” to underflow and results in writing a ‘0’ to an illegal memory address. Since we cannot control where exactly the ‘0’ will be written, this typically results in a crash. To get a  better idea, these can be the two addresses – first with a ‘0’ offset (and so the address of the “cbuf” buffer) and the second one as the address with an offset set to ‘-1’ (pointing to a very distant address location):

&cbuf[len]   = 0x6083e0
&cbuf[len-1] = 0x1006083df

libsndfile “flac_buffer_copy()” Buffer Overflow Vulnerability [CVE-2017-7585]

Summary

The vulnerability is caused due to an error in the “flac_buffer_copy()” function (flac.c), which can be exploited by malicious people to cause a stack-based buffer overflow via a specially crafted FLAC file. The vulnerability is confirmed in version 1.0.27 and has been fixed in version 1.0.28 [5, 6].

Technical Details

Stack-based buffer overflow vulnerabilities are fun to research as there is often a potential for a code execution. In this case, it was even possible to trigger both – read and write memory access violations. The root cause of the out-of-bounds access is a loop, which, when processing a crafted file, doesn’t terminate. The reason for not terminating resides in the values of variables being used for termination. To be more precise, in the value of the “pflac->remain” variable.

Let’s see how the loop header looks like (flac.c):

for (i = 0 ; i < frame->header.blocksize && pflac->remain > 0 ; i++)
{
     …
     for (j = 0 ; j < frame->header.channels ; j++)
(a)     retpcm [offset + j] = buffer [j][pflac->bufferpos] * norm ;
     pflac->remain -= frame->header.channels ;
     pflac->bufferpos++ ;
     …
}

At the first sight, this looks fine. In every iteration, “i” is being incremented by 1 and “pflac->remain” is decremented by the value of “frame->header.channels”. There is one case, though, when this logic fails. With a crafted file, the “frame->header.channels” can be set to an even number, let’s say 2, and the “pflac->remain” to an odd number, let’s say 1023. With this set-up, “pflac->remain” will eventually become 1. Everything is still fine until the next iteration, when the value – being an “unsigned int” type – turns into a very large number and the loop continues and continues until the access violation happens on (a).

Let’s focus on the write memory access (lvalue of the (a) line) as that is obviously more interesting speaking of a potential code execution. The value being overwritten, “retpcm”, is a pointer to a stack-based buffer “ubuf” defined at the beginning of the “psf_calc_signal_max()” function (command.c). It was proven that it’s possible to overwrite the return address of this very function. What complicates the exploitation flow beyond a DoS is a way how to terminate the loop so it doesn’t write too far. There was a potential via the “frame->header.blocksize” variable, but this value appeared to be only very partially controllable and a reliable non-crashing test case wasn’t achieved.

libsndfile “header_read()” Buffer Overflow Vulnerability [CVE-2017-7586]

Note

By the time of reporting this vulnerability to the maintainer, the issue was already fixed in the GIT master branch as a part of other improvements. Based on that we didn’t claim credits for the finding and rather credited the maintainer, yet we reported it as the security impact wasn’t known before. It was also quite an interesting issue to explore as at the beginning it seemed there might be a potential for an interesting outcome. After in depth research we had to rule the code execution out, though.

Summary

The vulnerability is caused due to an error in the “header_read()” function (common.c) when handling ID3 tags, which can be exploited by malicious people to cause a stack-based buffer overflow via a specially crafted FLAC file. The vulnerability is confirmed in version 1.0.27 and has been fixed in version 1.0.28.

Technical Details

The overwrite happens during processing a FLAC audio file header containing the ID3 marking. When the library recognizes the “ID3” tag in the “guess_file_type()” function (sndfile.c), it continues with calculating the header offset in the “id3_skip()” function (id3.c). This is important to notice because during the ID3 tag skipping, “psf->fileoffset” is set to a value that is directly taken from the input file. The flow then continues up to the “header_seek()” function (SEEK_SET branch in common.c), where the values of “psf->headindex” and “psf->headend” are set:

  • “psf->headindex” is assigned the same value as psf->fileoffset
  • “psf->headend” is calculated from the input file and stores the number of remaining bytes

These values are then used in the “header_read()” function (common.c). The function divides the flow using 3 “if” conditions, while all of them utilize “psf->headindex”. Since we control the value, we can choose which path will be taken. To make it even more interesting, if the second condition is fulfilled, the flow continues to a “memcpy” call where the number of bytes is only to be computed:

if (psf->headindex + bytes > SIGNED_SIZEOF (psf->header))
{    int most ;
     most = SIGNED_SIZEOF (psf->header) - psf->headend ;
     psf_fread (psf->header + psf->headend, 1, most, psf) ;
     memcpy (ptr, psf->header + psf->headend, most) ;
     …
  • “SIGNED_SIZEOF (psf->header)” is a built-in value, set to “12292” (common.h)
  • “psf->headend” reflects the size of the file and so is controllable (well, partially, there are some limitations so we reach the right path, but the value can be set between 1 and 12k bytes)

The last piece of puzzle we need to put together is what and where we are going to write. In the “header_read()” function, we write to the “(void *) ptr” parameter and if we track this pointer down, we end up in the “guess_file_type()” function (sndfile.c) and its local “uint32_t buffer [3]” variable. The size of this variable is 12 bytes, which is not random but rather reflects the maximum number of bytes that would normally be written. For us, though, this is convenient because by overwriting this buffer we also overwrite the return address of the “guess_file_type()” function.

It all looks promising until the point we find out what kind of data will be written. As the data source serve bytes between the “psf->headend” and the value of “SIGNED_SIZEOF (psf->header)”. Since this very part of the memory is allocated using the “calloc” call (“sf_open()” function in the sndfile.c file) and cannot be later adjusted via the input file, we end up overwriting the memory with only zeros. The likely outcome is a crash of an application using the library.

FLAC “read_metadata_vorbiscomment_()” Memory Leak Denial of Service Vulnerability [CVE-2017-6888]

Summary

The vulnerability is caused due to an error in the “read_metadata_vorbiscomment_()” function (stream_decoder.c), which can be exploited by malicious people to cause a memory leak via a specially crafted FLAC file. The vulnerability is confirmed in version 1.3.2 and has been fixed in the official source code repository [7, 8].

Technical Details

This is the only vulnerability in this write-up that doesn’t directly result in a crash. Unlike the others, this vulnerability causes a memory leak, which, in a worst-case scenario, can lead to an exhaustion of all available memory. When the library processes a specially crafted FLAC file, a memory block allocated in stream_decoder.c is never freed:

if (0 == (obj->comments[i].entry = safe_malloc_add_2op_(obj->comments[i].length, /*+*/1))) {

The amount of memory being allocated is read directly from the file and so can lead to a partially arbitrary memory leak. It’s only “partial” because the amount is limited to a 24bit value, which can still end up leaking well over 16MB in just one run.

Conclusion

In conclusion, I’d like to emphasize an important aspect of finding and reporting vulnerabilities, and that is communication. Open-source software development is often very dynamic and fast paced and I can say that so was the communication with the maintainers. Unlike coordinating vulnerability disclosure with major vendors, which can be a slow process, we received a response within a couple of days and a fix was also provided very swiftly. We consider the expedited response and release of the fix very positive as it makes the products safer and significantly limits the exploitation possibilities for the “bad guys”.

References

[1] https://secuniaresearch.flexerasoftware.com/secunia_research/2017-3/

[2] https://github.com/libarchive/libarchive/commit/98dcbbf0bf4854bf987557e55e55fff7abbf3ea9

[3] https://secuniaresearch.flexerasoftware.com/secunia_research/2017-5/

[4] https://github.com/LibRaw/LibRaw/commit/d7c3d2cb460be10a3ea7b32e9443a83c243b2251

[5] https://secuniaresearch.flexerasoftware.com/secunia_research/2017-4/

[6] https://github.com/erikd/libsndfile/commit/60b234301adf258786d8b90be5c1d437fc8799e0

[7] https://secuniaresearch.flexerasoftware.com/secunia_research/2017-7/

[8] https://git.xiph.org/?p=flac.git;a=commit;h=4f47b63e9c971e6391590caf00a0f2a5ed612e67

Leave a Reply

Your email address will not be published. Required fields are marked *