Headline
CVE-2016-8729: TALOS-2016-0243 || Cisco Talos Intelligence Group
An exploitable memory corruption vulnerability exists in the JBIG2 parser of Artifex MuPDF 1.9. A specially crafted PDF can cause a negative number to be passed to a memset resulting in memory corruption and potential code execution. An attacker can specially craft a PDF and send to the victim to trigger this vulnerability.
Summary
An exploitable memory corruption vulnerability exists in the JBIG2 parser of Artifex MuPDF 1.9. A specially crafted PDF can cause a negative number to be passed to a memset resulting in memory corruption and potential code execution. An attacker can specially craft a PDF and send to the victim to trigger this vulnerability.
Tested Versions
MuPDF 1.9 MuPDF 1.10 RC2
Product URLs
http://mupdf.com/
CVSSv3 Score
7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE
CWE-122: Heap-based Buffer Overflow
Details
MuPDF is a lightweight PDF, XPS, and E-Book viewer that has packages available for Windows as well as Android, iPad, and iPhone.
During the parsing of a JBIG2 image embedded in a PDF, each image segment is handled based on the flags for that particular segment. Segments with flags 38 or 39 are handled by calling jbig2_immediate_generic_region on the current segment [0].
thirdparty/jbig2dec/jbig2_segment.c:227
/* general segment parsing dispatch */
int
jbig2_parse_segment(Jbig2Ctx *ctx, Jbig2Segment *segment, const uint8_t *segment_data)
{
jbig2_error(ctx, JBIG2_SEVERITY_INFO, segment->number,
"Segment %d, flags=%x, type=%d, data_length=%d", segment->number, segment->flags, segment->flags & 63, segment->data_length);
switch (segment->flags & 63) {
...
case 38: /* immediate generic region */
case 39: /* immediate lossless generic region */
return jbig2_immediate_generic_region(ctx, segment, segment_data); [0]
Each segment is lifted into a Jbig2RegionSegmentInfo object by reading the segment header information. Two key values are extracted during jbig2_get_region_segment_info [1]: width and height [2].
thirdparty/jbig2dec/jbig2_segment.c:227
/**
* Handler for immediate generic region segments
*/
int
jbig2_immediate_generic_region(Jbig2Ctx *ctx, Jbig2Segment *segment, const byte *segment_data)
{
Jbig2RegionSegmentInfo rsi;
...
jbig2_get_region_segment_info(&rsi, segment_data); [1]
...
image = jbig2_image_new(ctx, rsi.width, rsi.height); [3]
thirdparty/jbig2dec/jbig2_segment.c:186
void
jbig2_get_region_segment_info(Jbig2RegionSegmentInfo *info, const uint8_t *segment_data)
{
/* 7.4.1 */
info->width = jbig2_get_int32(segment_data); [2]
info->height = jbig2_get_int32(segment_data + 4); [2]
info->x = jbig2_get_int32(segment_data + 8);
info->y = jbig2_get_int32(segment_data + 12);
info->flags = segment_data[16];
info->op = (Jbig2ComposeOp)(info->flags & 0x7);
}
After extracting the width and height from the segment, jbig2_image_new is called [3]. A stride value is calculated from the width and subsequently checked to ensure a multiplication overflow won’t occur [4]. Assuming this check is passed, the resulting stride value is stored in an image object and returned.
thirdparty/jbig2dec/jbig2_image.c:34
Jbig2Image *
jbig2_image_new(Jbig2Ctx *ctx, int width, int height)
{
Jbig2Image *image;
...
stride = ((width - 1) >> 3) + 1; /* generate a byte-aligned stride */
/* check for integer multiplication overflow */
check = ((int64_t) stride) * ((int64_t) height); [4]
if (check != (int)check) {
jbig2_error(ctx, JBIG2_SEVERITY_FATAL, -1, "integer multiplication overflow from stride(%d)*height(%d)", stride, height);
jbig2_free(ctx->allocator, image);
return NULL;
}
...
image->stride = stride; [5]
...
return image;
}
If the MMR flag is set in the image segment flags, then the resulting image is passed to jbig2_decode_generic_mmr. During this decoding, the stride value is used directly as the size value in a memset [6].
int
jbig2_decode_generic_mmr(Jbig2Ctx *ctx, Jbig2Segment *segment, const Jbig2GenericRegionParams *params, const byte *data, size_t size, Jbig2Image *image)
{
Jbig2MmrCtx mmr;
const int rowstride = image->stride;
...
for (y = 0; y < image->height; y++) {
memset(dst, 0, rowstride); [6]
...
}
}
Using the calculation of stride = ((width - 1) >> 3) + 1;, a negative value for stride can be achieved. Passing this negative value to memset results in a buffer overflow condition that could possibly be leveraged to gain code execution.
Crash Information
Dr. Memory output
~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS beyond heap bounds: writing 0x0000000002a7f350-0x0000000002a7f354 4 byte(s)
~~Dr.M~~ # 0 replace_memset [/work/drmemory_package/drmemory/replace.c:201]
~~Dr.M~~ # 1 jbig2_decode_generic_mmr [thirdparty/jbig2dec/jbig2_mmr.c:1021]
~~Dr.M~~ # 2 jbig2_immediate_generic_region [thirdparty/jbig2dec/jbig2_generic.c:766]
~~Dr.M~~ # 3 jbig2_parse_segment [thirdparty/jbig2dec/jbig2_segment.c:249]
~~Dr.M~~ # 4 jbig2_data_in [thirdparty/jbig2dec/jbig2.c:312]
~~Dr.M~~ # 5 fz_load_jbig2_globals [source/fitz/filter-jbig2.c:350]
~~Dr.M~~ # 6 pdf_load_jbig2_globals [source/pdf/pdf-stream.c:72]
~~Dr.M~~ # 7 build_filter [source/pdf/pdf-stream.c:181]
~~Dr.M~~ # 8 pdf_open_filter [source/pdf/pdf-stream.c:313]
~~Dr.M~~ # 9 pdf_open_image_stream [source/pdf/pdf-stream.c:412]
~~Dr.M~~ #10 pdf_load_image_stream [source/pdf/pdf-stream.c:569]
~~Dr.M~~ #11 pdf_load_compressed_stream [source/pdf/pdf-stream.c:612]
~~Dr.M~~ #12 pdf_load_image_imp [source/pdf/pdf-image.c:160]
~~Dr.M~~ #13 pdf_load_image [source/pdf/pdf-image.c:283]
~~Dr.M~~ #14 pdf_process_Do [source/pdf/pdf-interpret.c:555]
~~Dr.M~~ #15 pdf_process_keyword [source/pdf/pdf-interpret.c:992]
~~Dr.M~~ #16 pdf_process_stream [source/pdf/pdf-interpret.c:1170]
~~Dr.M~~ #17 pdf_process_contents [source/pdf/pdf-interpret.c:1242]
~~Dr.M~~ #18 pdf_run_page_contents_with_usage [source/pdf/pdf-run.c:41]
~~Dr.M~~ #19 pdf_run_page_contents [source/pdf/pdf-run.c:62]
~~Dr.M~~ Note: @0:00:00.538 in thread 16021
~~Dr.M~~ Note: refers to 0 byte(s) beyond last valid byte in prior malloc
~~Dr.M~~ Note: prev lower malloc: 0x0000000002a7f350-0x0000000002a7f350
~~Dr.M~~ Note: allocated here:
~~Dr.M~~ Note: # 0 replace_malloc [/work/drmemory_package/common/alloc_replace.c:2576]
~~Dr.M~~ Note: # 1 jbig2_default_alloc [thirdparty/jbig2dec/jbig2.c:36]
~~Dr.M~~ Note: # 2 jbig2_alloc [thirdparty/jbig2dec/jbig2.c:63]
~~Dr.M~~ Note: # 3 jbig2_image_new [thirdparty/jbig2dec/jbig2_image.c:56]
~~Dr.M~~ Note: # 4 jbig2_immediate_generic_region [thirdparty/jbig2dec/jbig2_generic.c:760]
~~Dr.M~~ Note: # 5 jbig2_parse_segment [thirdparty/jbig2dec/jbig2_segment.c:249]
~~Dr.M~~ Note: # 6 jbig2_data_in [thirdparty/jbig2dec/jbig2.c:312]
~~Dr.M~~ Note: # 7 fz_load_jbig2_globals [source/fitz/filter-jbig2.c:350]
~~Dr.M~~ Note: # 8 pdf_load_jbig2_globals [source/pdf/pdf-stream.c:72]
~~Dr.M~~ Note: # 9 build_filter [source/pdf/pdf-stream.c:181]
~~Dr.M~~ Note: #10 pdf_open_filter [source/pdf/pdf-stream.c:313]
~~Dr.M~~ Note: #11 pdf_open_image_stream [source/pdf/pdf-stream.c:412]
~~Dr.M~~ Note: instruction: mov %eax -> (%rbx)
Valgrind output
==19258== Memcheck, a memory error detector
==19258== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==19258== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==19258== Command: ./build/release/mutool convert -o /tmp/asdfasdf -F png ../smart_jbig_crashes_mupdf/04637126fea55ca2a3bf243a3ccfe2858922d943.pdf
==19258==
warning: jbig2dec warning: MMR is 1, but GBTEMPLATE is not 0 (segment 0)
==19258== Invalid write of size 8
==19258== at 0x4C3453F: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19258== by 0x5A697F: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x5A3939: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x5451E0: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4D4DE6: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5614: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5942: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5EBD: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5F9A: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A6108: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A63CC: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4AF887: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== Address 0x5799f60 is 0 bytes after a block of size 0 alloc'd
==19258== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19258== by 0x5457E0: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x5A3745: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x5451E0: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4D4DE6: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5614: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5942: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5EBD: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A5F9A: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A6108: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4A63CC: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
==19258== by 0x4AF887: ??? (in /home/vagrant/fuzzing/mupdf/mupdf-orig/build/release/mutool)
Timeline
2016-11-29 - Vendor Disclosure
2017-05-15 - Public Release