Talos Vulnerability Report

TALOS-2016-0242

MuPDF Fitz library font glyph scaling Code Execution Vulnerability

May 15, 2017
CVE Number

CVE-2016-8728

Summary

An exploitable heap out of bounds write vulnerability exists in the Fitz graphical library part of the MuPDF renderer. A specially crafted PDF file can cause a out of bounds write resulting in heap metadata and sensitive process memory corruption leading to potential code execution. Victim needs to open the specially crafted file in a vulnerable reader in order to trigger this vulnerability.

Tested Versions

MuPDF 1.10-rc1

Product URLs

http://mupdf.com/

CVSSv3 Score

8.6 – CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

Details

MuPDF is a lightweight PDF parsing and rendering library featuring high fidelity graphics, high speed and compact code size which makes it a fairly popular PDF library for embedding in different projects, especially mobile and web applications.

Fitz is an underlying graphics library which MuPDF uses to render PDFs and images. There exists an exploitable vulnerability in the glyph scaling code of MuPDF. Specifically, when a font glyph is specified and used in a way where it must be scaled down an invalid pointer arithmetic can result in invalid memory access which can be abused to overwrite sensitive memory.

The vulnerability is located in function scale_single_row in file source/fitz/draw-scale-simple.c:

static void
scale_single_row(unsigned char * restrict dst, int dstride, const unsigned char * restrict src, const fz_weights * restrict weights, int src_w, int h, int forcealpha)
{
        const int *contrib = &weights->index[weights->index[0]];
        int min, len, i, j, n, nf;
        int tmp[FZ_MAX_COLORS];


        n = weights->n;                                         [1]
        nf = n + forcealpha;                                    [2]
...
        if (weights->flip)
        {
                dst += (weights->count-1)*n;                    [3]
                for (i=weights->count; i > 0; i--)
                {
...
                        for (j = 0; j < nf; j++)                [4]
                        {
                                *dst++ = (unsigned char)(tmp[j]>>8);
                                tmp[j] = 128;
                        }
                        dst -= 2*nf;                            [5]
                }
                dst += nf + dstride;
        }

In the above code, at [1] and [2] variables n and nf are initialized, at [3] the destination pointer dst is increased in relation to count and n. Inside a for loop at [4] dst pointer is written to and incremented and finally at [5] it is decremented. The vulnerability can be triggered due to the fact that dst in increased nf times inside for loop, but decreased by 2*nf just outside the for loop. If the outer loop (at [3]) loops more than once, dst can start pointing behind it’s original value which leads to adjacent memory overwrite. With precise control over values of n and count, the pointer can be shifted as desired giving control over which memory gets overwritten.

In the following debugging session, we can see the concrete values for n and count from a PoC testcase that results in triggering this vulnerability:

Breakpoint 2, scale_single_row (dst=0xa4292a0 "\210\030߷\220\252B\nP", dstride=0xc, src=0xa3ecad8 "AAAABBBBCCCC\031", weights=0xa40c560, src_w=0x4, h=0x2, 
    forcealpha=0x1) at source/fitz/draw-scale-simple.c:1286
1286                const int *contrib = &weights->index[weights->index[0]];
gdb$ p *(struct fz_weights_s *)0xa40c560
$5 = {
  flip = 0x1, 
  count = 0x3, 
  max_len = 0x4, 
  n = 0x3, 
  new_line = 0x0, 
  patch_l = 0x0, 
  index = {0x3}
}
gdb$ x/x 0xa4292a0
0xa4292a0:        0xb7df1888
gdb$ x/x 0xa4292a0-4
0xa42929c:        0x00000021
gdb$ watch *0xa42929c
Hardware watchpoint 7: *0xa42929c
gdb$ c
Continuing.
Hardware watchpoint 7: *0xa42929c


Old value = 0x21
New value = 0xb0021
scale_single_row (dst=0xa42929f "", dstride=0xc, src=0xa3ecad8 "AAAABBBBCCCC\031", weights=0xa40c560, src_w=0x4, h=0x2, forcealpha=0x1)
    at source/fitz/draw-scale-simple.c:1314
1314                                        tmp[j] = 128;
gdb$ c
Hardware watchpoint 7: *0xa42929c


Old value = 0xb0021
New value = 0xb0b0021
scale_single_row (dst=0xa4292a0 "\210\030BBC\377\036\036\036v", dstride=0xc, src=0xa3ecad8 "AAAABBBBCCCC\031", weights=0xa40c560, src_w=0x4, h=0x2, 
    forcealpha=0x1) at source/fitz/draw-scale-simple.c:1314
1314                                        tmp[j] = 128;
gdb$

Breakpoint 2 is hit, at the beginning of function scale_single_row and we can see that values of weights structure are:

flip = 0x1, 
count = 0x3, 
max_len = 0x4, 
n = 0x3, 
new_line = 0x0, 
patch_l = 0x0, 

Also important is that forcealpha is set to 1. In this case, nf will be equal to 4, at first dst will be increased by (count - 1 )*n, that is 6, then the loop will loop 4 times, increasing dst by 4 for a total of 10. Outside the loop, dst will be decreased by 2*nf or 8, totaling 2. Next time the inner for loop is hit, dst will be increased by 4 times (for 6 total) but will be decreased by 8, therefore accessing the data previous to the original pointer address. This can be observed in the above gdb input by setting a read/write access breakpoint on memory location just before the original pointer. This overwrite results in heap metadata corruption leading to process termination. The data that is being written to an out of bounds location is under indirect control of the attacker.

Crash Information

Valgrind output showing the vulnerability being triggered against a sample PDF viewer application mupdf-x11:

==10727== Memcheck, a memory error detector
==10727== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==10727== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==10727== Command: ../mupdf/build/debug/mupdf-x11 triage.pdf
==10727== 
error: cannot recognize xref format
warning: trying to repair broken xref
==10727== Invalid write of size 1
==10727==    at 0x808C898: scale_single_row (draw-scale-simple.c:1313)
==10727==    by 0x808DA6A: fz_scale_pixmap_cached (draw-scale-simple.c:1736)
==10727==    by 0x8063635: fz_transform_pixmap (draw-device.c:1302)
==10727==    by 0x8063B3B: fz_draw_fill_image (draw-device.c:1416)
==10727==    by 0x805687E: fz_fill_image (device.c:317)
==10727==    by 0x8071D4F: fz_run_display_list (list-device.c:1646)
==10727==    by 0x805AD23: fz_run_t3_glyph (font.c:1224)
==10727==    by 0x805AEE1: fz_render_t3_glyph_pixmap (font.c:1272)
==10727==    by 0x805B000: fz_render_t3_glyph (font.c:1307)
==10727==    by 0x805C42B: fz_render_glyph (draw-glyph.c:327)
==10727==    by 0x8061E02: fz_draw_fill_text (draw-device.c:798)
==10727==    by 0x80564D7: fz_fill_text (device.c:198)
==10727==  Address 0x4743ad6 is 2 bytes before a block of size 24 alloc'd
==10727==    at 0x402B211: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==10727==    by 0x8074989: fz_malloc_default (memory.c:213)
==10727==    by 0x807445C: do_scavenging_malloc (memory.c:17)
==10727==    by 0x80745EB: fz_malloc_array (memory.c:80)
==10727==    by 0x8068A1B: fz_new_pixmap_with_data (pixmap.c:76)
==10727==    by 0x8068AC3: fz_new_pixmap (pixmap.c:95)
==10727==    by 0x808D8FE: fz_scale_pixmap_cached (draw-scale-simple.c:1709)
==10727==    by 0x8063635: fz_transform_pixmap (draw-device.c:1302)
==10727==    by 0x8063B3B: fz_draw_fill_image (draw-device.c:1416)
==10727==    by 0x805687E: fz_fill_image (device.c:317)
==10727==    by 0x8071D4F: fz_run_display_list (list-device.c:1646)
==10727==    by 0x805AD23: fz_run_t3_glyph (font.c:1224)
==10727== 

Timeline

2016-11-29 - Vendor Disclosure
2017-05-15 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos