/* * 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 , * Andreas Dilger , * Glenn Randers-Pehrson , * Greg Roelofs , * John Bowler , * Tom Lane \ * * 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 #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #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; }