Lucene search

K
talosTalos IntelligenceTALOS-2016-0193
HistorySep 29, 2016 - 12:00 a.m.

OpenJPEG JPEG2000 mcc record Code Execution Vulnerability

2016-09-2900:00:00
Talos Intelligence
www.talosintelligence.com
28

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

0.028 Low

EPSS

Percentile

90.5%

Summary

An exploitable code execution vulnerability exists in the jpeg2000 image file format parser as implemented in the OpenJpeg library. A specially crafted jpeg2000 file can cause an out of bound heap write resulting in heap corruption leading to arbitrary code execution. For a successful attack, the target user needs to open a malicious jpeg2000 file. The jpeg2000 image file format is mostly used for embedding images inside PDF documents and the OpenJpeg library is used by a number of popular PDF renderers making PDF documents a likely attack vector.

Tested Versions

OpenJpeg openjp2 2.1.1

Product URLs

<http://www.openjpeg.org/&gt;

CVSSv3 Score

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

Details

The OpenJpeg library is a reference implementation of JPEG2000 standard and is used by many popular PDF renderers. Most notably Poppler, MuPDF and Pdfium.

Due to an error while parsing mcc records in the jpeg2000 file, out of bounds memory can be accessed resulting in an erroneous read and write of adjacent heap area memory. Careful manipulation of heap layout and can lead to further heap metadata process memory corruption ultimately leading to code execution under attacker control.

The vulnerability lies in opj_j2k_read_mcc_record function in src/lib/openjp2/j2k.c file which is responsible for parsing mcc records.

```
    l_mcc_record = l_tcp-&gt;m_mcc_records;

    for(i=0;i&lt;l_tcp-&gt;m_nb_mcc_records;++i) {
            if (l_mcc_record-&gt;m_index == l_indix) {
                    break;
            }
            ++l_mcc_record;
    }
```

When an mcc record is being parsed, a l_mcc_recprd array is being iterated over in search of appropriate index. Next, if the index is not found, the following code is executed:

```
    if (i == l_tcp-&gt;m_nb_mcc_records) {
                    if (l_tcp-&gt;m_nb_mcc_records == l_tcp-&gt;m_nb_max_mcc_records) {
                            opj_simple_mcc_decorrelation_data_t *new_mcc_records;
                            l_tcp-&gt;m_nb_max_mcc_records += OPJ_J2K_MCC_DEFAULT_NB_RECORDS;

                            new_mcc_records = (opj_simple_mcc_decorrelation_data_t *) opj_realloc(
                                            l_tcp-&gt;m_mcc_records, l_tcp-&gt;m_nb_max_mcc_records * sizeof(opj_simple_mcc_decorrelation_data_t));
                            if (! new_mcc_records) {
                                    opj_free(l_tcp-&gt;m_mcc_records);
                                    l_tcp-&gt;m_mcc_records = NULL;
                                    l_tcp-&gt;m_nb_max_mcc_records = 0;
                                    l_tcp-&gt;m_nb_mcc_records = 0;
                                    opj_event_msg(p_manager, EVT_ERROR, "Not enough memory to read MCC marker\n");
                                    return OPJ_FALSE;
                            }
                            l_tcp-&gt;m_mcc_records = new_mcc_records;
                            l_mcc_record = l_tcp-&gt;m_mcc_records + l_tcp-&gt;m_nb_mcc_records;
                            memset(l_mcc_record,0,(l_tcp-&gt;m_nb_max_mcc_records-l_tcp-&gt;m_nb_mcc_records) * sizeof(opj_simple_mcc_decorrelation_data_t));
                    }
                    l_mcc_record = l_tcp-&gt;m_mcc_records + l_tcp-&gt;m_nb_mcc_records;
            }
            l_mcc_record-&gt;m_index = l_indix;
```

The first if statement is entered if the index was not found, then, if current number of records has reached a maximum of l_tcp-&gt;m_nb_max_mcc_records (which is 10 initially), maximum is increased and memory is reallocated to accommodate more records. At the end of the function, number of records is increased:

```
    ++l_tcp-&gt;m_nb_mcc_records;
    return OPJ_TRUE;
```

The vulnerability in the above code lies in the improper increment of the number of records at the end of the function. If a malicious image is created, such that it has a number of mcc records with the same (zero) index, the counter in the for loop can never reach the value that would satisfy i == l_tcp-&gt;m_nb_mcc_records condition. If there are 10 or more such objects, l_tcp-&gt;m_nb_mcc_records will be increased to more than l_tcp-&gt;m_nb_max_mcc_records without actually reallocating the appropriate amount of memory. If then there is an mcc record with a different index in the image, the if condition inside the for loop won’t ever be true, which will lead to l_mcc_record pointer being increased out of bounds, causing an out of bounds read. Further on, this out of bounds pointer is retained and is used in a write operation when its index is being updated by a controlled value.

By varying the number of mcc records, an attacker can target a particular heap memory area and by abusing the same bug multiple times gain enough control over the process memory to get arbitrary code execution.

Crash Information

```
    bash-4.3$ valgrind  $opj_decompress -i minimal.jp2 -o dasd.bmp
    ==13197== Memcheck, a memory error detector
    ==13197== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
    ==13197== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
    ==13197== Command:../openjpeg/build/bin/opj_decompress -i minimal.jp2 -o dasd.bmp
    ==13197==

    [INFO] Start to read j2k main header (119).

    ==13197== Invalid read of size 4
    ==13197==    at 0x4049768: opj_j2k_read_mcc (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404DACA: opj_j2k_read_header_procedure (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404DDB4: opj_j2k_exec (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404CB01: opj_j2k_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x405A97C: opj_jp2_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x405CD75: opj_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x804C3E9: main (in../openjpeg/build/bin/opj_decompress)
    ==13197==  Address 0x4439080 is 0 bytes after a block of size 200 alloc'd
    ==13197==    at 0x402CEBA: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
    ==13197==    by 0x406D4C2: opj_calloc (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x40436B6: opj_j2k_read_siz (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404DACA: opj_j2k_read_header_procedure (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404DDB4: opj_j2k_exec (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404CB01: opj_j2k_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x405A97C: opj_jp2_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x405CD75: opj_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x804C3E9: main (in../openjpeg/build/bin/opj_decompress)
    ==13197==
    ==13197== Invalid write of size 4
    ==13197==    at 0x4049940: opj_j2k_read_mcc (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404DACA: opj_j2k_read_header_procedure (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404DDB4: opj_j2k_exec (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x404CB01: opj_j2k_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x405A97C: opj_jp2_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x405CD75: opj_read_header (in../openjpeg/build/bin/libopenjp2.so.2.1.1)
    ==13197==    by 0x804C3E9: main (in../openjpeg/build/bin/opj_decompress)
    ==13197==  Address 0x4439094 is 20 bytes after a block of size 200 in arena "client"
    ==13197==
    [ERROR] Error reading MCC marker
    [ERROR] Marker handler function failed to read the marker segment
```

In the above Valgrind output, an invalid out of bounds read and write is recorded.

Timeline

2016-07-26 - Vendor Disclosure
2016-09-29 - Public Release

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

6.8 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:M/Au:N/C:P/I:P/A:P

0.028 Low

EPSS

Percentile

90.5%