CVE-2016-8728
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.
MuPDF 1.10-rc1
8.6 – CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
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.
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==
2016-11-29 - Vendor Disclosure
2017-05-15 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos