Details of Elevation of privilege vulnerability in libziparchive

Introduction

An Integer overflow vulnerability in libziparchive when opening a ZIP archive that contains a large number of CD offset and size could allow attackers to trigger an out-of-bounds access or cause a denial of service.

CVE: CVE-2016-6762
BugID: A-31251826
Severity: High
Updated Google devices: All
Updated AOSP versions: 5.0.2, 5.1.1, 6.0, 6.0.1, 7.0
Author: ele7enxxh of Chengdu Security Response Center, Qihoo 360 Technology Co. Ltd

Description

The vulnerable code is as follows:
http://androidxref.com/7.0.0_r1/xref/system/core/libziparchive/zip_archive.cc#272

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
254 const off64_t eocd_offset = search_start + i;
...
272 if (eocd->cd_start_offset + eocd->cd_size > eocd_offset) {
273 ALOGW("Zip: bad offsets (dir %" PRIu32 ", size %" PRIu32 ", eocd %" PRId64 ")",
274 eocd->cd_start_offset, eocd->cd_size, static_cast<int64_t>(eocd_offset));
275 return kInvalidOffset;
276 }
277 if (eocd->num_records == 0) {
278 ALOGW("Zip: empty archive?");
279 return kEmptyArchive;
280 }
281
282 ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32,
283 eocd->num_records, eocd->cd_size, eocd->cd_start_offset);
284
285 /*
286 * It all looks good. Create a mapping for the CD, and set the fields
287 * in archive.
288 */
289 if (!archive->directory_map.create(debug_file_name, fd,
290 static_cast<off64_t>(eocd->cd_start_offset),
291 static_cast<size_t>(eocd->cd_size), true /* read only */) ) {
292 return kMmapFailed;
293 }

The eocd_offset is 64bit integers, however both cd_start_offset and cd_size are 32bit integers:
http://androidxref.com/7.0.0_r1/xref/system/core/libziparchive/zip_archive_common.h#29

1
2
3
4
5
6
7
8
9
29struct EocdRecord {
...
53 // The size of the central directory (in bytes).
54 uint32_t cd_size;
55 // The offset of the start of the central directory, relative
56 // to the start of the file.
57 uint32_t cd_start_offset;
...
63} __attribute__((packed));

The boundary check of an invalid EOCD record succeed due to the integer overflow in eocd->cd_start_offset + eocd->cd_size, as for the invocation of archive->directory_map.create():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
121bool FileMap::create(const char* origFileName, int fd, off64_t offset, size_t length,
122 bool readOnly)
123{
...
163 int prot, flags, adjust;
164 off64_t adjOffset;
165 size_t adjLength;
166
167 void* ptr;
168
169 assert(fd >= 0);
170 assert(offset >= 0);
171 assert(length > 0);
172
173 // init on first use
174 if (mPageSize == -1) {
175 mPageSize = sysconf(_SC_PAGESIZE);
176 if (mPageSize == -1) {
177 ALOGE("could not get _SC_PAGESIZE\n");
178 return false;
179 }
180 }
181
182 adjust = offset % mPageSize;
183 adjOffset = offset - adjust;
184 adjLength = length + adjust;
185
186 flags = MAP_SHARED;
187 prot = PROT_READ;
188 if (!readOnly)
189 prot |= PROT_WRITE;
190
191 ptr = mmap(NULL, adjLength, prot, flags, fd, adjOffset);

The length(eocd->cd_start_offset with aligned/adjusted) and offset(eocd->cd_size with aligned/adjusted) arguments to the mmap function are invalid, which would lead to a heap overflow on when writing past the heap boundary during the invocation of ParseZipArchive().

Attack vector

The Poc of corrupting the heap is as follows:
https://github.com/ele7enxxh/poc-exp/blob/master/CVE-2016-6762/CVE-2016-6762.apk
adb install CVE-2016-6762.apk
dexdump CVE-2016-6762.apk

Patches

Fix out of bound access in libziparchive:
https://android.googlesource.com/platform/system/core/+/1ee4892e66ba314131b7ecf17e98bb1762c4b84c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 87dac0e..54d866c 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -501,9 +501,14 @@
* Grab the CD offset and size, and the number of entries in the
* archive and verify that they look reasonable.
*/
- if (eocd->cd_start_offset + eocd->cd_size > eocd_offset) {
+ if (static_cast<off64_t>(eocd->cd_start_offset) + eocd->cd_size > eocd_offset) {
ALOGW("Zip: bad offsets (dir %" PRIu32 ", size %" PRIu32 ", eocd %" PRId64 ")",
eocd->cd_start_offset, eocd->cd_size, static_cast<int64_t>(eocd_offset));
+#if defined(__ANDROID__)
+ if (eocd->cd_start_offset + eocd->cd_size <= eocd_offset) {
+ android_errorWriteLog(0x534e4554, "31251826");
+ }
+#endif
return kInvalidOffset;
}
if (eocd->num_records == 0) {

Timeline

2016–08-28: Android bug reported to Google
2016-09-20: Android bug confirmed and the severity is set to High
2016-12-05: Android security bulletin released with fix
2016-12-08: Public disclosure