mirror of
https://github.com/mtrojnar/osslsigncode
synced 2026-06-08 16:13:39 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 97a9ade6ec | |||
| f2f33bb131 | |||
| 202b2c2866 | |||
| 2a5409b7c4 | |||
| 87bce8e372 | |||
| f7ace57c81 | |||
| 92f8761b47 | |||
| 09d3312fd9 | |||
| 9d02a20aec | |||
| f190ec5d87 | |||
| 4b30d6be28 |
@@ -7,7 +7,7 @@ on:
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
version: osslsigncode-2.12
|
||||
version: osslsigncode-2.13
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ set(BUILTIN_SOCKET ON CACHE BOOL "") # for static Python
|
||||
|
||||
# configure basic project information
|
||||
project(osslsigncode
|
||||
VERSION 2.12
|
||||
VERSION 2.13
|
||||
DESCRIPTION "OpenSSL based Authenticode signing for PE, CAB, CAT, MSI, APPX and script files"
|
||||
HOMEPAGE_URL "https://github.com/mtrojnar/osslsigncode"
|
||||
LANGUAGES C)
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# osslsigncode change log
|
||||
|
||||
### 2.13 (2026.02.10)
|
||||
|
||||
**MULTIPLE SECURITY VULNERABILITIES**
|
||||
|
||||
This release includes important security fixes. Users are strongly encouraged
|
||||
to upgrade, as the issues below may be exploitable when processing untrusted
|
||||
files.
|
||||
|
||||
- fixed integer overflows when processing APPX compressed data streams
|
||||
(by Małgorzata Olszówka)
|
||||
- fixed double-free vulnerabilities in APPX file processing
|
||||
(by Małgorzata Olszówka)
|
||||
- fixed multiple memory corruption issues in PE page hash computation
|
||||
(by Antoni Klajn (Opera) and Małgorzata Olszówka)
|
||||
|
||||
### 2.12 (2026.02.02)
|
||||
|
||||
**CRITICAL SECURITY VULNERABILITY**
|
||||
|
||||
@@ -1503,6 +1503,7 @@ static int zipAppendSignatureFile(BIO *bio, ZIP_FILE *zip, uint8_t *data, uint64
|
||||
if (!get_current_position(bio, &offset)) {
|
||||
fprintf(stderr, "Unable to get offset\n");
|
||||
OPENSSL_free(header.fileName);
|
||||
header.fileName = NULL;
|
||||
OPENSSL_free(dataToWrite);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
@@ -1513,6 +1514,7 @@ static int zipAppendSignatureFile(BIO *bio, ZIP_FILE *zip, uint8_t *data, uint64
|
||||
if (!BIO_write_ex(bio, dataToWrite + written, toWrite, &check)
|
||||
|| check != toWrite) {
|
||||
OPENSSL_free(header.fileName);
|
||||
header.fileName = NULL;
|
||||
OPENSSL_free(dataToWrite);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
@@ -1685,6 +1687,8 @@ static int zipRewriteData(ZIP_FILE *zip, ZIP_CENTRAL_DIRECTORY_ENTRY *entry, BIO
|
||||
out:
|
||||
OPENSSL_free(header.fileName);
|
||||
OPENSSL_free(header.extraField);
|
||||
header.fileName = NULL;
|
||||
header.extraField = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1852,6 +1856,11 @@ static size_t zipReadFileData(ZIP_FILE *zip, uint8_t **pData, ZIP_CENTRAL_DIRECT
|
||||
}
|
||||
if (entry->overrideData) {
|
||||
compressedSize = entry->overrideData->compressedSize;
|
||||
/* Validate sizes for safe allocation */
|
||||
if (compressedSize > (uint64_t)(SIZE_MAX - 1)) {
|
||||
fprintf(stderr, "Corrupted compressedSize : %" PRIu64"\n", compressedSize);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
uncompressedSize = entry->overrideData->uncompressedSize;
|
||||
compressedData = OPENSSL_zalloc(compressedSize + 1);
|
||||
memcpy(compressedData, entry->overrideData->data, compressedSize);
|
||||
@@ -1863,6 +1872,8 @@ static size_t zipReadFileData(ZIP_FILE *zip, uint8_t **pData, ZIP_CENTRAL_DIRECT
|
||||
if (!zipReadLocalHeader(&header, zip, compressedSize)) {
|
||||
OPENSSL_free(header.fileName);
|
||||
OPENSSL_free(header.extraField);
|
||||
header.fileName = NULL;
|
||||
header.extraField = NULL;
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
if (header.fileNameLen != entry->fileNameLen
|
||||
@@ -1873,14 +1884,20 @@ static size_t zipReadFileData(ZIP_FILE *zip, uint8_t **pData, ZIP_CENTRAL_DIRECT
|
||||
fprintf(stderr, "Local header does not match central directory entry\n");
|
||||
OPENSSL_free(header.fileName);
|
||||
OPENSSL_free(header.extraField);
|
||||
header.fileName = NULL;
|
||||
header.extraField = NULL;
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
/* we don't really need those */
|
||||
OPENSSL_free(header.fileName);
|
||||
OPENSSL_free(header.extraField);
|
||||
header.fileName = NULL;
|
||||
header.extraField = NULL;
|
||||
|
||||
if (compressedSize > (uint64_t)zip->fileSize - entry->offsetOfLocalHeader) {
|
||||
fprintf(stderr, "Corrupted compressedSize : 0x%08" PRIX64 "\n", entry->compressedSize);
|
||||
/* Validate sizes for safe allocation */
|
||||
if (compressedSize > (uint64_t)(SIZE_MAX - 1)
|
||||
|| compressedSize > (uint64_t)zip->fileSize - entry->offsetOfLocalHeader) {
|
||||
fprintf(stderr, "Corrupted compressedSize : %" PRIu64"\n", compressedSize);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
compressedData = OPENSSL_zalloc(compressedSize + 1);
|
||||
@@ -1899,11 +1916,24 @@ static size_t zipReadFileData(ZIP_FILE *zip, uint8_t **pData, ZIP_CENTRAL_DIRECT
|
||||
*pData = compressedData;
|
||||
dataSize = compressedSize;
|
||||
} else if (entry->compression == COMPRESSION_DEFLATE) {
|
||||
uint8_t *uncompressedData = OPENSSL_zalloc(uncompressedSize + 1);
|
||||
uint64_t destLen = uncompressedSize;
|
||||
uint64_t sourceLen = compressedSize;
|
||||
uint8_t *uncompressedData;
|
||||
uint64_t destLen, sourceLen;
|
||||
int ret;
|
||||
|
||||
/* Validate sizes for safe allocation */
|
||||
if (uncompressedSize > (uint64_t)(SIZE_MAX - 1)) {
|
||||
fprintf(stderr, "Corrupted uncompressedSize : %" PRIu64"\n", uncompressedSize);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
/* Detect suspicious compression ratio (zip bomb protection) */
|
||||
if (uncompressedSize > 1024 * 1024 && uncompressedSize / 100 >= compressedSize) {
|
||||
fprintf(stderr, "Error: suspicious compression ratio\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
uncompressedData = OPENSSL_zalloc(uncompressedSize + 1);
|
||||
destLen = uncompressedSize;
|
||||
sourceLen = compressedSize;
|
||||
|
||||
ret = zipInflate(uncompressedData, &destLen, compressedData, (uLong *)&sourceLen);
|
||||
OPENSSL_free(compressedData);
|
||||
|
||||
@@ -1970,6 +2000,8 @@ static int zipReadLocalHeader(ZIP_LOCAL_HEADER *header, ZIP_FILE *zip, uint64_t
|
||||
header->extraFieldLen = fileGetU16(file);
|
||||
/* file name (variable size) */
|
||||
if (header->fileNameLen > 0) {
|
||||
/* fileNameLen is uint16_t (ZIP spec, 2-byte field),
|
||||
* so fileNameLen + 1 cannot overflow size_t */
|
||||
header->fileName = OPENSSL_zalloc(header->fileNameLen + 1);
|
||||
size = fread(header->fileName, 1, header->fileNameLen, file);
|
||||
if (size != header->fileNameLen) {
|
||||
@@ -1981,6 +2013,8 @@ static int zipReadLocalHeader(ZIP_LOCAL_HEADER *header, ZIP_FILE *zip, uint64_t
|
||||
}
|
||||
/* extra field (variable size) */
|
||||
if (header->extraFieldLen > 0) {
|
||||
/* extraFieldLen is uint16_t (ZIP spec, 2-byte field),
|
||||
* so extraFieldLen + 1 cannot overflow size_t */
|
||||
header->extraField = OPENSSL_zalloc(header->extraFieldLen + 1);
|
||||
size = fread(header->extraField, 1, header->extraFieldLen, file);
|
||||
if (size != header->extraFieldLen) {
|
||||
@@ -2011,6 +2045,8 @@ static int zipReadLocalHeader(ZIP_LOCAL_HEADER *header, ZIP_FILE *zip, uint64_t
|
||||
fprintf(stderr, "The input file is not a valid zip file - flags indicate data descriptor, but data descriptor signature does not match\n");
|
||||
OPENSSL_free(header->fileName);
|
||||
OPENSSL_free(header->extraField);
|
||||
header->fileName = NULL;
|
||||
header->extraField = NULL;
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
header->crc32 = fileGetU32(file);
|
||||
@@ -2477,6 +2513,8 @@ static ZIP_CENTRAL_DIRECTORY_ENTRY *zipReadNextCentralDirectoryEntry(FILE *file)
|
||||
entry->offsetOfLocalHeader = fileGetU32(file);
|
||||
/* file name (variable size) */
|
||||
if (entry->fileNameLen > 0) {
|
||||
/* fileNameLen is uint16_t (ZIP spec, 2-byte field),
|
||||
* so fileNameLen + 1 cannot overflow size_t */
|
||||
entry->fileName = OPENSSL_zalloc(entry->fileNameLen + 1);
|
||||
size = fread(entry->fileName, 1, entry->fileNameLen, file);
|
||||
if (size != entry->fileNameLen) {
|
||||
@@ -2487,6 +2525,8 @@ static ZIP_CENTRAL_DIRECTORY_ENTRY *zipReadNextCentralDirectoryEntry(FILE *file)
|
||||
}
|
||||
/* extra field (variable size) */
|
||||
if (entry->extraFieldLen > 0) {
|
||||
/* extraFieldLen is uint16_t (ZIP spec, 2-byte field),
|
||||
* so extraFieldLen + 1 cannot overflow size_t */
|
||||
entry->extraField = OPENSSL_zalloc(entry->extraFieldLen + 1);
|
||||
size = fread(entry->extraField, 1, entry->extraFieldLen, file);
|
||||
if (size != entry->extraFieldLen) {
|
||||
@@ -2497,6 +2537,8 @@ static ZIP_CENTRAL_DIRECTORY_ENTRY *zipReadNextCentralDirectoryEntry(FILE *file)
|
||||
}
|
||||
/* file comment (variable size) */
|
||||
if (entry->fileCommentLen > 0) {
|
||||
/* fileCommentLen is uint16_t (ZIP spec, 2-byte field),
|
||||
* so fileCommentLen + 1 cannot overflow size_t */
|
||||
entry->fileComment = OPENSSL_zalloc(entry->fileCommentLen + 1);
|
||||
size = fread(entry->fileComment, 1, entry->fileCommentLen, file);
|
||||
if (size != entry->fileCommentLen) {
|
||||
@@ -2635,6 +2677,8 @@ static int readZipEOCDR(ZIP_EOCDR *eocdr, FILE *file)
|
||||
}
|
||||
#endif
|
||||
if (eocdr->commentLen > 0) {
|
||||
/* ZIP_EOCDR commentLen is uint16_t (ZIP spec, 2-byte field),
|
||||
* so fileCommentLen + 1 cannot overflow size_t */
|
||||
eocdr->comment = OPENSSL_zalloc(eocdr->commentLen + 1);
|
||||
size = fread(eocdr->comment, 1, eocdr->commentLen, file);
|
||||
if (size != eocdr->commentLen) {
|
||||
|
||||
@@ -342,7 +342,8 @@ static int cab_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
|
||||
const u_char *p = content_val->data;
|
||||
SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
|
||||
if (idc) {
|
||||
if (spc_extract_digest_safe(idc, mdbuf, &mdtype) < 0) {
|
||||
if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
|
||||
fprintf(stderr, "Failed to extract message digest from signature\n\n");
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
|
||||
@@ -390,15 +390,12 @@ static int cat_print_content_member_digest(ASN1_TYPE *content)
|
||||
idc = d2i_SpcIndirectDataContent(NULL, &data, ASN1_STRING_length(value));
|
||||
if (!idc)
|
||||
return 0; /* FAILED */
|
||||
if (spc_extract_digest_safe(idc, mdbuf, &mdtype) < 0) {
|
||||
if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
|
||||
fprintf(stderr, "Failed to extract message digest from signature\n\n");
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
SpcIndirectDataContent_free(idc);
|
||||
if (mdtype == -1) {
|
||||
fprintf(stderr, "Failed to extract current message digest\n\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
printf("\tHash algorithm: %s\n", OBJ_nid2sn(mdtype));
|
||||
print_hash("\tMessage digest", "", mdbuf, EVP_MD_size(EVP_get_digestbynid(mdtype)));
|
||||
return 1; /* OK */
|
||||
|
||||
@@ -548,33 +548,6 @@ SpcLink *spc_link_obsolete_get(void)
|
||||
return link;
|
||||
}
|
||||
|
||||
/*
|
||||
* Safely extract digest from SpcIndirectDataContent
|
||||
* [in] idc: parsed SpcIndirectDataContent
|
||||
* [out] mdbuf: output buffer (must be EVP_MAX_MD_SIZE bytes)
|
||||
* [out] mdtype: digest algorithm's NID
|
||||
* [returns] -1 on error or digest length on success
|
||||
*/
|
||||
int spc_extract_digest_safe(SpcIndirectDataContent *idc,
|
||||
u_char *mdbuf, int *mdtype)
|
||||
{
|
||||
int digest_len;
|
||||
|
||||
if (!idc || !idc->messageDigest || !idc->messageDigest->digest ||
|
||||
!idc->messageDigest->digestAlgorithm) {
|
||||
fprintf(stderr, "Missing digest data\n");
|
||||
return -1;
|
||||
}
|
||||
digest_len = idc->messageDigest->digest->length;
|
||||
if (digest_len <= 0 || digest_len > EVP_MAX_MD_SIZE) {
|
||||
fprintf(stderr, "Invalid digest length: %d\n", digest_len);
|
||||
return -1;
|
||||
}
|
||||
memcpy(mdbuf, idc->messageDigest->digest->data, (size_t)digest_len);
|
||||
*mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm);
|
||||
return digest_len;
|
||||
}
|
||||
|
||||
/*
|
||||
* [in] mdbuf, cmdbuf: message digests
|
||||
* [in] mdtype: message digest algorithm type
|
||||
@@ -590,6 +563,37 @@ int compare_digests(u_char *mdbuf, u_char *cmdbuf, int mdtype)
|
||||
return mdok;
|
||||
}
|
||||
|
||||
/*
|
||||
* Safely extract digest from SpcIndirectDataContent with bounds checking.
|
||||
* This function validates that the digest length from the ASN.1 structure
|
||||
* does not exceed the destination buffer size, preventing buffer overflows
|
||||
* from maliciously crafted signatures.
|
||||
* [in] idc: parsed SpcIndirectDataContent structure
|
||||
* [out] mdbuf: output buffer (must be at least EVP_MAX_MD_SIZE bytes)
|
||||
* [out] mdtype: digest algorithm NID
|
||||
* [returns] digest length on success, -1 on error
|
||||
*/
|
||||
int spc_indirect_data_content_get_digest(SpcIndirectDataContent *idc, u_char *mdbuf, int *mdtype)
|
||||
{
|
||||
int digest_len;
|
||||
|
||||
if (!idc || !idc->messageDigest || !idc->messageDigest->digest ||
|
||||
!idc->messageDigest->digestAlgorithm) {
|
||||
return -1; /* FAILED */
|
||||
}
|
||||
digest_len = idc->messageDigest->digest->length;
|
||||
|
||||
/* Validate digest length to prevent buffer overflow */
|
||||
if (digest_len <= 0 || digest_len > EVP_MAX_MD_SIZE) {
|
||||
fprintf(stderr, "Invalid digest length in signature: %d (expected 1-%d)\n",
|
||||
digest_len, EVP_MAX_MD_SIZE);
|
||||
return -1; /* FAILED */
|
||||
}
|
||||
*mdtype = OBJ_obj2nid(idc->messageDigest->digestAlgorithm->algorithm);
|
||||
memcpy(mdbuf, idc->messageDigest->digest->data, (size_t)digest_len);
|
||||
return digest_len; /* OK */
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
@@ -645,6 +649,10 @@ static int spc_indirect_data_content_create(u_char **blob, int *len, FILE_FORMAT
|
||||
idc->data->value->type = V_ASN1_SEQUENCE;
|
||||
idc->data->value->value.sequence = ASN1_STRING_new();
|
||||
idc->data->type = ctx->format->data_blob_get(&p, &l, ctx);
|
||||
if (!idc->data->type) {
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
idc->data->value->value.sequence->data = p;
|
||||
idc->data->value->value.sequence->length = l;
|
||||
idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(mdtype);
|
||||
|
||||
@@ -24,9 +24,8 @@ int is_content_type(PKCS7 *p7, const char *objid);
|
||||
MsCtlContent *ms_ctl_content_get(PKCS7 *p7);
|
||||
ASN1_TYPE *catalog_content_get(CatalogAuthAttr *attribute);
|
||||
SpcLink *spc_link_obsolete_get(void);
|
||||
int spc_extract_digest_safe(SpcIndirectDataContent *idc,
|
||||
u_char *mdbuf, int *mdtype);
|
||||
int compare_digests(u_char *mdbuf, u_char *cmdbuf, int mdtype);
|
||||
int spc_indirect_data_content_get_digest(SpcIndirectDataContent *idc, u_char *mdbuf, int *mdtype);
|
||||
|
||||
/*
|
||||
Local Variables:
|
||||
|
||||
@@ -419,7 +419,8 @@ static int msi_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
|
||||
const u_char *p = content_val->data;
|
||||
SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
|
||||
if (idc) {
|
||||
if (spc_extract_digest_safe(idc, mdbuf, &mdtype) < 0) {
|
||||
if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
|
||||
fprintf(stderr, "Failed to extract message digest from signature\n\n");
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
|
||||
+32
-25
@@ -3082,12 +3082,8 @@ static int verify_content_member_digest(FILE_FORMAT_CTX *ctx, ASN1_TYPE *content
|
||||
fprintf(stderr, "Failed to extract SpcIndirectDataContent data\n");
|
||||
return 1; /* FAILED */
|
||||
}
|
||||
if (spc_extract_digest_safe(idc, mdbuf, &mdtype) < 0) {
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 1; /* FAILED */
|
||||
}
|
||||
if (mdtype == -1) {
|
||||
fprintf(stderr, "Failed to extract current message digest\n\n");
|
||||
if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
|
||||
fprintf(stderr, "Failed to extract message digest from signature\n\n");
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 1; /* FAILED */
|
||||
}
|
||||
@@ -5082,7 +5078,7 @@ static void engine_control_set(GLOBAL_OPTIONS *options, const char *arg)
|
||||
}
|
||||
#endif /* OPENSSL_NO_ENGINE */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
static int main_execute(int argc, char **argv)
|
||||
{
|
||||
FILE_FORMAT_CTX *ctx = NULL;
|
||||
GLOBAL_OPTIONS options;
|
||||
@@ -5094,24 +5090,6 @@ int main(int argc, char **argv)
|
||||
/* reset options */
|
||||
memset(&options, 0, sizeof(GLOBAL_OPTIONS));
|
||||
|
||||
/* Set up OpenSSL */
|
||||
if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS
|
||||
| OPENSSL_INIT_ADD_ALL_CIPHERS
|
||||
| OPENSSL_INIT_ADD_ALL_DIGESTS
|
||||
| OPENSSL_INIT_LOAD_CONFIG, NULL))
|
||||
DO_EXIT_0("Failed to init crypto\n");
|
||||
|
||||
/* create some MS Authenticode OIDS we need later on */
|
||||
if (!OBJ_create(SPC_STATEMENT_TYPE_OBJID, NULL, NULL)
|
||||
/* PKCS9_COUNTER_SIGNATURE exists as OpenSSL OBJ_pkcs9_countersignature */
|
||||
|| !OBJ_create(MS_JAVA_SOMETHING, NULL, NULL)
|
||||
|| !OBJ_create(SPC_SP_OPUS_INFO_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(SPC_NESTED_SIGNATURE_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(SPC_UNAUTHENTICATED_DATA_BLOB_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(SPC_RFC3161_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(PKCS9_SEQUENCE_NUMBER, NULL, NULL))
|
||||
DO_EXIT_0("Failed to create objects\n");
|
||||
|
||||
/* commands and options initialization */
|
||||
if (!main_configure(argc, argv, &options))
|
||||
goto err_cleanup;
|
||||
@@ -5359,6 +5337,35 @@ err_cleanup:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
/* one-time OpenSSL initialization */
|
||||
if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS
|
||||
| OPENSSL_INIT_ADD_ALL_CIPHERS
|
||||
| OPENSSL_INIT_ADD_ALL_DIGESTS
|
||||
| OPENSSL_INIT_LOAD_CONFIG, NULL))
|
||||
DO_EXIT_0("Failed to init crypto\n");
|
||||
|
||||
/* create some MS Authenticode OIDS we need later on */
|
||||
if (!OBJ_create(SPC_STATEMENT_TYPE_OBJID, NULL, NULL)
|
||||
/* PKCS9_COUNTER_SIGNATURE exists as OpenSSL OBJ_pkcs9_countersignature */
|
||||
|| !OBJ_create(MS_JAVA_SOMETHING, NULL, NULL)
|
||||
|| !OBJ_create(SPC_SP_OPUS_INFO_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(SPC_NESTED_SIGNATURE_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(SPC_UNAUTHENTICATED_DATA_BLOB_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(SPC_RFC3161_OBJID, NULL, NULL)
|
||||
|| !OBJ_create(PKCS9_SEQUENCE_NUMBER, NULL, NULL))
|
||||
DO_EXIT_0("Failed to create objects\n");
|
||||
|
||||
/* perform the requested operation */
|
||||
ret = main_execute(argc, argv);
|
||||
|
||||
err_cleanup:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
Local Variables:
|
||||
c-basic-offset: 4
|
||||
|
||||
@@ -163,8 +163,10 @@ static ASN1_OBJECT *pe_spc_image_data_get(u_char **p, int *plen, FILE_FORMAT_CTX
|
||||
if (EVP_MD_size(ctx->options->md) > EVP_MD_size(EVP_sha1()))
|
||||
phtype = NID_sha256;
|
||||
link = pe_page_hash_link_get(ctx, phtype);
|
||||
if (!link)
|
||||
if (!link) {
|
||||
SpcPeImageData_free(pid);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
pid->file = link;
|
||||
} else {
|
||||
pid->file = spc_link_obsolete_get();
|
||||
@@ -255,7 +257,9 @@ static int pe_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
if (spc_extract_digest_safe(idc, mdbuf, &mdtype) < 0) {
|
||||
if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
|
||||
fprintf(stderr, "Failed to extract message digest from signature\n\n");
|
||||
OPENSSL_free(ph);
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
@@ -402,6 +406,7 @@ static PKCS7 *pe_pkcs7_signature_new(FILE_FORMAT_CTX *ctx, BIO *hash)
|
||||
content = spc_indirect_data_content_get(hash, ctx);
|
||||
if (!content) {
|
||||
fprintf(stderr, "Failed to get spcIndirectDataContent\n");
|
||||
PKCS7_free(p7);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
if (!sign_spc_indirect_data_content(p7, content)) {
|
||||
@@ -914,12 +919,25 @@ static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype)
|
||||
uint16_t nsections, opthdr_size;
|
||||
uint32_t alignment, pagesize, hdrsize;
|
||||
uint32_t rs, ro, l, lastpos = 0;
|
||||
int pphlen, phlen, i, pi = 1;
|
||||
size_t written;
|
||||
u_char *res, *zeroes;
|
||||
int mdlen, pphlen, phlen, i, pi = 1;
|
||||
size_t written, off, sect_off, sect_tbl, need;
|
||||
u_char *res = NULL, *zeroes = NULL;
|
||||
char *sections;
|
||||
const EVP_MD *md = EVP_get_digestbynid(phtype);
|
||||
BIO *bhash;
|
||||
BIO *bhash = NULL;
|
||||
uint32_t filebound;
|
||||
size_t pphlen_sz, sections_factor;
|
||||
|
||||
if (rphlen == NULL || ctx == NULL || ctx->options == NULL || ctx->pe_ctx == NULL
|
||||
|| ctx->options->indata == NULL)
|
||||
return NULL;
|
||||
|
||||
if (md == NULL)
|
||||
return NULL;
|
||||
|
||||
mdlen = EVP_MD_size(md);
|
||||
if (mdlen <= 0)
|
||||
return NULL;
|
||||
|
||||
/* NumberOfSections indicates the size of the section table,
|
||||
* which immediately follows the headers, can be up to 65535 under Vista and later */
|
||||
@@ -961,10 +979,46 @@ static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype)
|
||||
fprintf(stderr, "Corrupted optional header size: 0x%08X\n", opthdr_size);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
pphlen = 4 + EVP_MD_size(md);
|
||||
phlen = pphlen * (3 + (int)nsections + (int)(ctx->pe_ctx->fileend / pagesize));
|
||||
/* Validate that pagesize >= hdrsize to prevent integer underflow */
|
||||
if (pagesize < hdrsize) {
|
||||
fprintf(stderr, "Page size (0x%08X) is smaller than header size (0x%08X)\n",
|
||||
pagesize, hdrsize);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
pphlen = 4 + mdlen;
|
||||
|
||||
/* Compute an upper bound for result size and guard overflow */
|
||||
pphlen_sz = (size_t)pphlen;
|
||||
sections_factor = 3 + (size_t)nsections + ((size_t)ctx->pe_ctx->fileend / pagesize);
|
||||
if (sections_factor > SIZE_MAX / pphlen_sz) {
|
||||
fprintf(stderr, "Page hash allocation size would overflow\n");
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
phlen = (int)(pphlen_sz * sections_factor);
|
||||
/* Sanity limit - page hash shouldn't exceed reasonable size (16 MB) */
|
||||
if (phlen < 0 || (size_t)phlen > SIZE_16M) {
|
||||
fprintf(stderr, "Page hash size exceeds limit: %d\n", phlen);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
|
||||
/* Determine the file boundary for section data validation */
|
||||
filebound = ctx->pe_ctx->sigpos ? ctx->pe_ctx->sigpos : ctx->pe_ctx->fileend;
|
||||
|
||||
/* Validate section table bounds before reading section headers */
|
||||
sect_off = (size_t)ctx->pe_ctx->header_size + 24u + (size_t)opthdr_size;
|
||||
sect_tbl = (size_t)nsections * 40u;
|
||||
|
||||
if (sect_off > (size_t)filebound || sect_tbl > (size_t)filebound - sect_off) {
|
||||
fprintf(stderr, "Section table out of bounds: off=%zu size=%zu filebound=%u\n",
|
||||
sect_off, sect_tbl, filebound);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
sections = (char *)ctx->options->indata + sect_off;
|
||||
|
||||
bhash = BIO_new(BIO_f_md());
|
||||
if (bhash == NULL)
|
||||
return NULL;
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-qual"
|
||||
@@ -977,7 +1031,10 @@ static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype)
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
BIO_push(bhash, BIO_new(BIO_s_null()));
|
||||
if (BIO_push(bhash, BIO_new(BIO_s_null())) == NULL) {
|
||||
BIO_free_all(bhash);
|
||||
return NULL;
|
||||
}
|
||||
if (!BIO_write_ex(bhash, ctx->options->indata, ctx->pe_ctx->header_size + 88, &written)
|
||||
|| written != ctx->pe_ctx->header_size + 88) {
|
||||
BIO_free_all(bhash);
|
||||
@@ -989,36 +1046,84 @@ static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype)
|
||||
BIO_free_all(bhash);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
if (!BIO_write_ex(bhash,
|
||||
ctx->options->indata + ctx->pe_ctx->header_size + 160 + ctx->pe_ctx->pe32plus*16,
|
||||
hdrsize - (ctx->pe_ctx->header_size + 160 + ctx->pe_ctx->pe32plus*16), &written)
|
||||
|| written != hdrsize - (ctx->pe_ctx->header_size + 160 + ctx->pe_ctx->pe32plus*16)) {
|
||||
off = ctx->pe_ctx->header_size + 160 + (size_t)ctx->pe_ctx->pe32plus * 16;
|
||||
if (hdrsize < off || hdrsize > filebound) {
|
||||
BIO_free_all(bhash);
|
||||
return NULL; /* FAILED: header too small */
|
||||
}
|
||||
if (!BIO_write_ex(bhash, ctx->options->indata + off, (size_t)hdrsize - off, &written)
|
||||
|| written != hdrsize - off) {
|
||||
BIO_free_all(bhash);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
if (pagesize < hdrsize) {
|
||||
BIO_free_all(bhash);
|
||||
return NULL; /* FAILED: header larger than page */
|
||||
}
|
||||
zeroes = OPENSSL_zalloc((size_t)pagesize);
|
||||
if (!BIO_write_ex(bhash, zeroes, pagesize - hdrsize, &written)
|
||||
|| written != pagesize - hdrsize) {
|
||||
if (zeroes == NULL) {
|
||||
BIO_free_all(bhash);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
if (!BIO_write_ex(bhash, zeroes, (size_t)pagesize - (size_t)hdrsize, &written)
|
||||
|| written != (size_t)pagesize - (size_t)hdrsize) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
res = OPENSSL_malloc((size_t)phlen);
|
||||
if (res == NULL) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
memset(res, 0, 4);
|
||||
BIO_gets(bhash, (char*)res + 4, EVP_MD_size(md));
|
||||
if (BIO_gets(bhash, (char *)res + 4, mdlen) != mdlen) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
BIO_free_all(bhash);
|
||||
sections = ctx->options->indata + ctx->pe_ctx->header_size + 24 + opthdr_size;
|
||||
for (i=0; i<nsections; i++) {
|
||||
/* Resource Table address and size */
|
||||
bhash = NULL;
|
||||
|
||||
for (i = 0; i < (int)nsections; i++) {
|
||||
/* SizeOfRawData and PointerToRawData from section header */
|
||||
rs = GET_UINT32_LE(sections + 16);
|
||||
ro = GET_UINT32_LE(sections + 20);
|
||||
if (rs == 0 || rs >= UINT32_MAX) {
|
||||
if (rs == 0) {
|
||||
sections += 40;
|
||||
continue;
|
||||
}
|
||||
for (l=0; l<rs; l+=pagesize, pi++) {
|
||||
PUT_UINT32_LE(ro + l, res + pi*pphlen);
|
||||
/* Validate section bounds against file size to prevent OOB read */
|
||||
if (ro >= filebound || rs > filebound - ro) {
|
||||
fprintf(stderr, "Section %d has invalid bounds: offset=0x%08X, size=0x%08X, fileend=0x%08X\n",
|
||||
i, ro, rs, filebound);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
for (l = 0; l < rs; l += pagesize, pi++) {
|
||||
need = (size_t)(pi + 1) * (size_t)pphlen;
|
||||
|
||||
/* Prevent OOB write into res if pi grows beyond allocated factor */
|
||||
if (need > (size_t)phlen) {
|
||||
fprintf(stderr, "Page hash buffer overflow prevented: pi=%d need=%zu phlen=%d\n",
|
||||
pi, need, phlen);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
|
||||
PUT_UINT32_LE(ro + l, res + (size_t)pi * (size_t)pphlen);
|
||||
|
||||
bhash = BIO_new(BIO_f_md());
|
||||
if (bhash == NULL) {
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-qual"
|
||||
@@ -1033,17 +1138,24 @@ static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype)
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
BIO_push(bhash, BIO_new(BIO_s_null()));
|
||||
if (rs - l < pagesize) {
|
||||
if (!BIO_write_ex(bhash, ctx->options->indata + ro + l, rs - l, &written)
|
||||
|| written != rs - l) {
|
||||
if (BIO_push(bhash, BIO_new(BIO_s_null())) == NULL) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL;
|
||||
}
|
||||
if (l < rs && rs - l < pagesize) {
|
||||
size_t tail = (size_t)(rs - l);
|
||||
|
||||
if (!BIO_write_ex(bhash, ctx->options->indata + ro + l, tail, &written)
|
||||
|| written != tail) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
if (!BIO_write_ex(bhash, zeroes, pagesize - (rs - l), &written)
|
||||
|| written != pagesize - (rs - l)) {
|
||||
if (!BIO_write_ex(bhash, zeroes, pagesize - tail, &written)
|
||||
|| written != pagesize - tail) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
@@ -1058,17 +1170,35 @@ static u_char *pe_page_hash_calc(int *rphlen, FILE_FORMAT_CTX *ctx, int phtype)
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
}
|
||||
BIO_gets(bhash, (char*)res + pi*pphlen + 4, EVP_MD_size(md));
|
||||
if (BIO_gets(bhash, (char *)res + (size_t)pi * (size_t)pphlen + 4, mdlen) != mdlen) {
|
||||
BIO_free_all(bhash);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
BIO_free_all(bhash);
|
||||
bhash = NULL;
|
||||
}
|
||||
lastpos = ro + rs;
|
||||
sections += 40;
|
||||
}
|
||||
PUT_UINT32_LE(lastpos, res + pi*pphlen);
|
||||
memset(res + pi*pphlen + 4, 0, (size_t)EVP_MD_size(md));
|
||||
/* Final entry */
|
||||
need = (size_t)(pi + 1) * (size_t)pphlen;
|
||||
|
||||
if (need > (size_t)phlen) {
|
||||
fprintf(stderr, "Page hash buffer overflow prevented at final entry: pi=%d need=%zu phlen=%d\n",
|
||||
pi, need, phlen);
|
||||
OPENSSL_free(zeroes);
|
||||
OPENSSL_free(res);
|
||||
return NULL; /* FAILED */
|
||||
}
|
||||
|
||||
PUT_UINT32_LE(lastpos, res + (size_t)pi * (size_t)pphlen);
|
||||
memset(res + (size_t)pi * (size_t)pphlen + 4, 0, (size_t)mdlen);
|
||||
pi++;
|
||||
|
||||
OPENSSL_free(zeroes);
|
||||
*rphlen = pi*pphlen;
|
||||
*rphlen = pi * pphlen;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1088,6 +1218,10 @@ static int pe_verify_page_hash(FILE_FORMAT_CTX *ctx, u_char *ph, int phlen, int
|
||||
if (!ph)
|
||||
return 1; /* OK */
|
||||
cph = pe_page_hash_calc(&cphlen, ctx, phtype);
|
||||
if (!cph) {
|
||||
fprintf(stderr, "Page hash verification failed: could not calculate page hash\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
mdok = (phlen == cphlen) && !memcmp(ph, cph, (size_t)phlen);
|
||||
printf("Page hash algorithm : %s\n", OBJ_nid2sn(phtype));
|
||||
if (ctx->options->verbose) {
|
||||
@@ -1190,7 +1324,8 @@ static int pe_check_file(FILE_FORMAT_CTX *ctx)
|
||||
{
|
||||
uint32_t real_pe_checksum, sum = 0;
|
||||
|
||||
if (!ctx) {
|
||||
if (ctx == NULL || ctx->pe_ctx == NULL || ctx->options == NULL
|
||||
|| ctx->options->indata == NULL) {
|
||||
fprintf(stderr, "Init error\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
@@ -1202,25 +1337,52 @@ static int pe_check_file(FILE_FORMAT_CTX *ctx)
|
||||
printf("Calculated PE checksum: %08X\n", real_pe_checksum);
|
||||
printf("Warning: invalid PE checksum\n");
|
||||
}
|
||||
/* Signature directory bounds */
|
||||
if (ctx->pe_ctx->sigpos == 0 || ctx->pe_ctx->siglen == 0
|
||||
|| ctx->pe_ctx->sigpos > ctx->pe_ctx->fileend) {
|
||||
|| ctx->pe_ctx->sigpos > ctx->pe_ctx->fileend
|
||||
|| ctx->pe_ctx->siglen > ctx->pe_ctx->fileend - ctx->pe_ctx->sigpos) {
|
||||
fprintf(stderr, "No signature found\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
/*
|
||||
* Validate WIN_CERTIFICATE chain.
|
||||
* If the sum of the rounded dwLength values does not equal the Size value,
|
||||
* then either the attribute certificate table or the Size field is corrupted.
|
||||
*/
|
||||
while (sum < ctx->pe_ctx->siglen) {
|
||||
uint32_t len = GET_UINT32_LE(ctx->options->indata + ctx->pe_ctx->sigpos + sum);
|
||||
if (ctx->pe_ctx->siglen - len > 8) {
|
||||
uint32_t len, off;
|
||||
|
||||
/* Prevent overflow in sigpos + sum */
|
||||
if (sum > UINT32_MAX - ctx->pe_ctx->sigpos) {
|
||||
fprintf(stderr, "Corrupted attribute certificate table\n");
|
||||
fprintf(stderr, "Attribute certificate table size : %08X\n", ctx->pe_ctx->siglen);
|
||||
fprintf(stderr, "Attribute certificate entry length: %08X\n\n", len);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
/* quadword align data */
|
||||
len += len % 8 ? 8 - len % 8 : 0;
|
||||
off = ctx->pe_ctx->sigpos + sum;
|
||||
|
||||
/* Need at least 4 bytes to read dwLength */
|
||||
if (off > ctx->pe_ctx->fileend || ctx->pe_ctx->fileend - off < 4) {
|
||||
fprintf(stderr, "Corrupted attribute certificate table\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
len = GET_UINT32_LE(ctx->options->indata + off);
|
||||
|
||||
/* dwLength must include the 8-byte WIN_CERTIFICATE header */
|
||||
if (len < 8 || len > ctx->pe_ctx->siglen - sum || len > ctx->pe_ctx->fileend - off) {
|
||||
fprintf(stderr, "Corrupted attribute certificate table\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
|
||||
/* Quadword align data */
|
||||
if (len % 8) {
|
||||
uint32_t pad = 8 - (len % 8);
|
||||
|
||||
/* Ensure quadword alignment does not overflow or exceed remaining table size */
|
||||
if (pad > ctx->pe_ctx->siglen - sum - len) {
|
||||
fprintf(stderr, "Corrupted attribute certificate table\n");
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
len += pad;
|
||||
}
|
||||
sum += len;
|
||||
}
|
||||
if (sum != ctx->pe_ctx->siglen) {
|
||||
|
||||
@@ -294,7 +294,8 @@ static int script_verify_digests(FILE_FORMAT_CTX *ctx, PKCS7 *p7)
|
||||
const u_char *p = content_val->data;
|
||||
SpcIndirectDataContent *idc = d2i_SpcIndirectDataContent(NULL, &p, content_val->length);
|
||||
if (idc) {
|
||||
if (spc_extract_digest_safe(idc, mdbuf, &mdtype) < 0) {
|
||||
if (spc_indirect_data_content_get_digest(idc, mdbuf, &mdtype) < 0) {
|
||||
fprintf(stderr, "Failed to extract message digest from signature\n\n");
|
||||
SpcIndirectDataContent_free(idc);
|
||||
return 0; /* FAILED */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user