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\archive_read_support_format_7zip.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 “unpack_streams” variable. This variable is size_t what means that on x86 platform will be 32bit unsigned int. Maxium value of of “numUnpackStreams” is equal to UMAX_ENTRY. To overflow “unpack_streams” 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