Potential, if rare, double-free in OpenSSL crypto/evp/digest.c EVP_DigestInit_ex

The sequence of operations in the attached proof of concept will trigger
a double free.

This happens because:

214     if (ctx->digest != type) {
215         if (ctx->digest && ctx->digest->ctx_size)
216             OPENSSL_free(ctx->md_data);
217         ctx->digest = type;
218         if (!(ctx->flags & EVP_MD_CTX_FLAG_NO_INIT) && type->ctx_size) {
219             ctx->update = type->update;
220             ctx->md_data = OPENSSL_malloc(type->ctx_size);
221             if (ctx->md_data == NULL) {
222                 EVPerr(EVP_F_EVP_DIGESTINIT_EX, ERR_R_MALLOC_FAILURE);
223                 return 0;
224             }
225         }
226     }

Under particular circumstances ctx->md_data is freed but not set again,
and a subsequent call will free ctx->md_data again.

I'm not sure if the PoC represents something that could happen in a real
application, or whether other, saner paths towards this double free (or
perhaps use after free) exist. Either way, I advice setting ctx->md_data
to NULL after freeing it.

PoC:

#include <openssl/evp.h>
#include <openssl/bio.h>
#include <stdio.h>
int main(void)
{
    EVP_MD_CTX *mctx = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    BIO *bmd = NULL;
    EVP_PKEY *sigkey = NULL;
    int errid = 0;
    int r;

    OpenSSL_add_all_digests();

    bmd = BIO_new(BIO_f_md());
    if ( bmd == 0 )
    {
        errid = 1;
        goto err;
    }
    if (!BIO_get_md_ctx(bmd, &mctx))
    {
        errid = 2;
        goto err;
    }
    if ( !EVP_DigestInit_ex(mctx, EVP_md5(), 0) )
    {
        errid = 3;
        goto err;
    }
    sigkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, 0, (unsigned char*)"aaaa", -1);
    if ( !sigkey )
    {
        errid = 4;
        goto err;
    }
    r = EVP_DigestSignInit(mctx, &pctx, 0, 0, sigkey);

    if (!r)
    {
        errid = 5;
        goto err;
    }
    if (!EVP_DigestInit_ex(mctx, EVP_md5(), 0))
    {
        errid = 6;
        goto err;
    }
    return 0;
err:
    printf("Error: %d\n", errid);
    return 1;

    return 0;
}

 

OpenSSL responded with:

“I do not believe this scenario would ever normally occur, i.e. only as a result of programmer error (you should not be mixing the EVP_DigestInit/EVP_DigestSignInit calls in that way). Therefore I will not be treating this as a security issue.
Nonetheless it seems prudent to set ctx->md_data to NULL after the free,
so I will apply a patch to that effect.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.