Talos Vulnerability Report

TALOS-2016-0152

Libarchive 7zip read_SubStreamsInfo Code Execution Vulnerability

June 19, 2016
CVE Number

CVE-2016-4300

SUMMARY

An exploitable \heap overflow vulnerability exists in the 7zip read_SubStreamsInfo functionality of libarchive. A specially crafted 7zip file can cause a integer overflow resulting in memory corruption that can lead to code execution. An attacker can send a malformed file to trigger this vulnerability.

TESTED VERSIONS

libarchive 3.1.2

PRODUCT URLs

https://github.com/libarchive/libarchive

CVSSv3 SCORE

7.8 - CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

DETAILS

Vulnerable code exists in 7zip support format module: libarchive\archivereadsupportformat7zip.c:

(...)
#define UMAX_ENTRY  ARCHIVE_LITERAL_ULL(100000000)
(...)
Line 2129   static int
Line 2130   read_SubStreamsInfo(struct archive_read *a, struct _7z_substream_info *ss,
Line 2131       struct _7z_folder *f, size_t numFolders)
Line 2132   {
Line 2133       const unsigned char *p;
Line 2134       uint64_t *usizes;
Line 2135       size_t unpack_streams;
Line 2136       int type;
Line 2137       unsigned i;
Line 2138       uint32_t numDigests;
(...)
Line 2149   if (type == kNumUnPackStream) {
Line 2150       unpack_streams = 0;
Line 2151       for (i = 0; i < numFolders; i++) {
Line 2152           if (parse_7zip_uint64(a, &(f[i].numUnpackStreams)) < 0)
Line 2153               return (-1);
Line 2154           if (UMAX_ENTRY < f[i].numUnpackStreams)
Line 2155               return (-1);
Line 2156           unpack_streams += (size_t)f[i].numUnpackStreams;     // <---- INTEGER OVERFLOW
Line 2157       }
Line 2158       if ((p = header_bytes(a, 1)) == NULL)
Line 2159           return (-1);
Line 2160       type = *p;
Line 2161   } else
Line 2162       unpack_streams = numFolders;
Line 2163
Line 2164   ss->unpack_streams = unpack_streams;
Line 2165   if (unpack_streams) {
Line 2166       ss->unpackSizes = calloc(unpack_streams,                // <----- ALLOCATION BASED ON OVERFLOWED INT
Line 2167           sizeof(*ss->unpackSizes));
Line 2168       ss->digestsDefined = calloc(unpack_streams,
Line 2169           sizeof(*ss->digestsDefined));
Line 2170       ss->digests = calloc(unpack_streams,
Line 2171           sizeof(*ss->digests));
Line 2172       if (ss->unpackSizes == NULL || ss->digestsDefined == NULL ||
Line 2173           ss->digests == NULL)
Line 2174           return (-1);
Line 2175   }
Line 2176
Line 2177   usizes = ss->unpackSizes;
Line 2178   for (i = 0; i < numFolders; i++) {
Line 2179       unsigned pack;
Line 2180       uint64_t sum;
Line 2181
Line 2182       if (f[i].numUnpackStreams == 0)
Line 2183           continue;
Line 2184
Line 2185       sum = 0;
Line 2186       if (type == kSize) {
Line 2187           for (pack = 1; pack < f[i].numUnpackStreams; pack++) {
Line 2188               if (parse_7zip_uint64(a, usizes) < 0)                // <--- BUFFER OVERFLOW
Line 2189                   return (-1);
Line 2190               sum += *usizes++;
Line 2191           }
Line 2192       }
Line 2193       *usizes++ = folder_uncompressed_size(&f[i]) - sum;
Line 2194   }

In lines 2149-2157 we can see that for all "folders" is calculated sum of "numUnpackStreams" which result is stored in "unpackstreams" variable. This variable is sizet what means that on x86 platform will be 32bit unsigned int. Maxium value of of "numUnpackStreams" is equal to UMAXENTRY. To overflow "unpackstreams" variable we need to have 7zip file with number of folders "numFolders" bigger than 42 and "numUnpackStreams" with sufficient values.

The overflowed value is also used as size parameter in calloc in lines 2166-2171. Interesting buffer allocated there for us is "ss->unpackSizes". Later in lines 2187-2194 based on "numFolders" and "numUnpackStreams" of each folder, a 64bit unsigned integer (maximum) is read from the file and stored into buffer "usizes" which is indeed "ss->unpackSizes" ( line 2177 ) causing a heap based buffer overflow after some iterations. At this point the attacker controls the length and contents of the memory corruption.

CRASH INFORMATION

valgrind output:

==33167== Memcheck, a memory error detector
==33167== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==33167== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==33167== 
==33167== Invalid write of size 4
==33167==    at 0x805A459: parse_7zip_uint64 (archive_read_support_format_7zip.c:1666)
==33167==    by 0x805B9C0: read_SubStreamsInfo (archive_read_support_format_7zip.c:2191)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd28 is 0 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid write of size 4
==33167==    at 0x805A45F: parse_7zip_uint64 (archive_read_support_format_7zip.c:1666)
==33167==    by 0x805B9C0: read_SubStreamsInfo (archive_read_support_format_7zip.c:2191)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd2c is 4 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid read of size 4
==33167==    at 0x805A512: parse_7zip_uint64 (archive_read_support_format_7zip.c:1675)
==33167==    by 0x805B9C0: read_SubStreamsInfo (archive_read_support_format_7zip.c:2191)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd28 is 0 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid read of size 4
==33167==    at 0x805A514: parse_7zip_uint64 (archive_read_support_format_7zip.c:1675)
==33167==    by 0x805B9C0: read_SubStreamsInfo (archive_read_support_format_7zip.c:2191)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd2c is 4 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid write of size 4
==33167==    at 0x805A540: parse_7zip_uint64 (archive_read_support_format_7zip.c:1675)
==33167==    by 0x805B9C0: read_SubStreamsInfo (archive_read_support_format_7zip.c:2191)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd28 is 0 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid write of size 4
==33167==    at 0x805A542: parse_7zip_uint64 (archive_read_support_format_7zip.c:1675)
==33167==    by 0x805B9C0: read_SubStreamsInfo (archive_read_support_format_7zip.c:2191)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd2c is 4 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid read of size 4
==33167==    at 0x805B9D8: read_SubStreamsInfo (archive_read_support_format_7zip.c:2193)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd2c is 4 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
==33167== Invalid read of size 4
==33167==    at 0x805B9DB: read_SubStreamsInfo (archive_read_support_format_7zip.c:2193)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167==  Address 0x445cd28 is 0 bytes after a block of size 8 alloc'd
==33167==    at 0x402C109: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==33167==    by 0x805B8FA: read_SubStreamsInfo (archive_read_support_format_7zip.c:2169)
==33167==    by 0x805BF42: read_StreamsInfo (archive_read_support_format_7zip.c:2320)
==33167==    by 0x805C0ED: read_Header (archive_read_support_format_7zip.c:2390)
==33167==    by 0x805D5DE: slurp_central_directory (archive_read_support_format_7zip.c:2927)
==33167==    by 0x805834F: archive_read_format_7zip_read_header (archive_read_support_format_7zip.c:640)
==33167==    by 0x804E3C6: _archive_read_next_header2 (archive_read.c:645)
==33167==    by 0x804E47A: _archive_read_next_header (archive_read.c:683)
==33167==    by 0x8088A06: archive_read_next_header (archive_virtual.c:148)
==33167==    by 0x804A178: extract (ext.c:55)
==33167==    by 0x804A271: main (ext.c:83)
==33167== 
Damaged 7-Zip archive
==33167== 
==33167== HEAP SUMMARY:
==33167==     in use at exit: 169,758 bytes in 199 blocks
==33167==   total heap usage: 199 allocs, 0 frees, 169,758 bytes allocated
==33167== 
==33167== LEAK SUMMARY:
==33167==    definitely lost: 0 bytes in 0 blocks
==33167==    indirectly lost: 0 bytes in 0 blocks
==33167==      possibly lost: 0 bytes in 0 blocks
==33167==    still reachable: 169,758 bytes in 199 blocks
==33167==         suppressed: 0 bytes in 0 blocks
==33167== Rerun with --leak-check=full to see details of leaked memory
==33167== 
==33167== For counts of detected and suppressed errors, rerun with: -v
==33167== ERROR SUMMARY: 564 errors from 8 contexts (suppressed: 0 from 0)

TIMELINE

2016-04-19 - Vendor Disclosure
2016-06-18 - Public Release

Credit

Discovered by Marcin ‘Icewall’ Noga of Cisco Talos