mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
11 KiB
325 lines
11 KiB
/*
|
|
* Copyright (C) 2013-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
|
|
* Copyright (C) 2011-2013 Sourcefire, Inc.
|
|
* Copyright (C) 1995-2007 by Alexander Lehmann <lehmann@usa.net>,
|
|
* Andreas Dilger <adilger@enel.ucalgary.ca>,
|
|
* Glenn Randers-Pehrson <randeg@alum.rpi.edu>,
|
|
* Greg Roelofs <newt@pobox.com>,
|
|
* John Bowler <jbowler@acm.org>,
|
|
* Tom Lane <tgl@sss.pgh.pa.us>\
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software and its
|
|
* documentation for any purpose and without fee is hereby granted, provided
|
|
* that the above copyright notice appear in all copies and that both that
|
|
* copyright notice and this permission notice appear in supporting
|
|
* documentation. This software is provided "as is" without express or
|
|
* implied warranty.
|
|
*
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
#include "clamav-config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <zlib.h>
|
|
|
|
#include "clamav.h"
|
|
#include "others.h"
|
|
#include "png.h"
|
|
#include "scanners.h"
|
|
|
|
#define BUFFER_SIZE 128000 /* size of read block */
|
|
|
|
cl_error_t cli_parsepng(cli_ctx *ctx)
|
|
{
|
|
uint64_t sz = 0;
|
|
char chunkid[5] = {'\0', '\0', '\0', '\0', '\0'};
|
|
size_t toread = 0, toread_check = 0;
|
|
int32_t c = 0;
|
|
int32_t have_IEND = 0, have_PLTE = 0;
|
|
uint64_t zhead = 1; /* 0x10000 indicates both zlib header bytes read */
|
|
int64_t num_chunks = 0L;
|
|
int64_t w = 0L, h = 0L;
|
|
int32_t bitdepth = 0, sampledepth = 0, lace = 0;
|
|
uint64_t nplte = 0;
|
|
uint32_t ityp = 1;
|
|
uint32_t buffer[BUFFER_SIZE];
|
|
uint64_t offset = 8;
|
|
fmap_t *map = NULL;
|
|
|
|
int64_t cur_xoff, cur_xskip;
|
|
uint64_t cur_width, cur_linebytes, cur_imagesize;
|
|
int32_t err = Z_OK;
|
|
uint32_t *outbuf = NULL;
|
|
z_stream zstrm;
|
|
uint64_t offadjust = 0;
|
|
size_t left_comp_read = 0, uncomp_data = 0;
|
|
|
|
cli_dbgmsg("in cli_parsepng()\n");
|
|
|
|
if (NULL == ctx) {
|
|
cli_dbgmsg("PNG: passed context was NULL\n");
|
|
return CL_EARG;
|
|
}
|
|
map = *ctx->fmap;
|
|
|
|
while (fmap_readn(map, &c, offset, sizeof(c)) == sizeof(c)) {
|
|
|
|
int j = 0;
|
|
for (j = 0; j < 4; ++j) {
|
|
unsigned char c;
|
|
if (fmap_readn(map, &c, offset, sizeof(c)) != sizeof(c)) {
|
|
cli_dbgmsg("PNG: EOF(?) while reading %s\n", "chunk length");
|
|
return CL_CLEAN;
|
|
}
|
|
offset++;
|
|
sz <<= 8;
|
|
sz |= c & 0xff;
|
|
}
|
|
|
|
if (sz > 0x7fffffff) {
|
|
cli_dbgmsg("PNG: invalid chunk length (too large)\n");
|
|
return CL_EPARSE;
|
|
}
|
|
|
|
if (fmap_readn(map, chunkid, offset, 4) != 4) {
|
|
cli_dbgmsg("PNG: EOF while reading chunk type\n");
|
|
return CL_EPARSE;
|
|
}
|
|
offset += 4;
|
|
|
|
/* GRR: add 4-character EBCDIC conversion here (chunkid) */
|
|
|
|
chunkid[4] = '\0';
|
|
++num_chunks;
|
|
|
|
toread = (sz > BUFFER_SIZE) ? BUFFER_SIZE : sz;
|
|
toread_check = fmap_readn(map, buffer, offset, toread);
|
|
if ((size_t)-1 == toread_check) {
|
|
cli_dbgmsg("PNG: Failed to read from map.\n");
|
|
return CL_EPARSE;
|
|
}
|
|
if (toread > toread_check) {
|
|
cli_dbgmsg("PNG: EOF while reading data\n");
|
|
return CL_EPARSE;
|
|
}
|
|
toread = toread_check;
|
|
|
|
offset += toread;
|
|
|
|
/*------*
|
|
| IHDR |
|
|
*------*/
|
|
if (strcmp(chunkid, "IHDR") == 0) {
|
|
if (sz != 13) {
|
|
cli_dbgmsg("PNG: invalid IHDR length\n");
|
|
break;
|
|
} else {
|
|
w = be32_to_host(*buffer);
|
|
h = be32_to_host(*(buffer + 4));
|
|
if (w <= 0 || h <= 0 || w > 2147483647 || h > 2147483647) {
|
|
cli_dbgmsg("PNG: invalid image dimensions\n");
|
|
break;
|
|
}
|
|
bitdepth = sampledepth = (uint32_t)buffer[8];
|
|
ityp = (uint32_t)buffer[9];
|
|
lace = (uint32_t)buffer[12];
|
|
switch (sampledepth) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
if (ityp == 2 || ityp == 4 || ityp == 6) { /* RGB or GA or RGBA */
|
|
cli_dbgmsg("PNG: invalid sample depth (%d)\n", sampledepth);
|
|
break;
|
|
}
|
|
break;
|
|
case 8:
|
|
break;
|
|
case 16:
|
|
if (ityp == 3) { /* palette */
|
|
cli_dbgmsg("PNG: invalid sample depth (%d)\n", sampledepth);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
cli_dbgmsg("PNG: invalid sample depth (%d)\n", sampledepth);
|
|
break;
|
|
}
|
|
switch (ityp) {
|
|
case 2:
|
|
bitdepth = sampledepth * 3; /* RGB */
|
|
break;
|
|
case 4:
|
|
bitdepth = sampledepth * 2; /* gray+alpha */
|
|
break;
|
|
case 6:
|
|
bitdepth = sampledepth * 4; /* RGBA */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* GRR 20000304: data dump not yet compatible with interlaced images: */
|
|
/*================================================*
|
|
* PNG chunks (with the exception of IHDR, above) *
|
|
*================================================*/
|
|
|
|
}
|
|
/*------*
|
|
| PLTE |
|
|
*------*/
|
|
else if (strcmp(chunkid, "PLTE") == 0) {
|
|
if (!(sz > 768 || sz % 3 != 0)) {
|
|
nplte = sz / 3;
|
|
}
|
|
if (ityp == 1) /* for MNG and tRNS */
|
|
ityp = 3;
|
|
have_PLTE = 1;
|
|
|
|
}
|
|
/*------*
|
|
| IDAT |
|
|
*------*/
|
|
else if (lace == 0 && strcmp(chunkid, "IDAT") == 0) {
|
|
unsigned zlib_windowbits = 15;
|
|
|
|
/* Dump the zlib header from the first two bytes. */
|
|
if (zhead < 0x10000 && sz > 0) {
|
|
zhead = (zhead << 8) + buffer[0];
|
|
if (sz > 1 && zhead < 0x10000)
|
|
zhead = (zhead << 8) + buffer[1];
|
|
if (zhead >= 0x10000) {
|
|
unsigned int CINFO = (zhead & 0xf000) >> 12;
|
|
zlib_windowbits = CINFO + 8;
|
|
}
|
|
}
|
|
|
|
outbuf = (uint32_t *)malloc(BUFFER_SIZE);
|
|
offadjust = offset + sz - 8;
|
|
left_comp_read = MIN(map->len - offset + sz - 8, sz);
|
|
|
|
zstrm.next_in = (uint8_t *)buffer;
|
|
zstrm.avail_in = MIN(toread, left_comp_read);
|
|
left_comp_read -= zstrm.avail_in;
|
|
|
|
/* initialize zlib and bit/byte/line variables if not already done */
|
|
zstrm.zalloc = (alloc_func)Z_NULL;
|
|
zstrm.zfree = (free_func)Z_NULL;
|
|
zstrm.opaque = (voidpf)Z_NULL;
|
|
if ((err = inflateInit2(&zstrm, zlib_windowbits)) != Z_OK) {
|
|
cli_dbgmsg("PNG: zlib: can't initialize (error = %d)\n", err);
|
|
if (outbuf) {
|
|
free(outbuf);
|
|
outbuf = NULL;
|
|
}
|
|
} else {
|
|
cur_xoff = 0;
|
|
cur_xskip = lace ? 8 : 1;
|
|
cur_width = (w - cur_xoff + cur_xskip - 1) / cur_xskip; /* round up */
|
|
cur_linebytes = ((cur_width * bitdepth + 7) >> 3) + 1; /* round, fltr */
|
|
cur_imagesize = cur_linebytes * h;
|
|
|
|
while (err != Z_STREAM_END) {
|
|
if (zstrm.avail_in == 0) {
|
|
// The zlib stream is over. Quit the while loop
|
|
if (left_comp_read == 0)
|
|
break;
|
|
|
|
toread = MIN(sizeof(buffer), left_comp_read);
|
|
toread_check = fmap_readn(map, buffer, offset, toread);
|
|
if ((size_t)-1 == toread_check) {
|
|
cli_dbgmsg("PNG: Failed to read from map.\n");
|
|
if (outbuf) {
|
|
free(outbuf);
|
|
outbuf = NULL;
|
|
}
|
|
return CL_EPARSE;
|
|
}
|
|
if (toread > toread_check) {
|
|
cli_dbgmsg("PNG: EOF while reading data\n");
|
|
if (outbuf) {
|
|
free(outbuf);
|
|
outbuf = NULL;
|
|
}
|
|
return CL_EPARSE;
|
|
}
|
|
toread = toread_check;
|
|
offset += toread;
|
|
zstrm.next_in = (uint8_t *)buffer;
|
|
zstrm.avail_in = toread;
|
|
left_comp_read -= toread;
|
|
}
|
|
|
|
zstrm.next_out = (uint8_t *)outbuf;
|
|
zstrm.avail_out = BUFFER_SIZE;
|
|
err = inflate(&zstrm, Z_NO_FLUSH);
|
|
uncomp_data += (BUFFER_SIZE - zstrm.avail_out);
|
|
if (err != Z_OK && err != Z_STREAM_END) {
|
|
cli_dbgmsg("PNG: zlib: inflate error\n");
|
|
break;
|
|
}
|
|
}
|
|
inflateEnd(&zstrm);
|
|
if (outbuf) {
|
|
free(outbuf);
|
|
outbuf = NULL;
|
|
}
|
|
|
|
if (uncomp_data > cur_imagesize && err == Z_STREAM_END) {
|
|
cli_append_virus(ctx, "Heuristics.PNG.CVE-2010-1205");
|
|
return CL_VIRUS;
|
|
}
|
|
}
|
|
|
|
}
|
|
/*------*
|
|
| IEND |
|
|
*------*/
|
|
else if (strcmp(chunkid, "IEND") == 0) {
|
|
|
|
have_IEND = 1;
|
|
break;
|
|
|
|
}
|
|
/*------*
|
|
| pHYs |
|
|
*------*/
|
|
else if (strcmp(chunkid, "pHYs") == 0) {
|
|
|
|
if (sz != 9) {
|
|
// Could it be CVE-2007-2365?
|
|
cli_dbgmsg("PNG: invalid pHYS length\n");
|
|
}
|
|
}
|
|
/*------*
|
|
| tRNS |
|
|
*------*/
|
|
else if (strcmp(chunkid, "tRNS") == 0) {
|
|
|
|
if (ityp == 3) {
|
|
if ((sz > 256 || sz > nplte) && !have_PLTE) {
|
|
cli_append_virus(ctx, "Heuristics.PNG.CVE-2004-0597");
|
|
return CL_VIRUS;
|
|
}
|
|
|
|
offset += (sz - toread) + 4;
|
|
}
|
|
|
|
// Is there an overlay?
|
|
if (have_IEND && (map->len - (offset + 4) > 0))
|
|
return cli_magic_scan_nested_fmap_type(map, offset + 4, map->len - (offset + 4), ctx, CL_TYPE_ANY, NULL);
|
|
|
|
return CL_SUCCESS;
|
|
}
|
|
}
|
|
return CL_SUCCESS;
|
|
}
|
|
|