diff --git a/libclamav/CMakeLists.txt b/libclamav/CMakeLists.txt index b92ff765e7..09c4f80714 100644 --- a/libclamav/CMakeLists.txt +++ b/libclamav/CMakeLists.txt @@ -395,6 +395,8 @@ set(LIBCLAMAV_SOURCES tiff.c tiff.h # GIF image format checker gif.c gif.h + # UDF partition + udf.c udf.h ) if(ENABLE_SHARED_LIB) diff --git a/libclamav/dconf.c b/libclamav/dconf.c index abfa456297..fc1a119593 100644 --- a/libclamav/dconf.c +++ b/libclamav/dconf.c @@ -106,6 +106,7 @@ static struct dconf_module modules[] = { {"ARCHIVE", "GPT", ARCH_CONF_GPT, 1}, {"ARCHIVE", "APM", ARCH_CONF_APM, 1}, {"ARCHIVE", "EGG", ARCH_CONF_EGG, 1}, + {"ARCHIVE", "UDF", ARCH_CONF_UDF, 1}, {"DOCUMENT", "HTML", DOC_CONF_HTML, 1}, {"DOCUMENT", "RTF", DOC_CONF_RTF, 1}, diff --git a/libclamav/dconf.h b/libclamav/dconf.h index 9867940b9b..7bb5fabd08 100644 --- a/libclamav/dconf.h +++ b/libclamav/dconf.h @@ -96,6 +96,7 @@ struct cli_dconf { #define ARCH_CONF_GPT 0x1000000 #define ARCH_CONF_APM 0x2000000 #define ARCH_CONF_EGG 0x4000000 +#define ARCH_CONF_UDF 0x8000000 /* Document flags */ #define DOC_CONF_HTML 0x1 diff --git a/libclamav/filetypes.c b/libclamav/filetypes.c index 16d0d6c75c..f370fd23a9 100644 --- a/libclamav/filetypes.c +++ b/libclamav/filetypes.c @@ -137,6 +137,7 @@ static const struct ftmap_s { { "CL_TYPE_LNK", CL_TYPE_LNK }, { "CL_TYPE_EGG", CL_TYPE_EGG }, { "CL_TYPE_EGGSFX", CL_TYPE_EGGSFX }, + { "CL_TYPE_UDF", CL_TYPE_UDF }, { NULL, CL_TYPE_IGNORED } }; // clang-format on diff --git a/libclamav/filetypes.h b/libclamav/filetypes.h index 9ffb8565c5..449fb00fa2 100644 --- a/libclamav/filetypes.h +++ b/libclamav/filetypes.h @@ -122,7 +122,7 @@ typedef enum cli_file { CL_TYPE_HWPOLE2, CL_TYPE_MHTML, CL_TYPE_LNK, - + CL_TYPE_UDF, CL_TYPE_OTHER, /* on-the-fly, used for target 14 (OTHER) */ CL_TYPE_IGNORED /* please don't add anything below */ } cli_file_t; diff --git a/libclamav/filetypes_int.h b/libclamav/filetypes_int.h index a6cb131877..0e7049795f 100644 --- a/libclamav/filetypes_int.h +++ b/libclamav/filetypes_int.h @@ -149,6 +149,7 @@ static const char *ftypes_int[] = { "0:0:4d53434600000000:MS CAB:CL_TYPE_ANY:CL_TYPE_MSCAB", "1:*:4d53434600000000:CAB-SFX:CL_TYPE_ANY:CL_TYPE_CABSFX", "1:*:014344303031{2043-2443}4344303031:ISO9660:CL_TYPE_ANY:CL_TYPE_ISO9660:71", + "1:0,32768:004245413031:UDF:CL_TYPE_ANY:CL_TYPE_UDF:180", "0:0:5b616c69617365735d:TAR-POSIX-CVE-2012-1419:CL_TYPE_ANY:CL_TYPE_POSIX_TAR", "1:8,12:19040010:SIS:CL_TYPE_ANY:CL_TYPE_SIS", "1:0,1024:44656c6976657265642d546f3a{-256}52656365697665643a:Mail file:CL_TYPE_ANY:CL_TYPE_MAIL", diff --git a/libclamav/scanners.c b/libclamav/scanners.c index 5859896ad3..a9fb272e79 100644 --- a/libclamav/scanners.c +++ b/libclamav/scanners.c @@ -100,6 +100,7 @@ #include "gif.h" #include "png.h" #include "iso9660.h" +#include "udf.h" #include "dmg.h" #include "xar.h" #include "hfsplus.h" @@ -3490,12 +3491,24 @@ static cl_error_t scanraw(cli_ctx *ctx, cli_file_t type, uint8_t typercg, cli_fi // Reassign type of current layer based on what we discovered cli_recursion_stack_change_type(ctx, fpt->type); - cli_dbgmsg("DMG signature found at %u\n", (unsigned int)fpt->offset); + cli_dbgmsg("ISO signature found at %u\n", (unsigned int)fpt->offset); nret = cli_scaniso(ctx, fpt->offset); } } break; + case CL_TYPE_UDF: + if (SCAN_PARSE_ARCHIVE && (DCONF_ARCH & ARCH_CONF_UDF)) { + { + // Reassign type of current layer based on what we discovered + cli_recursion_stack_change_type(ctx, fpt->type); + + cli_dbgmsg("UDF signature found at %u\n", (unsigned int)fpt->offset); + nret = cli_scanudf(ctx, fpt->offset); + } + } + break; + case CL_TYPE_MBR: if (SCAN_PARSE_ARCHIVE) { // TODO: determine all types that GPT or MBR may start with diff --git a/libclamav/udf.c b/libclamav/udf.c new file mode 100644 index 0000000000..7e8e674c72 --- /dev/null +++ b/libclamav/udf.c @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2023 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Author: Andy Ragusa + * + * 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. + */ + + +#include + +#include "clamav.h" +#include "scanners.h" +#include "udf.h" +#include "fmap.h" +#include "str.h" +#include "entconv.h" +#include "hashtab.h" + +static uint16_t getDescriptorTagId(const uint8_t *const buffer) +{ + return le16_to_host(((DescriptorTag *)buffer)->tagId); +} + +static bool isDirectory(FileIdentifierDescriptor *fid) +{ + return (0 != (fid->characteristics & 2)); +} + +static cl_error_t writeWholeFile(cli_ctx *ctx, const char *const fileName, const uint8_t *const data, const size_t dataLen) +{ + + int fd = -1; + char *tmpf = NULL; + + cl_error_t status = CL_ETMPFILE; + + if (0 == dataLen || NULL == data) { + cli_warnmsg("writeWholeFile: Invalid arguments\n"); + status = CL_EARG; + goto done; + } + + /*Not sure if I care about the name that is actually created.*/ + if (cli_gentempfd_with_prefix(ctx->sub_tmpdir, fileName, &tmpf, &fd) != CL_SUCCESS) { + cli_warnmsg("writeWholeFile: Can't create temp file\n"); + status = CL_ETMPFILE; + goto done; + } + + if (cli_writen(fd, data, dataLen) != dataLen) { + cli_warnmsg("iso_scan_file: Can't write to file %s\n", tmpf); + status = CL_EWRITE; + goto done; + } + + status = cli_magic_scan_desc(fd, tmpf, ctx, fileName, LAYER_ATTRIBUTES_NONE); + + close(fd); + if (!ctx->engine->keeptmp) { + if (cli_unlink(tmpf)) { + /* If status is already set to virus or something, that should take priority of the + * error unlinking the file.*/ + if (CL_CLEAN == status){ + status = CL_EUNLINK; + } + goto done; + } + } + +done: + FREE(tmpf); + + return status; +} + +static cl_error_t extractFile(cli_ctx *ctx, PartitionDescriptor *pPartitionDescriptor, LogicalVolumeDescriptor *pLogicalVolumeDescriptor, void *address, uint16_t icbFlags, FileIdentifierDescriptor *fileIdentifierDescriptor) +{ + + cl_error_t ret = CL_SUCCESS; + uint32_t offset = 0; + uint32_t length = 0; + uint8_t *contents = NULL; + + if (isDirectory(fileIdentifierDescriptor)) { + goto done; + } + + switch (icbFlags & 3) { + case 0: { + short_ad *shortDesc = (short_ad *)address; + + offset = pPartitionDescriptor->partitionStartingLocation * pLogicalVolumeDescriptor->logicalBlockSize; + offset += shortDesc->position * pLogicalVolumeDescriptor->logicalBlockSize; + + length = shortDesc->length; + + } break; + case 1: { + long_ad *longDesc = (long_ad *)address; + offset = pPartitionDescriptor->partitionStartingLocation * pLogicalVolumeDescriptor->logicalBlockSize; + length = longDesc->length; + + if (longDesc->extentLocation.partitionReferenceNumber != pPartitionDescriptor->partitionNumber) { + cli_warnmsg("extractFile: Unable to extract the files because the Partition Descriptor Reference Numbers don't match\n"); + goto done; + } + offset += longDesc->extentLocation.blockNumber * pLogicalVolumeDescriptor->logicalBlockSize; + offset += pPartitionDescriptor->partitionStartingLocation; + + } break; + case 2: + + { + ext_ad *extDesc = (ext_ad *)address; + offset = pPartitionDescriptor->partitionStartingLocation * pLogicalVolumeDescriptor->logicalBlockSize; + length = extDesc->recordedLen; + + if (extDesc->extentLocation.partitionReferenceNumber != pPartitionDescriptor->partitionNumber) { + cli_warnmsg("extractFile: Unable to extract the files because the Partition Descriptor Reference Numbers don't match\n"); + goto done; + } + offset += extDesc->extentLocation.blockNumber * pLogicalVolumeDescriptor->logicalBlockSize; + offset += pPartitionDescriptor->partitionStartingLocation; + + } + + break; + default: + //impossible unless the file is malformed. + cli_warnmsg("extractFile: Unknown descriptor type found.\n"); + goto done; + } + + contents = (uint8_t *)fmap_need_off(ctx->fmap, offset, length); + if (NULL == contents) { + cli_warnmsg("extractFile: Unable to get offset referenced in the file.\n"); + goto done; + } + + ret = writeWholeFile(ctx, "Test", contents, length); + + fmap_unneed_off(ctx->fmap, offset, length); + +done: + + return ret; +} + +static bool parseFileEntryDescriptor(cli_ctx *ctx, const uint8_t *const data, PartitionDescriptor *pPartitionDescriptor, LogicalVolumeDescriptor *pLogicalVolumeDescriptor, FileIdentifierDescriptor *fileIdentifierDescriptor) +{ + + FileEntryDescriptor *fed = (FileEntryDescriptor *)data; + bool ret = false; + + if (261 != fed->tag.tagId) { + cli_warnmsg("parseFileEntryDescriptor: Tag ID of 0x%x does not match File Entry Descriptor.\n", fed->tag.tagId); + goto done; + } + + if (257 != fileIdentifierDescriptor->tag.tagId) { + cli_warnmsg("parseFileEntryDescriptor: Tag ID of 0x%x does not match File Identifier Descriptor.\n", fed->tag.tagId); + goto done; + } + + if (CL_SUCCESS != extractFile(ctx, pPartitionDescriptor, pLogicalVolumeDescriptor, + (void *)&(data[getFileEntryDescriptorSize(fed) - fed->allocationDescLen]), + fed->icbTag.flags, fileIdentifierDescriptor)){ + ret = false; + goto done; + } + + ret = true; +done: + return ret; +} + +static void dumpTag(DescriptorTag *dt) +{ + fprintf(stderr, "TagId = %d (0x%x)\n", dt->tagId, dt->tagId); + fprintf(stderr, "Version = %d (0x%x)\n", dt->descriptorVersion, dt->descriptorVersion); + fprintf(stderr, "Checksum = %d (0x%x)\n", dt->checksum, dt->checksum); + fprintf(stderr, "Serial Number = %d (0x%x)\n", dt->serialNumber, dt->serialNumber); + + fprintf(stderr, "Descriptor CRC = %d (0x%x)\n", dt->descriptorCRC, dt->descriptorCRC); + fprintf(stderr, "Descriptor CRC Length = %d (0x%x)\n", dt->descriptorCRCLength, dt->descriptorCRCLength); + fprintf(stderr, "Tag Location = %d (0x%x)\n", dt->tagLocation, dt->tagLocation); +} + +typedef struct { + uint8_t structType; + char standardIdentifier[5]; + uint8_t structVersion; + uint8_t rest[2041]; +} GenericVolumeStructureDescriptor; +#define NUM_GENERIC_VOLUME_DESCRIPTORS 3 + +/*If this function fails, idx will not be updated*/ +static bool skipEmptyDescriptors(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + bool ret = false; + uint8_t *buffer = NULL; + size_t idx = *idxp; + bool allzeros = true; + size_t i; + + while (1) { + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + + allzeros = true; + for (i = 0; i < VOLUME_DESCRIPTOR_SIZE; i++) { + if (0 != buffer[i]) { + allzeros = false; + break; + } + } + if (!allzeros) { + break; + } + idx += VOLUME_DESCRIPTOR_SIZE; + } + + ret = true; +done: + + *idxp = idx; + *lastOffsetp = idx; + + return ret; +} + +typedef enum { + PRIMARY_VOLUME_DESCRIPTOR = 1, + IMPLEMENTATION_USE_VOLUME_DESCRIPTOR = 4, + LOGICAL_VOLUME_DESCRIPTOR = 6, + PARTITION_DESCRIPTOR = 5, + UNALLOCATED_SPACE_DESCRIPTOR = 7, + TERMINATING_DESCRIPTOR = 8, + LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR = 9, + ANCHOR_VOLUME_DESCRIPTOR_DESCRIPTOR_POINTER = 2, + FILE_SET_DESCRIPTOR = 256 +} VOLUME_DESCRIPTOR_TAG; + +/*Skip past all the empty descriptors and find the PrimaryVolumeDescriptor. + * Return error if the next non-empty descriptor is not a PrimaryVolumeDescriptor.*/ +static PrimaryVolumeDescriptor *getPrimaryVolumeDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + PrimaryVolumeDescriptor *test = NULL; + PrimaryVolumeDescriptor *ret = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (PrimaryVolumeDescriptor *)buffer; + if (PRIMARY_VOLUME_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + idx += VOLUME_DESCRIPTOR_SIZE; + ret = test; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the ImplementationUseVolumeDescriptor. + * Return error if the next non-empty descriptor is not a ImplementationUseVolumeDescriptor.*/ +static ImplementationUseVolumeDescriptor *getImplementationUseVolumeDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + ImplementationUseVolumeDescriptor *test = NULL; + ImplementationUseVolumeDescriptor *ret = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (ImplementationUseVolumeDescriptor *)buffer; + if (IMPLEMENTATION_USE_VOLUME_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + ret = test; + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the LogicalVolumeDescriptor. + * Return error if the next non-empty descriptor is not a LogicalVolumeDescriptor.*/ +static LogicalVolumeDescriptor *getLogicalVolumeDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + LogicalVolumeDescriptor *ret = NULL; + LogicalVolumeDescriptor *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (LogicalVolumeDescriptor *)buffer; + if (LOGICAL_VOLUME_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + idx += VOLUME_DESCRIPTOR_SIZE; + ret = test; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the PartitionDescriptor. + * Return error if the next non-empty descriptor is not a PartitionDescriptor.*/ +static PartitionDescriptor *getPartitionDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + PartitionDescriptor *ret = NULL; + PartitionDescriptor *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (PartitionDescriptor *)buffer; + if (PARTITION_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + ret = test; + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the UnallocatedSpaceDescriptor. + * Return error if the next non-empty descriptor is not a UnallocatedSpaceDescriptor.*/ +static UnallocatedSpaceDescriptor *getUnallocatedSpaceDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + UnallocatedSpaceDescriptor *ret = NULL; + UnallocatedSpaceDescriptor *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (UnallocatedSpaceDescriptor *)buffer; + if (UNALLOCATED_SPACE_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + ret = test; + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the TerminatingDescriptor. + * Return error if the next non-empty descriptor is not a TerminatingDescriptor.*/ +static TerminatingDescriptor *getTerminatingDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + TerminatingDescriptor *ret = NULL; + TerminatingDescriptor *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (TerminatingDescriptor *)buffer; + if (TERMINATING_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + ret = test; + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the LogicalVolumeIntegrityDescriptor. + * Return error if the next non-empty descriptor is not a LogicalVolumeIntegrityDescriptor.*/ +static LogicalVolumeIntegrityDescriptor *getLogicalVolumeIntegrityDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + LogicalVolumeIntegrityDescriptor *ret = NULL; + LogicalVolumeIntegrityDescriptor *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (LogicalVolumeIntegrityDescriptor *)buffer; + if (LOGICAL_VOLUME_INTEGRITY_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + ret = test; + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the AnchorVolumeDescriptor. + * Return error if the next non-empty descriptor is not a AnchorVolumeDescriptor.*/ +static AnchorVolumeDescriptorPointer *getAnchorVolumeDescriptorPointer(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + AnchorVolumeDescriptorPointer *ret = NULL; + AnchorVolumeDescriptorPointer *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (AnchorVolumeDescriptorPointer *)buffer; + if (ANCHOR_VOLUME_DESCRIPTOR_DESCRIPTOR_POINTER != test->tag.tagId) { + goto done; + } + + ret = test; + + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +/*Skip past all the empty descriptors and find the FileSetDescriptor. + * Return error if the next non-empty descriptor is not a FileSetDescriptor.*/ +static FileSetDescriptor *getFileSetDescriptor(cli_ctx *ctx, size_t *idxp, size_t *lastOffsetp) +{ + uint8_t *buffer = NULL; + FileSetDescriptor *ret = NULL; + FileSetDescriptor *test = NULL; + size_t idx = *idxp; + size_t lastOffset = *lastOffsetp; + + if (!skipEmptyDescriptors(ctx, idxp, lastOffsetp)) { + goto done; + } + + idx = *idxp; + lastOffset = *lastOffsetp; + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + test = (FileSetDescriptor *)buffer; + if (FILE_SET_DESCRIPTOR != test->tag.tagId) { + goto done; + } + + ret = test; + idx += VOLUME_DESCRIPTOR_SIZE; + +done: + *idxp = idx; + *lastOffsetp = lastOffset; + + return ret; +} + +typedef struct { + + const uint8_t **idxs; + + uint32_t cnt; + + uint32_t capacity; + +} PointerList; +#define POINTER_LIST_INCREMENT 1024 + +static void freePointerList(PointerList *pl) +{ + FREE(pl->idxs); + memset(pl, 0, sizeof(PointerList)); +} + +static cl_error_t initPointerList(PointerList *pl) +{ + cl_error_t ret = CL_SUCCESS; + uint32_t capacity = POINTER_LIST_INCREMENT; + + freePointerList(pl); + CLI_CALLOC(pl->idxs, capacity, sizeof(uint8_t *), + cli_errmsg("initPointerList: Can't allocate memory\n"); + ret = CL_EMEM); + + pl->capacity = capacity; +done: + return ret; +} + +static cl_error_t insertPointer(PointerList *pl, const uint8_t *pointer) +{ + cl_error_t ret = CL_SUCCESS; + + if (pl->cnt == (pl->capacity - 1)) { + uint32_t newCapacity = pl->capacity + POINTER_LIST_INCREMENT; + CLI_REALLOC(pl->idxs, newCapacity * sizeof(uint8_t *), + cli_errmsg("insertPointer: Can't allocate memory\n"); + ret = CL_EMEM); + + pl->capacity = newCapacity; + } + + pl->idxs[pl->cnt++] = pointer; + +done: + return ret; +} + +static cl_error_t findFileIdentifiers(const uint8_t *const input, PointerList *pfil) +{ + + cl_error_t ret = CL_SUCCESS; + const uint8_t *buffer = input; + uint16_t tagId = getDescriptorTagId(buffer); + + while (257 == tagId) { + if (CL_SUCCESS != (ret = insertPointer(pfil, buffer))) { + goto done; + } + + buffer = buffer + getFileIdentifierDescriptorSize((FileIdentifierDescriptor *)buffer); + tagId = getDescriptorTagId(buffer); + } + +done: + return ret; +} + +static cl_error_t findFileEntries(const uint8_t *const input, PointerList *pfil) +{ + + cl_error_t ret = CL_SUCCESS; + const uint8_t *buffer = input; + uint16_t tagId = getDescriptorTagId(buffer); + + while (261 == tagId) { + if (CL_SUCCESS != (ret = insertPointer(pfil, buffer))) { + goto done; + } + + buffer = buffer + getFileEntryDescriptorSize((FileEntryDescriptor *)buffer); + tagId = getDescriptorTagId(buffer); + } + +done: + return ret; +} + +cl_error_t cli_scanudf(cli_ctx *ctx, const size_t offset) +{ + cl_error_t ret = CL_SUCCESS; + size_t idx = offset; + size_t lastOffset = 0; + size_t i = 0; + uint8_t *buffer = NULL; + PrimaryVolumeDescriptor *pvd = NULL; + GenericVolumeStructureDescriptor *gvsd = NULL; + ImplementationUseVolumeDescriptor *iuvd = NULL; + LogicalVolumeDescriptor *lvd = NULL; + PartitionDescriptor *pd = NULL; + UnallocatedSpaceDescriptor *usd = NULL; + TerminatingDescriptor *td = NULL; + LogicalVolumeIntegrityDescriptor *lvid = NULL; + AnchorVolumeDescriptorPointer *avdp = NULL; + + bool isInitialized = false; + PointerList fileIdentifierList; + PointerList fileEntryList; + + if (offset < 32768) { + return CL_SUCCESS; /* Need 16 sectors at least 2048 bytes long */ + } + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, NUM_GENERIC_VOLUME_DESCRIPTORS * VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + ret = CL_SUCCESS; + goto done; + } + + for (i = 0; i < NUM_GENERIC_VOLUME_DESCRIPTORS; i++) { + gvsd = (GenericVolumeStructureDescriptor *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + lastOffset = idx; + + if (strncmp("BEA01", gvsd->standardIdentifier, 5)) { + } else if (strncmp("BOOT2", gvsd->standardIdentifier, 5)) { + } else if (strncmp("CD001", gvsd->standardIdentifier, 5)) { + } else if (strncmp("CDW02", gvsd->standardIdentifier, 5)) { + } else if (strncmp("NSR02", gvsd->standardIdentifier, 5)) { + } else if (strncmp("NSR03", gvsd->standardIdentifier, 5)) { + } else if (strncmp("TEA01", gvsd->standardIdentifier, 5)) { + } else { + cli_warnmsg("Unknown Standard Identifier '%s'\n", gvsd->standardIdentifier); + break; + } + + idx += VOLUME_DESCRIPTOR_SIZE; + } + + memset(&fileIdentifierList, 0, sizeof(PointerList)); + memset(&fileEntryList, 0, sizeof(PointerList)); + + while (1) { + + if (!isInitialized) { + + if (CL_SUCCESS != (ret = initPointerList(&fileIdentifierList))) { + goto done; + } + + if (CL_SUCCESS != (ret = initPointerList(&fileEntryList))) { + goto done; + } + + if (NULL == (pvd = getPrimaryVolumeDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (iuvd = getImplementationUseVolumeDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (lvd = getLogicalVolumeDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (pd = getPartitionDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (usd = getUnallocatedSpaceDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (td = getTerminatingDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + /*May not be every file, need to verify.*/ + if (NULL == (lvid = getLogicalVolumeIntegrityDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (td = getTerminatingDescriptor(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == (avdp = getAnchorVolumeDescriptorPointer(ctx, &idx, &lastOffset))) { + goto done; + } + + if (NULL == getFileSetDescriptor(ctx, &idx, &lastOffset)) { + goto done; + } + + isInitialized = true; + } + + buffer = (uint8_t *)fmap_need_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + if (NULL == buffer) { + goto done; + } + lastOffset = idx; + + uint16_t tagId = getDescriptorTagId(buffer); + if (tagId) { + switch (tagId) { + case 257: + + findFileIdentifiers(buffer, &fileIdentifierList); + break; + + case 261: + + findFileEntries(buffer, &fileEntryList); + break; + case 8: + break; + + default: { + /*Dump all the files here.*/ + size_t i; + size_t cnt = fileIdentifierList.cnt; + + /*The number of file entries should match the number of file identifiers, but in the + * case that the file is malformed, we are going to do the best we can to extract as much as we can. + */ + if (fileEntryList.cnt < cnt) { + cnt = fileEntryList.cnt; + } + + for (i = 0; i < cnt; i++) { + if (!parseFileEntryDescriptor(ctx, + (const uint8_t *const)fileEntryList.idxs[i], + pd, lvd, (FileIdentifierDescriptor *)fileIdentifierList.idxs[i])) { + goto done; + } + } + + /* Start looking for the next volume */ + isInitialized = false; + break; + } + } + } + + idx += VOLUME_DESCRIPTOR_SIZE; + } + +done: + freePointerList(&fileIdentifierList); + freePointerList(&fileEntryList); + + for (idx = offset; idx <= lastOffset; idx += VOLUME_DESCRIPTOR_SIZE) { + fmap_unneed_off(ctx->fmap, idx, VOLUME_DESCRIPTOR_SIZE); + } + + return ret; +} + diff --git a/libclamav/udf.h b/libclamav/udf.h new file mode 100644 index 0000000000..c3466b5ccf --- /dev/null +++ b/libclamav/udf.h @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2023 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * Authors: Cisco + * + * 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. + */ + +#ifndef _UDF_H_ +#define _UDF_H_ + +#include "others.h" + + +#define UDF_EMPTY_LEN 32768 + +#define VOLUME_DESCRIPTOR_SIZE 0x800 + +typedef struct { + uint16_t typeTimeZone; /* + 0 Coordinated UTC + 1 Local Time + 2 Up to agreement between originator and recipient + 3 - 15 Reserved + */ + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t centiseconds; + uint8_t hundredsMicroSeconds; + uint8_t microseconds; +} timestamp; + + +typedef struct __attribute__((packed)) { + + uint32_t blockNumber; + + uint16_t partitionReferenceNumber; + +} lb_addr; + +typedef struct __attribute__((packed)) { + uint32_t length; //4/14.14.1.1 + /*30 least significant bits are length in bytes. + * + * 2 most significant bits are described in figure 4/42 + * + * 0 extent recorded and allocated + * 1 extent NOT recorded but allocated + * 2 extent NOT recorded and NOT allocated + * 3 the extent is the next extent of allocation descriptors. + * */ + + lb_addr extentLocation; //logical block number. (CAN be zero) + + uint8_t implementationUse[6]; + +} long_ad; + + + +/* + * https://www.ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf + * section 3/7.2 */ +typedef struct __attribute__((packed)) { + uint16_t tagId; + uint16_t descriptorVersion; + uint8_t checksum; + uint8_t reserved; + uint16_t serialNumber; + uint16_t descriptorCRC; + uint16_t descriptorCRCLength; + uint32_t tagLocation; +} DescriptorTag; + +typedef struct { + uint8_t flags; + /* + * 1/7.4 + * characteristics + * bit 0 dirty: If regid has been modified and might not be valid, set to * 1. Otherwise, 0 + * bit 1 protected: If 1, this regid cannot be modified + * bit 2-7 reserved + */ + uint8_t identifier[23]; + /* + * If first byte is 0x2b, then this is covered by ECMA-168 (this spec) + * If first byte is 0x2d, then this is not registered + */ + uint8_t identifierSuffix[8]; +} regid; + + + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + uint32_t volumeDescriptorSequenceNumber; + uint32_t primaryVolumeDescriptorNumber; + uint8_t volumeIdentifier[32]; + uint16_t volumeSequenceNumber; + uint16_t interchangeLevel; + uint16_t maxInterchangeLevel; + uint32_t charSetList; + uint8_t volumeSetIdentifier[128]; + uint8_t descriptorCharSet[64]; + uint8_t explanatoryCharSet[64]; + uint64_t volumeAbstract; + uint64_t volumeCopyrightNotice; + uint8_t applicationIdentifier[32]; + uint8_t recordingDateTime[12]; + uint8_t implementationIdentifier[32]; + uint8_t implementationUse[64]; + uint32_t predVolumeDescSequenceLocation; + uint16_t flags; + uint8_t reserved[22]; + +} PrimaryVolumeDescriptor; + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + uint32_t volumeDescriptorSequenceNumber; + + regid implementationIdentifier; + uint8_t implementationUse[460]; + +} ImplementationUseVolumeDescriptor; + + +/* https://www.ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf */ +/* 4/3 */ +typedef struct __attribute__((packed)) { + uint32_t logicalBlockNumber; + + uint16_t partitionReferenceNumber; +} LBAddr; + +//https://www.ecma-international.org/wp-content/uploads/ECMA-167_3rd_edition_june_1997.pdf +//section 4/23 +typedef struct __attribute__((packed)) { + uint32_t priorRecordedNumberOfDirectEntries; + uint16_t strategyType; + uint8_t strategyParameter[2]; /*described as 'bytes' in docs, so don't want to worry about byte order.*/ + uint16_t maxEntries; + uint8_t reserved_must_be_zero; + + uint8_t fileType; + + LBAddr parentICBLocation; + + uint16_t flags; +} ICBTag; + + + +typedef struct __attribute__((packed)) { + + DescriptorTag tag; + + uint16_t versionNumber; + + uint8_t characteristics; + + uint8_t fileIdentifierLength; + + long_ad icb; + + uint16_t implementationLength; + + uint8_t rest[1]; + +}FileIdentifierDescriptor; + +#define FILE_IDENTIFIER_DESCRIPTOR_SIZE_KNOWN (sizeof(FileIdentifierDescriptor) - 1) + + +/*Section 14.4.9 of https:... */ +static uint32_t getFileIdentifierDescriptorPaddingLength(const FileIdentifierDescriptor * const fid){ + uint32_t ret = 0; + uint32_t tmp = fid->implementationLength + fid->fileIdentifierLength + 38; + ret = tmp + 3; + ret = ret / 4; + + ret = ret * 4; + ret = ret - tmp; + + return ret; +} + +static inline size_t getFileIdentifierDescriptorSize(const FileIdentifierDescriptor * fid){ + + return FILE_IDENTIFIER_DESCRIPTOR_SIZE_KNOWN + fid->implementationLength + fid->fileIdentifierLength + getFileIdentifierDescriptorPaddingLength(fid); + +} + + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + + ICBTag icbTag; + + uint32_t uid; + + uint32_t gid; + + uint32_t permissions ; + + uint16_t fileLinkCnt; + + uint8_t recordFormat; + uint8_t recordDisplayAttributes; + + + uint32_t recordLength; + + uint64_t infoLength; + + uint64_t logicalBlocksRecorded; + + timestamp accessDateTime; + + timestamp modificationDateTime; + + timestamp attributeDateTime; + + uint32_t checkpoint; + + long_ad extendedAttrICB; + + regid implementationId; + + uint64_t uniqueId; + + uint32_t extendedAttrLen; + + uint32_t allocationDescLen; + + /* Variable length stuff here, need to handle; + */ + uint8_t rest[1]; + + +} FileEntryDescriptor; + +#define FILE_ENTRY_DESCRIPTOR_SIZE_KNOWN (sizeof(FileEntryDescriptor) - 1) +static inline size_t getFileEntryDescriptorSize(const FileEntryDescriptor* fed){ + return FILE_ENTRY_DESCRIPTOR_SIZE_KNOWN + fed->extendedAttrLen + fed->allocationDescLen; +} + + + +typedef struct { + DescriptorTag tag; + + ICBTag icbTag; + + uint32_t uid; + + uint32_t gid; + + uint32_t permissions ; + + uint16_t fileLinkCnt; + + uint8_t recordFormat; + + uint8_t recordDisplayAttributes; + + uint32_t recordLength; + + uint64_t infoLength; + + uint64_t objectSize; //different + + uint64_t logicalBlocksRecorded; + + timestamp accessDateTime; + + timestamp modificationDateTime; + + timestamp creationDateTime; //different + + timestamp attributeDateTime; + + uint32_t checkpoint; + + uint32_t reserved; //different + + long_ad extendedAttrICB; + + long_ad streamDirectoryICB; //different + + regid implementationId; + + uint64_t uniqueId; + + uint32_t extendedAttrLen; + + uint32_t allocationDescLen; + + /* Variable length stuff here, need to handle; + */ + + +} ExtendedFileEntryDescriptor; + + + + +typedef struct { + + uint32_t length; + + uint32_t position; + +} short_ad; + +typedef struct { + uint32_t extentLen; + uint32_t recordedLen; + + uint32_t infoLen; + + lb_addr extentLocation; + + uint8_t implementationUse[2]; +} ext_ad; + +typedef struct { + uint32_t extentLength; + + uint32_t extentLocation; + +} extent_ad; + + +typedef struct { + + DescriptorTag tag; + + uint32_t volumeDescriptorSequenceNumber; + + uint16_t partitionFlags; + + uint16_t partitionNumber; + + regid partitionContents; + + uint8_t partitionContentsUse[128]; + + uint32_t accessType; + + uint32_t partitionStartingLocation ; + + uint32_t partitionLength; + + regid implementationIdentifier; + + uint8_t implementationUse[128]; + + uint8_t reserved[156]; + +} PartitionDescriptor; + + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + + uint32_t volumeDescriptorSequenceNumber; + + uint32_t numAllocationDescriptors; + + uint8_t rest[1]; /*reset is 'numAllocationDescriptors' * sizeof (extent_ad), + and padded out to VOLUME_DESCRIPTOR_SIZE with zeros. */ + +} UnallocatedSpaceDescriptor; + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + + uint8_t padding[496]; +} TerminatingDescriptor; + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + + timestamp recordingDateTime; + + uint32_t integrityType; + + extent_ad nextIntegrityExtent; + + uint8_t logicalVolumeContents[32]; + + uint32_t numPartitions; + + uint32_t lenImplementationUse ; + + uint32_t freeSpaceTable; + + uint32_t sizeTable; + + uint8_t rest[1]; + +} LogicalVolumeIntegrityDescriptor; + + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + + extent_ad mainVolumeDescriptorSequence; + + extent_ad reserveVolumeDescriptorSequence; + + uint8_t reserved[480]; + +} AnchorVolumeDescriptorPointer; + +typedef struct __attribute__((packed)) { + DescriptorTag tag; + + uint32_t volumeDescriptorSequenceNumber; + + extent_ad nextVolumeDescriptorSequence; + + uint8_t reserved[484]; + +} VolumeDescriptorPointer; + + +/* + * charsetType can be + 0 The CS0 coded character set (1/7.2.2). + 1 The CS1 coded character set (1/7.2.3). + 2 The CS2 coded character set (1/7.2.4). + 3 The CS3 coded character set (1/7.2.5). + 4 The CS4 coded character set (1/7.2.6). + 5 The CS5 coded character set (1/7.2.7). + 6 The CS6 coded character set (1/7.2.8). + 7 The CS7 coded character set (1/7.2.9). + 8 The CS8 coded character set (1/7.2.10). + 9-255 Reserved for future standardisation. + * + */ +typedef struct { + uint8_t charSetType; + + uint8_t charSetInfo[63]; +} charspec; + + + +typedef struct { + + DescriptorTag tag; + + uint32_t volumeDescriptorSequenceNumber; + + charspec descriptorCharSet; + + uint8_t logicalVolumeIdentifier[128]; //TODO: handle dstring + + uint32_t logicalBlockSize; + + regid domainIdentifier; + + uint8_t logicalVolumeContentsUse[16]; + + uint32_t mapTableLength; + + uint32_t numPartitionMaps; + + regid implementationIdentifier; + + uint8_t implementationUse[128]; + + ext_ad integritySequenceExtent; + + uint8_t partitionMaps[1]; //actual length of mapTableLength above; + +}LogicalVolumeDescriptor ; + +typedef struct { + DescriptorTag tag; + timestamp recordingDateTime; + + uint16_t interchangeLevel; + + uint16_t maxInterchangeLevel; + uint32_t characterSetList; + uint32_t maxCharacterSetList; + + uint32_t fileSetNumber; + uint32_t fileSetDescriptorNumber; + + charspec logicalVolumeIdentifierCharSet; + uint8_t logicalVolumeIdentifier[128]; + charspec fileSetCharSet; + uint8_t fileSetIdentifier[32]; + + uint8_t copyrightIdentifier[32]; + uint8_t abstractIdentifier[32]; + long_ad rootDirectoryICB; + + regid domainIdentifier; + + long_ad nextExtent; + long_ad systemStreamDirectoryICB; + uint8_t reserved[32]; + +} FileSetDescriptor; + + + +cl_error_t cli_scanudf(cli_ctx *ctx, size_t offset); + +#endif /* _UDF_H_ */ + + +