Introduction

Problem description:

On linux installations with X/fvwm, xpdf on a minimal installation will not work properly and crash. The goal of this analysis was to understand the cause of the crash to see if it might be exploitable under more common conditions also, e.g. when rendering embedded pdf via libpoppler from network source.

Method

Analysis was done on Ubuntu Precise, 64bit with The analysis was performed using standard debugging and source code analysis.

Results, Discussion

Default behavior:

After minimal install, rendering of each tested pdf document, e.g. of this test document, failed with a crash. Debugger shows:

Starting program: /usr/bin/xpdf.real Test.pdf Warning: Cannot convert string "-*-helvetica-medium-r-normal--12-*-*-*-*-*-iso8859-1" to type FontStruct Warning: Cannot convert string "-*-courier-medium-r-normal--12-*-*-*-*-*-iso8859-1" to type FontStruct Warning: Cannot convert string "-*-times-bold-i-normal--20-*-*-*-*-*-iso8859-1" to type FontStruct Warning: Cannot convert string "-*-times-medium-r-normal--16-*-*-*-*-*-iso8859-1" to type FontStruct Error: PDF file is damaged - attempting to reconstruct xref table... ***** MediaBox = ll:0,0 ur:595.28,841.89 ***** CropBox = ll:0,0 ur:595.28,841.89 ***** Rotate = 0 ***** page 1 ***** ***** MediaBox = ll:0,0 ur:595.28,841.89 ***** CropBox = ll:0,0 ur:595.28,841.89 ***** Rotate = 0 Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7796420 in GooHash::hash(GooString*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 (gdb) bt #0 0x00007ffff7796420 in GooHash::hash(GooString*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #1 0x00007ffff7796472 in GooHash::find(GooString*, int*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #2 0x00007ffff779660e in GooHash::lookup(GooString*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #3 0x00007ffff7748be4 in GlobalParams::getResidentUnicodeMap(GooString*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #4 0x00007ffff774a2f3 in GlobalParams::getUnicodeMap2(GooString*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #5 0x00007ffff778d3da in TextPage::coalesce(bool, bool) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #6 0x00007ffff77921ba in TextOutputDev::endPage() () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #7 0x00007ffff77252e7 in Gfx::~Gfx() () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #8 0x00007ffff7761ce2 in Page::displaySlice(OutputDev*, double, double, int, bool, bool, int, int, int, int, bool, Catalog*, bool (*)(void*), void*, bool (*)(Annot*, void*), void*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #9 0x00007ffff7761d7b in Page::display(OutputDev*, double, double, int, bool, bool, bool, Catalog*, bool (*)(void*), void*, bool (*)(Annot*, void*), void*) () from /usr/lib/x86_64-linux-gnu/libpoppler.so.19 #10 0x0000000000415432 in ?? () #11 0x0000000000418631 in ?? () #12 0x000000000041b15d in ?? () #13 0x00000000004130f5 in ?? () #14 0x00000000004275fa in ?? () #15 0x00000000004194f3 in ?? () #16 0x000000000040b694 in ?? () #17 0x00007ffff651476d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6 ... (gdb) x/1i $rip => 0x7ffff7796420 <_ZN7GooHash4hashEP9GooString>: mov 0x20(%rsi),%r8 (gdb) info registers ... rsi 0x1010001 16842753 ...

Checking the stacktrace and binary, the code copying the invalid rsi value is in GlobalParams::getTextEncoding.

Dump of assembler code for function _ZN12GlobalParams15getTextEncodingEv: => 0x00007ffff774a3e0 <+0>: mov 0x80(%rdi),%rsi

The memory location 0x80(%rdi) is written only once, that revealed that the libpoppler GlobalParams class constructor did not write it. In fact, the constructor is never called. Instead of that, the xpdf program brings an own and divergent version of the GlobalParams class, handling that over to libpoppler. Comparing the different definitions (xpdf/GlobalParams.h and poppler/GlobalParams.h) reveals, that xpdf class definition will copy boolean configuration values to that location, used by libpoppler to store textEncoding. The rsi value can be controlled adding configuration options to ~/.xpdfrc:

psCrop no psExpandSmaller no psShrinkLarger no psCenter no psDuplex no

Due to the fact, that overwritten pointer may only contain bytes 0 and 1, it is nearly impossible, that this pointer will point to a valid memory location, no matter which user xpdf configuration is used.

Discussion

Due to using boolean values from xpdf configuration to overwrite textEncoding pointer, at least in encoding handling the location cannot be influenced remotely and even local exploitation on 64 bit systems is very unlikely to succeed.

Due to 8-byte pointer field alignment in GlobalParams, the bug should cause different results with 32bit systems, with two different outcomes:

It was also not analyzed, how xpdf on full installations works correctly. It seems that missing fonts or incomplete configuration caused changes in the GlobalParams object handling. It may be, that the libpoppler GlobalParams constructor is called correctly from another location. Checking that mechanism might reveal, if there is a chance to trigger some intermediate state on full installs, causing libpoppler to operate on partially initialized GlobalParams object.

Timeline

Material, References

Last modified 20171228
Contact e-mail: me (%) halfdog.net