Talos Vulnerability Report

TALOS-2017-0319

Poppler PDF Image Display DCTStream::readProgressiveSOF() Code Execution Vulnerability

July 7, 2017
CVE Number

CVE-2017-2818

Summary

An exploitable heap overflow vulnerability exists in the image rendering functionality of Poppler-0.53.0. A specifically crafted PDF can cause an overly large number of color components during image rendering, resulting in heap corruption. An attacker controlled PDF file can be used to trigger this vulnerability.

Tested Versions

Poppler-0.53.0

Product URLs

https://poppler.freedesktop.org/

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-122: Heap-based Buffer Overflow

Details

Poppler is a shared library for displaying PDF files, used as middleware within different enterprise and opensource solutions alike (e.g. Gimp). It is forked off of XPDF, and is a complete implementation of the PDF ISO standard.

The Poppler library, by default, uses a private implementation of reading and rendering images. There is a compilation option for libjpeg support, but the flag is not enabled by default. This private implementation contains assumptions about the JPEG file headers that can lead to heap corruption when broken.

This vulnerability was formerly found (CVE-2005-3627) with a fix applied to DCTStream::readBaselineSOF, however the bug was not also fixed in the readProgressiveSOF function. A look at the two functions highlights the vulnerability: There should be a check for: if (numComps <= 0 || numComps > 4) at [0]

GBool DCTStream::readBaselineSOF() {
  int length;
  int prec;
  int i;
  int c;

  length = read16();
  prec = str->getChar();
  height = read16();
  width = read16();
  numComps = str->getChar();
  if (numComps <= 0 || numComps > 4) {
    error(errSyntaxError, getPos(), "Bad number of components in DCT stream");
    numComps = 0;
    return gFalse;
  
 if (prec != 8) {
    error(errSyntaxError, getPos(), "Bad DCT precision {0:d}", prec);
    return gFalse;
  
//...

  GBool DCTStream::readProgressiveSOF() {
  int length;
  int prec;
  int i;
  int c;

  length = read16();
  prec = str->getChar();
  height = read16();
  width = read16();
  numComps = str->getChar();

  // [0] 

  if (prec != 8) {
    error(errSyntaxError, getPos(), "Bad DCT precision {0:d}", prec);
    return gFalse;

As there is no check on the numComps variable, the subsequent loop in DCTStream::readProgressiveSOF can then write past the intended bounds of compInfo[3], and into heap metadata

for (i = 0; i < numComps; ++i) {
    compInfo[i].id = str->getChar();
    c = str->getChar();
    compInfo[i].hSample = (c >> 4) & 0x0f;
    compInfo[i].vSample = c & 0x0f;
    compInfo[i].quantTable = str->getChar();
    if (compInfo[i].hSample < 1 || compInfo[i].hSample > 4 ||
        compInfo[i].vSample < 1 || compInfo[i].vSample > 4) {
          error(errSyntaxError, getPos(), "Bad DCT sampling factor");
          return gFalse;
    
    if (compInfo[i].quantTable < 0 || compInfo[i].quantTable > 3) {
          error(errSyntaxError, getPos(), "Bad DCT quant table selector");
          return gFalse;

Crash Information

RAX: 0x7f8c6dfbaf50 --> 0x7f8c6dcb2760 (:~DCTStream()>: 0x530030b6c9058b48)
RBX: 0x142dd00 --> 0x1 
RCX: 0x8 
RDX: 0xffffffff 
RSI: 0x0 
RDI: 0x142cf50 --> 0x7f8c6dfbaf50 --> 0x7f8c6dcb2760 (:~DCTStream()>: 0x530030b6c9058b48)
RBP: 0x142de00 --> 0x100000001 
RSP: 0x7ffce0c46010 --> 0x142e450 --> 0x7f8cfffffffd 
RIP: 0x7f8c6dcb15f8 (:close()+40>: 0xe808c383483b8b48)
R8 : 0x3 
R9 : 0x142c280 --> 0x142c660 --> 0x0 
R10: 0x7f8c6d31bbe0 --> 0x0 
R11: 0x1 
R12: 0x142e100 --> 0x0 
R13: 0x142e100 --> 0x0 
R14: 0x142cf50 --> 0x7f8c6dfbaf50 --> 0x7f8c6dcb2760 (:~DCTStream()>: 0x530030b6c9058b48)
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)

[-------------------------------------code-------------------------------------]
0x7f8c6dcb15e9 <DCTStream::close()+25>: mov r12,r13
0x7f8c6dcb15ec <DCTStream::close()+28>: lea rbp,[rbx+0x100]
0x7f8c6dcb15f3 <DCTStream::close()+35>: nop DWORD PTR [rax+rax*1+0x0]
=> 0x7f8c6dcb15f8 <DCTStream::close()+40>: mov rdi,QWORD PTR [rbx]
0x7f8c6dcb15fb <DCTStream::close()+43>: add rbx,0x8
0x7f8c6dcb15ff <DCTStream::close()+47>: call 0x7f8c6dbfd7a0 <gfree@plt>
0x7f8c6dcb1604 <DCTStream::close()+52>: mov QWORD PTR [rbx-0x8],0x0
0x7f8c6dcb160c <DCTStream::close()+60>: cmp rbx,rbp
[------------------------------------stack-------------------------------------]
0000| 0x7ffce0c46010 --> 0x142e450 --> 0x7f8cfffffffd 
0008| 0x7ffce0c46018 --> 0x0 
0016| 0x7ffce0c46020 --> 0x142e468 --> 0x8 
0024| 0x7ffce0c46028 --> 0x1 
0032| 0x7ffce0c46030 --> 0x0 
0040| 0x7ffce0c46038 --> 0x7f8c6dc9bdf7 (:getChar(bool)+55>: 0xfff650e1e8e7894c)
0048| 0x7ffce0c46040 --> 0x142e450 --> 0x7f8cfffffffd 
0056| 0x7ffce0c46048 --> 0x1429d98 --> 0xd ('\r')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Timeline

2017-05-17 - Vendor Disclosure
2017-07-07 - Public Release

Credit

Discovered by Lilith Wyatt of Cisco Talos.