Bug 976775 (CVE-2016-3074) - VUL-0: CVE-2016-3074: php5,php53: signedness vulnerability in bundled libgd library
Summary: VUL-0: CVE-2016-3074: php5,php53: signedness vulnerability in bundled libgd l...
Status: RESOLVED FIXED
Alias: CVE-2016-3074
Product: SUSE Security Incidents
Classification: Novell Products
Component: Incidents (show other bugs)
Version: unspecified
Hardware: Other Other
: P3 - Medium : Normal
Target Milestone: ---
Assignee: Security Team bot
QA Contact: Security Team bot
URL: https://smash.suse.de/issue/168205/
Whiteboard: CVSSv2:SUSE:CVE-2016-3074:5.1:(AV:N/A...
Keywords:
Depends on:
Blocks:
 
Reported: 2016-04-22 07:39 UTC by Johannes Segitz
Modified: 2016-08-09 18:23 UTC (History)
1 user (show)

See Also:
Found By: Security Response Team
Services Priority:
Business Priority:
Blocker: ---
Marketing QA Status: ---
IT Deployment: ---


Attachments
testcase from the git commit (1.64 KB, application/octet-stream)
2016-04-25 08:12 UTC, Petr Gajdos
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Johannes Segitz 2016-04-22 07:39:24 UTC
CVE-2016-3074

From: Hans Jerry Illikainen

Overview
========

libgd [1] is an open-source image library.  It is perhaps primarily used
by the PHP project.  It has been bundled with the default installation
of PHP since version 4.3 [2].

A signedness vulnerability (CVE-2016-3074) exist in libgd 2.1.1 which
may result in a heap overflow when processing compressed gd2 data.


Details
=======

4 bytes representing the chunk index size is stored in a signed integer,
chunkIdx[i].size, by `gdGetInt()' during the parsing of GD2 headers:

libgd-2.1.1/src/gd_gd2.c:
,----
|  53 typedef struct {
|  54     int offset;
|  55     int size;
|  56 }
|  57 t_chunk_info;
`----

libgd-2.1.1/src/gd_gd2.c:
,----
|  65 static int
|  66 _gd2GetHeader (gdIOCtxPtr in, int *sx, int *sy,
|  67                int *cs, int *vers, int *fmt, int *ncx, int *ncy,
|  68                t_chunk_info ** chunkIdx)
|  69 {
| ...
|  73     t_chunk_info *cidx;
| ...
| 155     if (gd2_compressed (*fmt)) {
| ...
| 163         for (i = 0; i < nc; i++) {
| ...
| 167             if (gdGetInt (&cidx[i].size, in) != 1) {
| 168                 goto fail2;
| 169             };
| 170         };
| 171         *chunkIdx = cidx;
| 172     };
| ...
| 181 }
`----

`gdImageCreateFromGd2Ctx()' and `gdImageCreateFromGd2PartCtx()' then
allocates memory for the compressed data based on the value of the
largest chunk size:

libgd-2.1.1/src/gd_gd2.c:
,----
| 371|637     if (gd2_compressed (fmt)) {
| 372|638         /* Find the maximum compressed chunk size. */
| 373|639         compMax = 0;
| 374|640         for (i = 0; (i < nc); i++) {
| 375|641             if (chunkIdx[i].size > compMax) {
| 376|642                 compMax = chunkIdx[i].size;
| 377|643             };
| 378|644         };
| 379|645         compMax++;
| ...|...
| 387|656         compBuf = gdCalloc (compMax, 1);
| ...|...
| 393|661     };
`----

A size of <= 0 results in `compMax' retaining its initial value during
the loop, followed by it being incremented to 1.  Since `compMax' is
used as the nmemb for `gdCalloc()', this leads to a 1*1 byte allocation
for `compBuf'.

This is followed by compressed data being read to `compBuf' based on the
current (potentially negative) chunk size:

libgd-2.1.1/src/gd_gd2.c:
,----
| 339 BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Ctx (gdIOCtxPtr in)
| 340 {
| ...
| 413         if (gd2_compressed (fmt)) {
| 414
| 415             chunkLen = chunkMax;
| 416
| 417             if (!_gd2ReadChunk (chunkIdx[chunkNum].offset,
| 418                                 compBuf,
| 419                                 chunkIdx[chunkNum].size,
| 420                                 (char *) chunkBuf, &chunkLen, in)) {
| 421                 GD2_DBG (printf ("Error reading comproessed chunk\n"));
| 422                 goto fail;
| 423             };
| 424
| 425             chunkPos = 0;
| 426         };
| ...
| 501 }
`----


libgd-2.1.1/src/gd_gd2.c:
,----
| 585 BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, int h)
| 586 {
| ...
| 713         if (!gd2_compressed (fmt)) {
| ...
| 731         } else {
| 732             chunkNum = cx + cy * ncx;
| 733
| 734             chunkLen = chunkMax;
| 735             if (!_gd2ReadChunk (chunkIdx[chunkNum].offset,
| 736                                 compBuf,
| 737                                 chunkIdx[chunkNum].size,
| 738                                 (char *) chunkBuf, &chunkLen, in)) {
| 739                 printf ("Error reading comproessed chunk\n");
| 740                 goto fail2;
| 741             };
| ...
| 746         };
| ...
| 815 }
`----

The size is subsequently interpreted as a size_t by `fread()' or
`memcpy()', depending on how the image is read:

libgd-2.1.1/src/gd_gd2.c:
,----
| 221 static int
| 222 _gd2ReadChunk (int offset, char *compBuf, int compSize, char *chunkBuf,
| 223            uLongf * chunkLen, gdIOCtx * in)
| 224 {
| ...
| 236     if (gdGetBuf (compBuf, compSize, in) != compSize) {
| 237         return FALSE;
| 238     };
| ...
| 251 }
`----

libgd-2.1.1/src/gd_io.c:
,----
| 211 int gdGetBuf(void *buf, int size, gdIOCtx *ctx)
| 212 {
| 213     return (ctx->getBuf)(ctx, buf, size);
| 214 }
`----


For file contexts:

libgd-2.1.1/src/gd_io_file.c:
,----
|  52 BGD_DECLARE(gdIOCtx *) gdNewFileCtx(FILE *f)
|  53 {
| ...
|  67     ctx->ctx.getBuf = fileGetbuf;
| ...
|  76 }
| ...
|  92 static int fileGetbuf(gdIOCtx *ctx, void *buf, int size)
|  93 {
|  94     fileIOCtx *fctx;
|  95     fctx = (fileIOCtx *)ctx;
|  96
|  97     return (fread(buf, 1, size, fctx->f));
|  98 }
`----


And for dynamic contexts:

libgd-2.1.1/src/gd_io_dp.c:
,----
|  74 BGD_DECLARE(gdIOCtx *) gdNewDynamicCtxEx(int initialSize, void *data, int freeOKFlag)
|  75 {
| ...
|  95     ctx->ctx.getBuf = dynamicGetbuf;
| ...
| 104 }
| ...
| 256 static int dynamicGetbuf(gdIOCtxPtr ctx, void *buf, int len)
| 257 {
| ...
| 280     memcpy(buf, (void *) ((char *)dp->data + dp->pos), rlen);
| ...
| 284 }
`----


PoC
===

Against Ubuntu 15.10 amd64 running nginx with php5-fpm and php5-gd [3]:

,----
| $ python exploit.py --bind-port 5555 http://1.2.3.4/upload.php
| [*] this may take a while
| [*] offset 912 of 10000...
| [+] connected to 1.2.3.4:5555
| id
| uid=33(www-data) gid=33(www-data) groups=33(www-data)
| 
| uname -a
| Linux wily64 4.2.0-35-generic #40-Ubuntu SMP Tue Mar 15 22:15:45 UTC
| 2016 x86_64 x86_64 x86_64 GNU/Linux
| 
| dpkg -l|grep -E "php5-(fpm|gd)"
| ii  php5-fpm       5.6.11+dfsg-1ubuntu3.1 ...
| ii  php5-gd        5.6.11+dfsg-1ubuntu3.1 ...
| 
| cat upload.php
| <?php
|     imagecreatefromgd2($_FILES["file"]["tmp_name"]);
| ?>
`----


Fix is in https://github.com/libgd/libgd/commit/2bb97f407c1145c850416a3bfbcc8cf124e68a19

References:
http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-3074
http://seclists.org/oss-sec/2016/q2/128
https://github.com/dyntopia/exploits/tree/master/CVE-2016-3074]
https://github.com/libgd/libgd/commit/2bb97f407c1145c850416a3bfbcc8cf124e68a19]
Comment 1 Swamp Workflow Management 2016-04-22 22:00:13 UTC
bugbot adjusting priority
Comment 2 Petr Gajdos 2016-04-25 08:12:17 UTC
Created attachment 674340 [details]
testcase from the git commit

Tested on factory, 13.2:

$ cat read.php
<?php
  $im = imagecreatefromgd2("invalid_neg_size.gd2");
?>
$ php read.php
Segmentation fault (core dumped)
$

Tested on sle12:

$ php read.php
PHP Fatal error:  Call to undefined function imagecreatefromgd2() in /976775/read.php on line 2
$

imagecreatefromgd2() is available from 5.6 on. Only 13.2 and factory affected.
Comment 3 Petr Gajdos 2016-04-28 13:46:12 UTC
Packages submitted.
Comment 4 Swamp Workflow Management 2016-05-11 12:07:55 UTC
openSUSE-SU-2016:1274-1: An update that fixes 6 vulnerabilities is now available.

Category: security (important)
Bug References: 976775,976996,976997,977000,977003,977005
CVE References: CVE-2015-8866,CVE-2015-8867,CVE-2016-3074,CVE-2016-4070,CVE-2016-4071,CVE-2016-4073
Sources used:
openSUSE 13.2 (src):    php5-5.6.1-57.1
Comment 5 Swamp Workflow Management 2016-06-11 12:14:52 UTC
openSUSE-SU-2016:1553-1: An update that fixes 13 vulnerabilities is now available.

Category: security (important)
Bug References: 976775,980366,980373,980375,981049,981050,981061,982009,982010,982011,982012,982013,982162
CVE References: CVE-2013-7456,CVE-2015-4116,CVE-2015-8873,CVE-2015-8874,CVE-2015-8876,CVE-2015-8877,CVE-2015-8879,CVE-2016-3074,CVE-2016-5093,CVE-2016-5094,CVE-2016-5095,CVE-2016-5096,CVE-2016-5114
Sources used:
openSUSE 13.2 (src):    php5-5.6.1-66.1
Comment 6 Marcus Meissner 2016-08-01 09:25:56 UTC
released