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.
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: