Talos Vulnerability Report

TALOS-2016-0243

Artifex MuPDf JBIG2 Parser Code Execution Vulnerability

May 15, 2017
CVE Number

CVE-2016-8729

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

Credit

Discovered by Aleksandar Nikolic and Cory Duplantis of Cisco Talos