Artifex MuPDf JBIG2 Parser Code Execution Vulnerability(CVE-2016-8729)

2017-09-18T00:00:00
ID SSV:96509
Type seebug
Reporter Root
Modified 2017-09-18T00:00:00

Description

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 ofstride = ((width - 1) >> 3) + 1;, a negative value forstridecan be achieved. Passing this negative value tomemset` 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

CREDIT

  • Discovered by Aleksandar Nikolic and Cory Duplantis of Cisco Talos