denyhosts/clamav/libclamav/jpeg.c

706 lines
28 KiB
C
Raw Normal View History

2022-10-22 18:41:00 +08:00
/*
* Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2011-2013 Sourcefire, Inc.
*
* Authors: Tomasz Kojm <tkojm@clamav.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdbool.h>
#include <time.h>
#include "jpeg.h"
#include "clamav.h"
#include "scanners.h"
// clang-format off
/*
* JPEG format highlights
* ----------------------
*
* Links:
* - https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure
* - https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
* - https://en.wikipedia.org/wiki/Exif
*
* A JPEG image is a sequence of segments.
*
* Each segment starts with a two-byte marker. The first byte is 0xff and is
* followed by one of the following to identify the segment.
* Some segments are simply the 2-byte marker, while others have a payload.
* Realistically it appears that just the start-of-image and end-of-image lack
* the 2-byte size field, the rest have it, even the 4-byte DRI segment.
*
* All variable-byte payloads have 2-bytes indicating the size which includes
* the 2-bytes (but not the marker itself).
*
* Within entropy-encoded (compressed) data, any 0xff will have an 0x00
* inserted after it to indicate that it's just and 0xff and _NOT_ a segment
* marker. Decoders skip the 0x00 byte.
* This only applies to entropy-encoded data, not to marker payload data.
* We dont' really worry about this though because this parser stops when it
* reaches the image data.
*/
/*
* JPEG Segment & Entropy Markers.
*/
typedef enum {
/* Start of Image
* No payload
*/
JPEG_MARKER_SEGMENT_SOI_START_OF_IMAGE = 0xD8,
/* Start of Frame for a Baseline DCT-based JPEG (S0F0)
* Variable size payload.
* Baseline DCT-based JPEG, and specifies the width, height, number of
* components, and component subsampling
*/
JPEG_MARKER_SEGMENT_S0F0_START_OF_FRAME_BASELINE_DCT = 0xC0,
/* Start of Frame for a extended sequential DCT-based JPEG (S0F1)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F1_START_OF_FRAME_EXT_SEQ_DCT = 0xC1,
/* Start of Frame for a progressive DCT-based JPEG (S0F2)
* Variable size payload.
* Progressive DCT-based JPEG, and specifies the width, height, number of
* components, and component subsampling
*/
JPEG_MARKER_SEGMENT_S0F2_START_OF_FRAME_PROG_DCT = 0xC2,
/* Start of Frame for a lossless sequential DCT-based JPEG (S0F3)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F3_START_OF_FRAME_DIFF_SEQ_DCT = 0xC3,
/* Start of Frame for a differential sequential DCT-based JPEG (S0F5)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F5_START_OF_FRAME_DIFF_SEQ_DCT = 0xC5,
/* Start of Frame for a differential progressive DCT-based JPEG (S0F6)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F6_START_OF_FRAME_DIFF_PROG_DCT = 0xC6,
/* Start of Frame for a differential lossless DCT-based JPEG (S0F7)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F7_START_OF_FRAME_DIFF_LOSSLESS_DCT = 0xC7,
/* Start of Frame for a differential sequential arithmatic-based JPEG (S0F5)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F9_START_OF_FRAME_DIFF_SEQ_ARITH = 0xC9,
/* Start of Frame for a differential progressive arithmatic-based JPEG (S0F6)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F10_START_OF_FRAME_DIFF_PROG_ARITH = 0xCA,
/* Start of Frame for a differential lossless arithmatic-based JPEG (S0F7)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_S0F11_START_OF_FRAME_DIFF_LOSSLESS_ARITH = 0xCB,
/* Define Huffman Tables (DHT)
* Variable size payload.
* Defines one or more Huffman tables.
*/
JPEG_MARKER_SEGMENT_DHT_DEFINE_HUFFMAN_TABLES = 0xC4,
/* Define Arithmatic Coding Conditioning (DAC)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_DHT_DEFINE_ARITH_CODING = 0xCC,
/* Define Quantization Tables (DTQ)
* Variable size payload.
* Defines one or more quantization tables.
*/
JPEG_MARKER_SEGMENT_DQT_DEFINE_QUANTIZATION_TABLES = 0xDB,
/* Define Restart Interval (DRI)
* 4-byte payload.
* Specifies the interval between RSTn markers, in Minimum Coded Units (MCUs).
* This marker is followed by two bytes indicating the fixed size so it can be
* treated like any other variable size segment.
*/
JPEG_MARKER_SEGMENT_DRI_DEFINE_RESTART_INTERVAL = 0xDD,
/* Start of Scan (SOS)
* Variable size payload
* This is the start of the JPEG image data, so we'll actually stop parsing
* when we reach this.
*/
JPEG_MARKER_SEGMENT_SOS_START_OF_SCAN = 0xDA,
/*
* App-specific markers E0 - EF
* Variable size payload.
* Since several vendors might use the *same* APPn marker type, application-
* specific markers often begin with a standard or vendor name (e.g., "Exif" or
* "Adobe") or some other identifying string.
*
* Some known app specific markers include:
* 0xE0:
* - JFIF
* 0xE1:
* - Exif
* - XMP data, starts with http://ns.adobe.com/xap/1.0/\0
* 0xE2:
* - ICC Profile Chunk. There could be multiple of these to fit the entire profile, see http://www.color.org/icc_specs2.xalter and http://www.color.org/specification/ICC1v43_2010-12.pdf Section B.4
* 0xE8:
* - SPIFF. Not a common format, see http://fileformats.archiveteam.org/wiki/SPIFF
* 0xED:
* - IPTC / IMM metadata (a type of comment)
* - Photoshop data
* 0xEE:
* - AdobeRGB (as opposed to sRGB)
*/
JPEG_MARKER_SEGMENT_APP0 = 0xE0,
JPEG_MARKER_SEGMENT_APP1 = 0xE1,
JPEG_MARKER_SEGMENT_APP2 = 0xE2,
JPEG_MARKER_SEGMENT_APP3 = 0xE3,
JPEG_MARKER_SEGMENT_APP4 = 0xE4,
JPEG_MARKER_SEGMENT_APP5 = 0xE5,
JPEG_MARKER_SEGMENT_APP6 = 0xE6,
JPEG_MARKER_SEGMENT_APP7 = 0xE7,
JPEG_MARKER_SEGMENT_APP8 = 0xE8,
JPEG_MARKER_SEGMENT_APP9 = 0xE9,
JPEG_MARKER_SEGMENT_APP10 = 0xEA,
JPEG_MARKER_SEGMENT_APP11 = 0xEB,
JPEG_MARKER_SEGMENT_APP12 = 0xEC,
JPEG_MARKER_SEGMENT_APP13 = 0xED,
JPEG_MARKER_SEGMENT_APP14 = 0xEE,
JPEG_MARKER_SEGMENT_APP15 = 0xEF,
/* DTI (?)
*
*/
JPEG_MARKER_SEGMENT_DTI = 0xF1,
/* DTT (?)
*
*/
JPEG_MARKER_SEGMENT_DTT = 0xF2,
/* JPG7
* Variable size payload (?)
*/
JPEG_MARKER_SEGMENT_JPG7 = 0xF7,
/* Comment (COM)
* Variable size payload.
*/
JPEG_MARKER_SEGMENT_COM_COMMENT = 0xFE,
/* End of Image
* No payload
*/
JPEG_MARKER_SEGMENT_EOI_END_OF_IMAGE = 0xD9,
/* Entropy-encoded (aka compressed) data markers.
*
* These aren't referenced since we don't parse the image data.
*/
JPEG_MARKER_NOT_A_MARKER_0x00 = 0x00,
JPEG_MARKER_NOT_A_MARKER_0xFF = 0xFF,
/* Reset entropy-markers are inserted every r macroblocks, where r is the restart interval set by a DRI marker.
* Not used if there was no DRI segment-marker.
* The low three bits of the marker code cycle in value from 0 to 7 (i.e. D0 - D7).
*/
JPEG_MARKER_ENTROPY_RST0_RESET = 0xD0,
JPEG_MARKER_ENTROPY_RST1_RESET = 0xD1,
JPEG_MARKER_ENTROPY_RST2_RESET = 0xD2,
JPEG_MARKER_ENTROPY_RST3_RESET = 0xD3,
JPEG_MARKER_ENTROPY_RST4_RESET = 0xD4,
JPEG_MARKER_ENTROPY_RST5_RESET = 0xD5,
JPEG_MARKER_ENTROPY_RST6_RESET = 0xD6,
JPEG_MARKER_ENTROPY_RST7_RESET = 0xD7,
} jpeg_marker_t;
// clang-format on
static cl_error_t jpeg_check_photoshop_8bim(cli_ctx *ctx, size_t *off)
{
cl_error_t retval;
const unsigned char *buf;
uint16_t ntmp;
uint8_t nlength, id[2];
uint32_t size;
size_t offset = *off;
fmap_t *map = ctx->fmap;
if (!(buf = fmap_need_off_once(map, offset, 4 + 2 + 1))) {
cli_dbgmsg("read bim failed\n");
return CL_BREAK;
}
if (memcmp(buf, "8BIM", 4) != 0) {
cli_dbgmsg("missed 8bim\n");
return CL_BREAK;
}
id[0] = (uint8_t)buf[4];
id[1] = (uint8_t)buf[5];
cli_dbgmsg("ID: 0x%.2x%.2x\n", id[0], id[1]);
nlength = buf[6];
ntmp = nlength + ((((uint16_t)nlength) + 1) & 0x01);
offset += 4 + 2 + 1 + ntmp;
if (fmap_readn(map, &size, offset, 4) != 4) {
return CL_BREAK;
}
size = be32_to_host(size);
if (size == 0) {
return CL_BREAK;
}
if ((size & 0x01) == 1) {
size++;
}
*off = offset + 4 + size;
/* Is it a thumbnail image: 0x0409 or 0x040c */
if ((id[0] == 0x04) && ((id[1] == 0x09) || (id[1] == 0x0c))) {
/* Yes */
cli_dbgmsg("found thumbnail\n");
} else {
/* No - Seek past record */
return CL_CLEAN;
}
/* Jump past header */
offset += 4 + 28;
/* Scan the thumbnail JPEG */
2023-01-14 18:28:39 +08:00
retval = cli_magic_scan_nested_fmap_type(map, offset, 0, ctx, CL_TYPE_JPEG,
"photoshop-thumbnail", LAYER_ATTRIBUTES_NONE);
2022-10-22 18:41:00 +08:00
return retval;
}
cl_error_t cli_parsejpeg(cli_ctx *ctx)
{
2023-01-14 18:28:39 +08:00
cl_error_t status = CL_SUCCESS;
2022-10-22 18:41:00 +08:00
fmap_t *map = NULL;
jpeg_marker_t marker, prev_marker, prev_segment = JPEG_MARKER_NOT_A_MARKER_0x00;
uint8_t buff[50]; /* 50 should be sufficient for now */
uint16_t len_u16;
unsigned int offset = 0, i, len, segment = 0;
bool found_comment = false;
bool found_app = false;
uint32_t num_JFIF = 0;
uint32_t num_Exif = 0;
uint32_t num_SPIFF = 0;
cli_dbgmsg("in cli_parsejpeg()\n");
if (NULL == ctx) {
cli_dbgmsg("passed context was NULL\n");
status = CL_EARG;
goto done;
}
map = ctx->fmap;
2023-01-14 18:28:39 +08:00
if (fmap_readn(map, buff, offset, 4) != 4) {
2022-10-22 18:41:00 +08:00
goto done; /* Ignore */
2023-01-14 18:28:39 +08:00
}
2022-10-22 18:41:00 +08:00
2023-01-14 18:28:39 +08:00
if (!memcmp(buff, "\xff\xd8\xff", 3)) {
2022-10-22 18:41:00 +08:00
offset = 2;
2023-01-14 18:28:39 +08:00
} else if (!memcmp(buff, "\xff\xd9\xff\xd8", 4)) {
2022-10-22 18:41:00 +08:00
offset = 4;
2023-01-14 18:28:39 +08:00
} else {
2022-10-22 18:41:00 +08:00
goto done; /* Not a JPEG file */
2023-01-14 18:28:39 +08:00
}
2022-10-22 18:41:00 +08:00
while (1) {
segment++;
prev_marker = JPEG_MARKER_NOT_A_MARKER_0x00;
for (i = 0; offset < map->len && i < 16; i++) {
uint8_t marker_u8;
if (fmap_readn(map, &marker_u8, offset, sizeof(marker_u8)) == sizeof(marker_u8)) {
offset += sizeof(marker_u8);
} else {
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_errmsg("JPEG: Failed to read marker, file corrupted?\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadMarker");
2022-10-22 18:41:00 +08:00
} else {
cli_dbgmsg("Failed to read marker, file corrupted?\n");
}
goto done;
}
marker = (jpeg_marker_t)marker_u8;
if (prev_marker == JPEG_MARKER_NOT_A_MARKER_0xFF && marker != JPEG_MARKER_NOT_A_MARKER_0xFF)
break;
prev_marker = marker;
}
if (i == 16) {
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_warnmsg("JPEG: Spurious bytes before segment %u\n", segment);
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SpuriousBytesBeforeSegment");
2022-10-22 18:41:00 +08:00
} else {
cli_dbgmsg("Spurious bytes before segment %u\n", segment);
}
goto done;
}
/*
* Check for MS04-028 exploit (See: https://docs.microsoft.com/en-us/security-updates/securitybulletins/2004/ms04-028)
* You can reproduce to test with https://www.exploit-db.com/exploits/474
* Checking here because the exploit PoC will fail our length check, below.
*/
if (JPEG_MARKER_SEGMENT_COM_COMMENT == marker) {
if (fmap_readn(map, buff, offset, 2) == 2) {
if (buff[0] == 0x00) {
if ((buff[1] == 0x00) || (buff[1] == 0x01)) {
/* Found exploit */
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Exploit.W32.MS04-028");
2022-10-22 18:41:00 +08:00
goto done;
}
}
}
}
if (fmap_readn(map, &len_u16, offset, sizeof(len_u16)) != sizeof(len_u16)) {
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_errmsg("JPEG: Failed to read the segment size, file corrupted?\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.CantReadSegmentSize");
2022-10-22 18:41:00 +08:00
} else {
cli_dbgmsg("Failed to read the segment size, file corrupted?\n");
}
goto done;
}
len = (unsigned int)be16_to_host(len_u16);
cli_dbgmsg("segment[%d] = 0x%02x, Length %u\n", segment, marker, len);
if (len < 2) {
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_warnmsg("JPEG: Invalid segment size\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.InvalidSegmentSize");
2022-10-22 18:41:00 +08:00
} else {
cli_dbgmsg("Invalid segment size\n");
}
goto done;
}
if (len >= map->len - offset + sizeof(len_u16)) {
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_warnmsg("JPEG: Segment data out of file\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SegmentDataOutOfFile");
2022-10-22 18:41:00 +08:00
} else {
cli_dbgmsg("Segment data out of file\n");
}
goto done;
}
offset += len;
switch (marker) {
case JPEG_MARKER_SEGMENT_APP0:
/*
* JFIF, maybe
*/
if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("JFIF") + 1) == strlen("JFIF") + 1) &&
(0 == memcmp(buff, "JFIF\0", strlen("JFIF") + 1))) {
/* Found a JFIF marker */
cli_dbgmsg(" JFIF application marker\n");
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
if (found_app && num_JFIF > 0) {
cli_warnmsg("JPEG: Duplicate Application Marker found (JFIF)\n");
cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF);
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFdupAppMarker");
2022-10-22 18:41:00 +08:00
goto done;
}
if (!(segment == 1 ||
(segment == 2 && found_comment) ||
(segment == 2 && num_Exif > 0) ||
(segment == 3 && found_comment && num_Exif > 0))) {
/* The JFIF segment is technically required to appear first, though it has been observed
* appearing in segment 2 in functional images when segment 1 is a comment or an Exif segment.
* If segment 1 wasn't a comment or Exif, then the file structure is unusual. */
cli_warnmsg("JPEG: JFIF marker at wrong position, found in segment # %d\n", segment);
cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF);
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFmarkerBadPosition");
2022-10-22 18:41:00 +08:00
goto done;
}
if (len < 16) {
cli_warnmsg("JPEG: JFIF header too short\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.JFIFheaderTooShort");
2022-10-22 18:41:00 +08:00
goto done;
}
}
found_app = true;
num_JFIF += 1;
} else {
/* Found something else. Eg could be an Ocad Revision # (eg "Ocad$Rev: 14797 $"), for example.
Whatever it is, we don't really care for now */
cli_dbgmsg(" Unfamiliar use of application marker: 0x%02x\n", marker);
}
break;
case JPEG_MARKER_SEGMENT_APP1:
/*
* Exif, or maybe XMP data
*/
if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("Exif") + 2) == strlen("Exif") + 2) &&
(0 == memcmp(buff, "Exif\0\0", strlen("Exif") + 2))) {
/* Found an Exif marker */
cli_dbgmsg(" Exif application marker\n");
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
if (found_app && (num_Exif > 0 || num_SPIFF > 0)) {
cli_warnmsg("JPEG: Duplicate Application Marker found (Exif)\n");
cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF);
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifDupAppMarker");
2022-10-22 18:41:00 +08:00
goto done;
}
if (segment > 3 && !found_comment && num_JFIF > 0) {
/* If Exif was found after segment 3 and previous segments weren't a comment or JFIF, something is unusual. */
cli_warnmsg("JPEG: Exif marker at wrong position\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderBadPosition");
2022-10-22 18:41:00 +08:00
goto done;
}
if (len < 16) {
cli_warnmsg("JPEG: Exif header too short\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.ExifHeaderTooShort");
2022-10-22 18:41:00 +08:00
goto done;
}
}
found_app = true;
num_Exif += 1;
} else if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("http://")) == strlen("http://")) &&
(0 == memcmp(buff, "http://", strlen("http://")))) {
cli_dbgmsg(" XMP metadata\n");
found_comment = true;
} else {
cli_dbgmsg(" Unfamiliar use of application marker: 0x%02x\n", marker);
}
break;
case JPEG_MARKER_SEGMENT_APP2:
/*
* ICC Profile
*/
if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("ICC_PROFILE") + 2) == strlen("ICC_PROFILE") + 2) &&
(0 == memcmp(buff, "ICC_PROFILE\0", strlen("ICC_PROFILE") + 1))) {
/* Found ICC Profile Chunk. Let's print out the chunk #, which follows "ICC_PROFILE\0"... */
uint8_t chunk_no = buff[strlen("ICC_PROFILE") + 1];
cli_dbgmsg(" ICC Profile, chunk # %d\n", chunk_no);
} else {
cli_dbgmsg(" Unfamiliar use of application marker: 0x%02x\n", marker);
}
break;
case JPEG_MARKER_SEGMENT_APP8:
/*
* SPIFF
*/
if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("SPIFF") + 1) == strlen("SPIFF") + 1) &&
(0 == memcmp(buff, "SPIFF\0", strlen("SPIFF") + 1))) {
/* Found SPIFF application marker */
cli_dbgmsg(" SPIFF application marker\n");
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
if (found_app) {
cli_warnmsg("JPEG: Duplicate Application Marker found (SPIFF)\n");
cli_warnmsg("JPEG: Already observed JFIF: %d, Exif: %d, SPIFF: %d\n", num_JFIF, num_Exif, num_SPIFF);
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFdupAppMarker");
2022-10-22 18:41:00 +08:00
goto done;
}
if (segment != 1 && (segment != 2 || !found_comment)) {
cli_warnmsg("JPEG: SPIFF marker at wrong position\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFmarkerBadPosition");
2022-10-22 18:41:00 +08:00
goto done;
}
if (len < 16) {
cli_warnmsg("JPEG: SPIFF header too short\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.SPIFFheaderTooShort");
2022-10-22 18:41:00 +08:00
goto done;
}
}
found_app = true;
num_SPIFF += 1;
} else {
cli_dbgmsg(" Unfamiliar use of application marker: 0x%02x\n", marker);
}
break;
case JPEG_MARKER_SEGMENT_APP13:
/*
* Check for Photoshop information
* Example file to test with: 2c5883a964917aa54c8b3e2c70dabf0a7b06ba8c21bcbaf6f1c19501be9d9196
*/
if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("Photoshop 3.0") + 1) == strlen("Photoshop 3.0") + 1) &&
(0 == memcmp(buff, "Photoshop 3.0\0", strlen("Photoshop 3.0") + 1))) {
/* Found a Photoshop file */
size_t photoshop_data_offset = offset - len + sizeof(len_u16) + strlen("Photoshop 3.0") + 1;
size_t old_offset;
cli_dbgmsg("Found Photoshop segment\n");
do {
old_offset = photoshop_data_offset;
status = jpeg_check_photoshop_8bim(ctx, &photoshop_data_offset);
if (photoshop_data_offset <= old_offset)
break;
} while (status == CL_CLEAN);
if (status == CL_BREAK) {
status = CL_CLEAN;
}
} else {
cli_dbgmsg(" Unfamiliar use of application marker: 0x%02x\n", marker);
}
found_comment = true;
break;
case JPEG_MARKER_SEGMENT_APP14:
/*
* Adobe RGB, probably
*/
if ((fmap_readn(map, buff, offset - len + sizeof(len_u16), strlen("Adobe") + 1) == strlen("Adobe") + 1) &&
(0 == memcmp(buff, "Adobe\0", strlen("Adobe") + 1))) {
cli_dbgmsg(" AdobeRGB application marker\n");
} else {
/* Not Adobe, dunno what this is. */
cli_dbgmsg(" Unfamiliar use of application marker: 0x%02x\n", marker);
}
break;
case JPEG_MARKER_SEGMENT_APP3:
case JPEG_MARKER_SEGMENT_APP4:
case JPEG_MARKER_SEGMENT_APP5:
case JPEG_MARKER_SEGMENT_APP6:
case JPEG_MARKER_SEGMENT_APP7:
case JPEG_MARKER_SEGMENT_APP9:
case JPEG_MARKER_SEGMENT_APP10:
case JPEG_MARKER_SEGMENT_APP11:
case JPEG_MARKER_SEGMENT_APP12:
case JPEG_MARKER_SEGMENT_APP15:
/*
* Unknown
*/
cli_dbgmsg(" Unfamiliar application marker: 0x%02x\n", marker);
break;
case JPEG_MARKER_SEGMENT_S0F0_START_OF_FRAME_BASELINE_DCT:
case JPEG_MARKER_SEGMENT_S0F1_START_OF_FRAME_EXT_SEQ_DCT:
case JPEG_MARKER_SEGMENT_S0F2_START_OF_FRAME_PROG_DCT:
case JPEG_MARKER_SEGMENT_S0F3_START_OF_FRAME_DIFF_SEQ_DCT:
case JPEG_MARKER_SEGMENT_S0F5_START_OF_FRAME_DIFF_SEQ_DCT:
case JPEG_MARKER_SEGMENT_S0F6_START_OF_FRAME_DIFF_PROG_DCT:
case JPEG_MARKER_SEGMENT_S0F7_START_OF_FRAME_DIFF_LOSSLESS_DCT:
case JPEG_MARKER_SEGMENT_S0F9_START_OF_FRAME_DIFF_SEQ_ARITH:
case JPEG_MARKER_SEGMENT_S0F10_START_OF_FRAME_DIFF_PROG_ARITH:
case JPEG_MARKER_SEGMENT_S0F11_START_OF_FRAME_DIFF_LOSSLESS_ARITH:
cli_dbgmsg(" Start of Frame (S0F) %02x\n", (uint8_t)marker);
break;
case JPEG_MARKER_SEGMENT_DHT_DEFINE_HUFFMAN_TABLES:
cli_dbgmsg(" Huffman Tables definitions (DHT)\n");
break;
case JPEG_MARKER_SEGMENT_DQT_DEFINE_QUANTIZATION_TABLES:
cli_dbgmsg(" Quantization Tables definitions (DQT)\n");
break;
case JPEG_MARKER_SEGMENT_DRI_DEFINE_RESTART_INTERVAL:
cli_dbgmsg(" Restart Interval definition (DRI)\n");
break;
case JPEG_MARKER_SEGMENT_JPG7: /* JPG7 */
cli_dbgmsg(" JPG7 segment marker\n");
if (found_app) {
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_warnmsg("JPEG: Application Marker before JPG7\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.AppMarkerBeforeJPG7");
2022-10-22 18:41:00 +08:00
goto done;
}
}
goto done;
case JPEG_MARKER_SEGMENT_SOS_START_OF_SCAN: /* SOS */
cli_dbgmsg(" Start of Scan (SOS) segment marker\n");
if (!found_app) {
cli_dbgmsg(" Found the Start-of-Scan segment without identifying the JPEG application type.\n");
}
/* What follows would be scan data (compressed image data),
* parsing is not presently required for validation purposes
* so we'll just call it quits. */
goto done;
case JPEG_MARKER_SEGMENT_EOI_END_OF_IMAGE: /* EOI (End of Image) */
cli_dbgmsg(" End of Image (EOI) segment marker\n");
/*
* We shouldn't reach this marker because we exit out when we hit the Start of Scan marker.
*/
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
cli_warnmsg("JPEG: No image in jpeg\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.NoImages");
2022-10-22 18:41:00 +08:00
}
goto done;
case JPEG_MARKER_SEGMENT_COM_COMMENT: /* COM (comment) */
cli_dbgmsg(" Comment (COM) segment marker\n");
found_comment = true;
break;
case JPEG_MARKER_SEGMENT_DTI: /* DTI */
cli_dbgmsg(" DTI segment marker\n");
break;
case JPEG_MARKER_SEGMENT_DTT: /* DTT */
cli_dbgmsg(" DTT segment marker\n");
if (SCAN_HEURISTIC_BROKEN_MEDIA) {
if (prev_segment != JPEG_MARKER_SEGMENT_DTI) {
cli_warnmsg("JPEG: No DTI segment before DTT\n");
2023-01-14 18:28:39 +08:00
status = cli_append_potentially_unwanted(ctx, "Heuristics.Broken.Media.JPEG.DTTMissingDTISegment");
2022-10-22 18:41:00 +08:00
goto done;
}
}
break;
default:
/* Some unknown marker we don't presently handle, don't worry about it. */
break;
}
prev_segment = marker;
}
done:
return status;
}