Skip to content

Commit

Permalink
OLE2: Fix bounds check on OLE2 encryption info check
Browse files Browse the repository at this point in the history
The checks for the encryption info cspName and encryption verifier don't
have the size of the overall file available for the check and may
overflow.

This commit passes in the size of the file to the
initialize_encryption_key() function and does all size checks within
that function instead of doing the overall size check before that
function.

Resolves: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=60563
  • Loading branch information
micahsnyder committed Aug 2, 2023
1 parent cec59d7 commit 6e11fc3
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 67 deletions.
23 changes: 8 additions & 15 deletions libclamav/ole2_encryption.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ typedef struct __attribute__((packed)) {
uint32_t reserved1;
uint32_t reserved2; /* MUST be 0 */

uint8_t cspName[1]; /* really the rest of the data in the block. Starts with a
string of wide characters, followed by the encryption verifier.
It is 44 instead of 32 because this structure is only used inside
encryption_info_stream_standard_t (below). It is in two different
structures because of the way the documentation is written.
*/
// uint8_t cspName[variable]; /* really the rest of the data in the block. Starts with a
// string of wide characters, followed by the encryption verifier.
// It is 44 instead of 32 because this structure is only used inside
// encryption_info_stream_standard_t (below). It is in two different
// structures because of the way the documentation is written.
// */

} encryption_info_t;

/*
/*
* https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/2895eba1-acb1-4624-9bde-2cdad3fea015
*/
typedef struct __attribute__((packed)) {
Expand All @@ -54,17 +54,10 @@ typedef struct __attribute__((packed)) {

uint32_t size;

union {
encryption_info_t encryptionInfo;
uint8_t padding[512 - 12]; /* Subtract the size of version_major, version_minor, flags and size.
This consumes a sector (512 bytes), so make sure enough space is always allocated.
*/
};
encryption_info_t encryptionInfo;

} encryption_info_stream_standard_t;

#define CSP_NAME_LENGTH(__ptr__) (sizeof(__ptr__->padding) - sizeof(__ptr__->encryptionInfo) + 1)

/* https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/e5ad39b8-9bc1-4a19-bad3-44e6246d21e6 */
typedef struct __attribute__((packed)) {
uint32_t salt_size;
Expand Down
124 changes: 72 additions & 52 deletions libclamav/ole2_extract.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ typedef struct ole2_header_tag {
unsigned char clsid[16];
uint16_t minor_version __attribute__((packed));
uint16_t dll_version __attribute__((packed));
int16_t byte_order __attribute__((packed)); /* -2=intel */
int16_t byte_order __attribute__((packed)); /* -2=intel */

uint16_t log2_big_block_size __attribute__((packed)); /* usually 9 (2^9 = 512) */
uint32_t log2_small_block_size __attribute__((packed)); /* usually 6 (2^6 = 64) */
Expand Down Expand Up @@ -134,7 +134,7 @@ typedef struct ole2_header_tag {
* https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/60fe8611-66c3-496b-b70d-a504c94c9ace
*/
typedef struct property_tag {
char name[64]; /* in unicode */
char name[64]; /* in unicode */
uint16_t name_size __attribute__((packed));
unsigned char type; /* 1=dir 2=file 5=root */
unsigned char color; /* black or red */
Expand Down Expand Up @@ -2089,7 +2089,7 @@ static cl_error_t generate_key_aes(const char *const password, encryption_key_t

tmp = verifier->salt_size;
if (verifier->salt_size > sizeof(verifier->salt)) {
cli_warnmsg("ole2: Invalid salt length '0x%x'\n", verifier->salt_size);
cli_dbgmsg("ole2: Invalid salt length '0x%x'\n", verifier->salt_size);
tmp = sizeof(verifier->salt);
}
memcpy(buffer, verifier->salt, tmp);
Expand Down Expand Up @@ -2193,7 +2193,7 @@ static bool verify_key_aes(const encryption_key_t *const key, encryption_verifie
// If it claims to be LARGER than 32 bytes, we have a problem - because the buffer isn't that big.
actual_hash_size = verifier->verifier_hash_size;
if (actual_hash_size > sizeof(verifier->encrypted_verifier_hash)) {
cli_warnmsg("ole2: Invalid encrypted verifier hash length 0x%x\n", verifier->verifier_hash_size);
cli_dbgmsg("ole2: Invalid encrypted verifier hash length 0x%x\n", verifier->verifier_hash_size);
actual_hash_size = sizeof(verifier->encrypted_verifier_hash);
}

Expand Down Expand Up @@ -2250,7 +2250,7 @@ static bool verify_key_aes(const encryption_key_t *const key, encryption_verifie
/**
* @brief Initialize encryption key, if the encryption validation passes.
*
* @param headerPtr Pointer to the encryption header.
* @param encryptionInfo Pointer to the encryption header.
* @param encryptionKey [out] Pointer to encryption_key_t structure to be initialized by this function.
* @return Success or failure depending on whether or not the
* encryption verifier was successful with the
Expand All @@ -2261,86 +2261,94 @@ static bool verify_key_aes(const encryption_key_t *const key, encryption_verifie
* https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/2895eba1-acb1-4624-9bde-2cdad3fea015
*
*/
static bool initialize_encryption_key(const encryption_info_stream_standard_t *headerPtr,
encryption_key_t *encryptionKey)
static bool initialize_encryption_key(
const uint8_t *encryptionInfoStreamPtr,
size_t remainingBytes,
encryption_key_t *encryptionKey)
{

bool bRet = false;
size_t idx = 0;
encryption_key_t key;
encryption_verifier_t ev;
bool bAES = false;

encryption_info_stream_standard_t encryptionInfo = {0};
uint16_t *encryptionInfo_CSPName = NULL;
const uint8_t *encryptionVerifierPtr = NULL;
encryption_verifier_t encryptionVerifier = {0};

// Populate the encryption_info_stream_standard_t structure
copy_encryption_info_stream_standard(&encryptionInfo, encryptionInfoStreamPtr);

memset(encryptionKey, 0, sizeof(encryption_key_t));
memset(&key, 0, sizeof(encryption_key_t));

cli_dbgmsg("Major Version = 0x%x\n", headerPtr->version_major);
cli_dbgmsg("Minor Version = 0x%x\n", headerPtr->version_minor);
cli_dbgmsg("Flags = 0x%x\n", headerPtr->flags);
cli_dbgmsg("Major Version = 0x%x\n", encryptionInfo.version_major);
cli_dbgmsg("Minor Version = 0x%x\n", encryptionInfo.version_minor);
cli_dbgmsg("Flags = 0x%x\n", encryptionInfo.flags);

/*Bit 0 and 1 must be 0*/
if (1 & headerPtr->flags) {
if (1 & encryptionInfo.flags) {
cli_dbgmsg("ole2: Invalid first bit, must be 0\n");
goto done;
}

if ((1 << 1) & headerPtr->flags) {
if ((1 << 1) & encryptionInfo.flags) {
cli_dbgmsg("ole2: Invalid second bit, must be 0\n");
goto done;
}

// https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/200a3d61-1ab4-4402-ae11-0290b28ab9cb
if ((SE_HEADER_FDOCPROPS & headerPtr->flags)) {
if ((SE_HEADER_FDOCPROPS & encryptionInfo.flags)) {
cli_dbgmsg("ole2: Unsupported document properties encrypted\n");
goto done;
}

if ((SE_HEADER_FEXTERNAL & headerPtr->flags) &&
(SE_HEADER_FEXTERNAL != headerPtr->flags)) {
if ((SE_HEADER_FEXTERNAL & encryptionInfo.flags) &&
(SE_HEADER_FEXTERNAL != encryptionInfo.flags)) {
cli_dbgmsg("ole2: Invalid fExternal flags. If fExternal bit is set, nothing else can be\n");
goto done;
}

if (SE_HEADER_FAES & headerPtr->flags) {
if (!(SE_HEADER_FCRYPTOAPI & headerPtr->flags)) {
if (SE_HEADER_FAES & encryptionInfo.flags) {
if (!(SE_HEADER_FCRYPTOAPI & encryptionInfo.flags)) {
cli_dbgmsg("ole2: Invalid combo of fAES and fCryptoApi flags\n");
goto done;
}

cli_dbgmsg("Flags: AES\n");
cli_dbgmsg("Flags = AES\n");
}

cli_dbgmsg("Size = 0x%x\n", headerPtr->size);
cli_dbgmsg("Size = 0x%x\n", encryptionInfo.size);

if (headerPtr->flags != headerPtr->encryptionInfo.flags) {
if (encryptionInfo.flags != encryptionInfo.encryptionInfo.flags) {
cli_dbgmsg("ole2: Flags must match\n");
goto done;
}

if (0 != headerPtr->encryptionInfo.sizeExtra) {
if (0 != encryptionInfo.encryptionInfo.sizeExtra) {
cli_dbgmsg("ole2: Size Extra must be 0\n");
goto done;
}

switch (headerPtr->encryptionInfo.algorithmID) {
switch (encryptionInfo.encryptionInfo.algorithmID) {
case SE_HEADER_EI_AES128:
if (SE_HEADER_EI_AES128_KEYSIZE != headerPtr->encryptionInfo.keySize) {
if (SE_HEADER_EI_AES128_KEYSIZE != encryptionInfo.encryptionInfo.keySize) {
cli_dbgmsg("ole2: Key length does not match algorithm id\n");
goto done;
}
bAES = true;
break;
case SE_HEADER_EI_AES192:
// not implemented
if (SE_HEADER_EI_AES192_KEYSIZE != headerPtr->encryptionInfo.keySize) {
if (SE_HEADER_EI_AES192_KEYSIZE != encryptionInfo.encryptionInfo.keySize) {
cli_dbgmsg("ole2: Key length does not match algorithm id\n");
goto done;
}
bAES = true;
goto done;
case SE_HEADER_EI_AES256:
// not implemented
if (SE_HEADER_EI_AES256_KEYSIZE != headerPtr->encryptionInfo.keySize) {
if (SE_HEADER_EI_AES256_KEYSIZE != encryptionInfo.encryptionInfo.keySize) {
cli_dbgmsg("ole2: Key length does not match algorithm id\n");
goto done;
}
Expand All @@ -2350,68 +2358,77 @@ static bool initialize_encryption_key(const encryption_info_stream_standard_t *h
// not implemented
goto done;
default:
cli_dbgmsg("ole2: Invalid Algorithm ID: 0x%x\n", headerPtr->encryptionInfo.algorithmID);
cli_dbgmsg("ole2: Invalid Algorithm ID: 0x%x\n", encryptionInfo.encryptionInfo.algorithmID);
goto done;
}

if (SE_HEADER_EI_SHA1 != headerPtr->encryptionInfo.algorithmIDHash) {
cli_dbgmsg("ole2: Invalid Algorithm ID Hash: 0x%x\n", headerPtr->encryptionInfo.algorithmIDHash);
if (SE_HEADER_EI_SHA1 != encryptionInfo.encryptionInfo.algorithmIDHash) {
cli_dbgmsg("ole2: Invalid Algorithm ID Hash: 0x%x\n", encryptionInfo.encryptionInfo.algorithmIDHash);
goto done;
}

if (!key_length_valid_aes_bits(headerPtr->encryptionInfo.keySize)) {
cli_dbgmsg("ole2: Invalid key size: 0x%x\n", headerPtr->encryptionInfo.keySize);
if (!key_length_valid_aes_bits(encryptionInfo.encryptionInfo.keySize)) {
cli_dbgmsg("ole2: Invalid key size: 0x%x\n", encryptionInfo.encryptionInfo.keySize);
goto done;
}

cli_dbgmsg("KeySize = 0x%x\n", headerPtr->encryptionInfo.keySize);
cli_dbgmsg("KeySize = 0x%x\n", encryptionInfo.encryptionInfo.keySize);

if (SE_HEADER_EI_AES_PROVIDERTYPE != headerPtr->encryptionInfo.providerType) {
if (SE_HEADER_EI_AES_PROVIDERTYPE != encryptionInfo.encryptionInfo.providerType) {
cli_dbgmsg("ole2: WARNING: Provider Type should be '0x%x', is '0x%x'\n",
SE_HEADER_EI_AES_PROVIDERTYPE, headerPtr->encryptionInfo.providerType);
SE_HEADER_EI_AES_PROVIDERTYPE, encryptionInfo.encryptionInfo.providerType);
goto done;
}

cli_dbgmsg("Reserved1: 0x%x\n", headerPtr->encryptionInfo.reserved1);
cli_dbgmsg("Reserved1 = 0x%x\n", encryptionInfo.encryptionInfo.reserved1);

if (0 != headerPtr->encryptionInfo.reserved2) {
cli_dbgmsg("ole2: Reserved 2 must be zero, is 0x%x\n", headerPtr->encryptionInfo.reserved2);
if (0 != encryptionInfo.encryptionInfo.reserved2) {
cli_dbgmsg("ole2: Reserved 2 must be zero, is 0x%x\n", encryptionInfo.encryptionInfo.reserved2);
goto done;
}

/*The encryption info is at the end of the CPSName string.
/* The encryption info is at the end of the CPSName string.
* Find the end, and we'll have the index of the EncryptionVerifier.
* The CPSName string *should* always be either
* 'Microsoft Enhanced RSA and AES Cryptographic Provider'
* or
* 'Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)'
*
*/
for (idx = 0; idx < CSP_NAME_LENGTH(headerPtr) - 1; idx += 2) {
if (((uint16_t *)&(headerPtr->encryptionInfo.cspName[idx]))[0] == 0) {
encryptionInfo_CSPName = (uint16_t *)(encryptionInfoStreamPtr + sizeof(encryption_info_stream_standard_t));
remainingBytes -= sizeof(encryption_info_stream_standard_t);

if (0 == remainingBytes) {
cli_dbgmsg("ole2: No CSPName or encryption_verifier_t\n");
goto done;
}

for (idx = 0; idx * sizeof(uint16_t) < remainingBytes; idx++) {
if (encryptionInfo_CSPName[idx] == 0) {
break;
}
}

idx += 2;
if ((sizeof(headerPtr->encryptionInfo.cspName) - idx) <= sizeof(encryption_verifier_t)) {
encryptionVerifierPtr = (uint8_t*)encryptionInfo_CSPName + (idx + 1) * sizeof(uint16_t);
remainingBytes -= (idx * sizeof(uint16_t));

if (remainingBytes < sizeof(encryption_verifier_t)) {
cli_dbgmsg("ole2: No encryption_verifier_t\n");
goto done;
}
copy_encryption_verifier(&ev, &(headerPtr->encryptionInfo.cspName[idx]));
copy_encryption_verifier(&encryptionVerifier, encryptionVerifierPtr);

key.key_length_bits = headerPtr->encryptionInfo.keySize;
key.key_length_bits = encryptionInfo.encryptionInfo.keySize;
if (!bAES) {
cli_dbgmsg("ole2: Unsupported encryption algorithm\n");
goto done;
}

if (CL_SUCCESS != generate_key_aes("VelvetSweatshop", &key, &ev)) {
if (CL_SUCCESS != generate_key_aes("VelvetSweatshop", &key, &encryptionVerifier)) {
/*Error message printed by generate_key_aes*/
goto done;
}

if (!verify_key_aes(&key, &ev)) {
if (!verify_key_aes(&key, &encryptionVerifier)) {
cli_dbgmsg("ole2: Key verification for '%s' failed, unable to decrypt.\n", "VelvetSweatshop");
goto done;
}
Expand Down Expand Up @@ -2542,11 +2559,14 @@ cl_error_t cli_ole2_extract(const char *dirname, cli_ctx *ctx, struct uniq **fil
/* determine if encrypted with VelvetSweatshop password */
encryption_offset = 4 * (1 << hdr.log2_big_block_size);
if ((encryption_offset + sizeof(encryption_info_stream_standard_t)) <= hdr.m_length) {
encryption_info_stream_standard_t encryption_info_stream_standard;
copy_encryption_info_stream_standard(&encryption_info_stream_standard, &(((const uint8_t *)phdr)[encryption_offset]));
bEncrypted = initialize_encryption_key(&encryption_info_stream_standard, &key);

cli_dbgmsg("Encrypted with VelvetSweatshop\n");
bEncrypted = initialize_encryption_key(
&(((const uint8_t *)phdr)[encryption_offset]),
hdr.m_length - encryption_offset,
&key);

cli_dbgmsg("Encrypted with VelvetSweatshop: %d\n", bEncrypted);

#if HAVE_JSON
if (ctx->wrkproperty == ctx->properties) {
cli_jsonint(ctx->wrkproperty, "EncryptedWithVelvetSweatshop", bEncrypted);
Expand Down

0 comments on commit 6e11fc3

Please sign in to comment.