Fun with Fuzzers: How I Discovered Three Vulnerabilities (Part 2 of 3)

This is the second in a three-part series that chronicles how I discovered three vulnerabilities over the course of a day. If you haven’t already, you can go back and read the first installment.

Introduction

I was surprised when american fuzzy lop (AFL) quickly found the first vulnerability in FlightCrew, so you can imagine my elation when it turned up the second in under two hours. This time, instead of a crash, AFL found that FlightCrew hung when it was provided with certain input. I fired up gdbtui and began to investigate.

AFL status

Vulnerability Analysis

Upon examining the backtrace, it quickly became apparent that the issue was not in FlightCrew itself, but within Zipios, a third-party library for handling ZIP files. FlightCrew includes a copy of the complete source code from Zipios v0.1.5.9.

In order to understand the root cause of this bug, a brief explanation of the ZIP archive structure is in order. ZIP archives consist of local file headers, file data, and the central directory. Additional metadata is also present but is not pertinent to this discussion.

For each file within the ZIP archive, the central directory contains a “central directory header.” The last field of a central directory header is an “offset” that points to the position of the file’s “local header.” Together, the central directory and local file headers contain the information necessary to extract file data from the ZIP archive.

In the fuzzed ZIP file, the “Offset of local header” field has been changed so that its value is larger than the size of the archive. We can see by examining the code that this leads to an infinite loop.

The central directory is read by the function ZipFile::readCentralDirectory(), which reads each central directory header. It then calls ZipFile::confirmLocalHeaders().

src/zipios/src/zipfile.cpp

bool ZipFile::confirmLocalHeaders( istream &_zipfile ) {
  Entries::const_iterator it ;
  ZipCDirEntry *ent ;
  int inconsistencies = 0 ;
  ZipLocalEntry zlh ;
  for ( it = _entries.begin() ; it != _entries.end() ; it++ ) {
    ent = static_cast< ZipCDirEntry * >( (*it).get()  ) ;
    _vs.vseekg( _zipfile, ent->getLocalHeaderOffset(), ios::beg ) ;
    _zipfile >> zlh ;
    if ( ! _zipfile || zlh != *ent ) {
      inconsistencies++ ;
      _zipfile.clear() ;
    }
  }
  return ! inconsistencies ;
}

The call to ent->getLocalHeaderoffset() returns the unchecked local header offset value. Because the local header offset is greater than the size of the file, the call to vseekg() on line 180 seeks to the end of the file, setting the eofbit error state flag for _zipfile. An overloaded >> operator is then called on line 181.

src/zipios/src/ziphead.cpp

std::istream& operator>> ( std::istream &is, ZipCDirEntry &zcdh ) {
  zcdh._valid = false ; // set to true upon successful completion.
  if ( ! is ) 
    return is ;

  if ( zcdh.signature != readUint32( is ) ) {
    // put stream in error state and return
    is.setstate ( std::ios::failbit ) ;
    return is ;
  }

src/zipios/zipios++/zipheadio.h

inline uint32 readUint32 ( istream &is ) {
  static const int buf_len = sizeof ( uint32 ) ;
  unsigned char buf [ buf_len ] ;
  int rsf = 0 ;
  while ( rsf < buf_len ) {
    is.read ( reinterpret_cast< char * >( buf ) + rsf, buf_len - rsf ) ;
    rsf += static_cast< int >( is.gcount () ) ;
  }
  return  ztohl ( buf ) ;
}

Because the previous call to vgseek() caused is to seek to the end of the file, the call to is.read() on line 83 of zipheadio.h fails. Since is is at the end of the file, there are no more bytes to read. The call to is.gcount() returns 0, and line 84 is, therefore, logically equivalent to rsf = rsf + 0. As neither rsf nor is ever changes, the while loop’s condition on line 82 will never be true, resulting in an infinite loop.

Security Impact

Users of the Zipios library (v0.1.5.9 and earlier) are vulnerable to a denial of service (DoS) attack. As this DoS is caused by an infinite loop, exploitation of this vulnerability can also lead to uncontrolled resource consumption.

The latest version of Zipios (v2.2.1.0) is not vulnerable. Other versions of Zipios have not been analyzed.

Remediation

This issue is resolved in v0.1.7 of Zipios. It is recommended that Zipios users upgrade to v0.1.7 or v2.2.0 if possible.

As of July 15, 2019, Ubuntu users have access to a patched version of Zipios v0.1.5.9, as well as a patched version of FlightCrew.

Other Zipios and FlightCrew users can apply the following patch and rebuild: https://sourceforge.net/p/zipios/code-git/ci/96e26640573410709bb863b8916a8216f4c6a546/tree/infinite_loop.patch

Disclosure Timeline

American fuzzy lop discovers that FlightCrew hangs when provided with certain malformed input due to a bug in the Zipios library.

Zipios upstream developers are notified via a secure channel. Upstream developers respond, stating that there is no support for v0.1.5.9 of the Zipios.

A patch is developed by Mike Salvatore and submitted to the upstream developers for review.

The patch is modified by Alexis Wilke and accepted upstream.

CVE-2019-13453 is assigned to this vulnerability by MITRE.

Further Reading

Part 3 of this series chronicles the next vulnerability that I discovered while analyzing FlightCrew.

CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-13453

Zipios announcement: https://sourceforge.net/p/zipios/news/2019/07/version-017-cve-/

ZIP file structure: