Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mbedtls: Avoid using a large buffer on the stack. #7586

Closed
wants to merge 1 commit into from

Conversation

@MAntoniak
Copy link
Contributor

@MAntoniak MAntoniak commented Aug 17, 2021

Use dynamic memory allocation for the buffer used in checking "pinned public key". The PUB_DER_MAX_BYTES parameter with default settings is set to a value greater than 2kB.

@@ -686,6 +686,13 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
if(!p)
return CURLE_OUT_OF_MEMORY;

pubkey = malloc(PUB_DER_MAX_BYTES);
Copy link
Member

@danielgustafsson danielgustafsson Aug 17, 2021

Is PUB_DER_MAX_BYTES always needed or can it be smaller and grown when needed?

Copy link
Contributor Author

@MAntoniak MAntoniak Aug 18, 2021

So I analyzed this question more. It seems possible, but it will not be easy.
From the mbedtls code you can deduce what size buffer is needed.

For RSA:

/ *
 * RSA public keys:
 * SubjectPublicKeyInfo :: = SEQUENCE {1 + 3
 * algorithm AlgorithmIdentifier, 1 + 1 (sequence)
 * + 1 + 1 + 9 (rsa oid)
 * + 1 + 1 (params null)
 * subjectPublicKey BIT STRING} 1 + 3 + (1 + below)
 * RSAPublicKey :: = SEQUENCE {1 + 3
 * modulus INTEGER, - n 1 + 3 + MPI_MAX + 1
 * publicExponent INTEGER - e 1 + 3 + MPI_MAX + 1
 *}
 * /

For EC:

/ *
 * EC public keys:
 * SubjectPublicKeyInfo :: = SEQUENCE {1 + 2
 * algorithm AlgorithmIdentifier, 1 + 1 (sequence)
 * + 1 + 1 + 7 (ec oid)
 * + 1 + 1 + 9 (namedCurve oid)
 * subjectPublicKey BIT STRING 1 + 2 + 1 [1]
 * + 1 (point format) [1]
 * + 2 * ECP_MAX (coords) [1]
 *}
 * /

The problem for RSA is determining the size of the modulus parameter and the publicExponent (instead of taking the value of MPI_MAX). For the EC, the problem is to determine the size of the Q parameter (instead of taking the value ECP_MAX).

If the public key is RSA, you can get the N (modulus) and E (exponent) parameters using the mbedtls_rsa_import function, read their size by the mbedtls_mpi_size function, sum the values ​​and add an additional 38 bytes.

Similarly for EC: the mbedtls_pk_ec function returns the mbedtls_ecp_keypair structure that contains the Q parameter. Then read the X and Y coordinates, sum and add an additional 30 bytes.

Disadvantages:

  • adding new asymmetric keys will require changes in curl as well.
  • RSA and EC key parameters have been marked as private in the mbedtls 3.0.0 version - it is uncertain whether they will not be removed in the future for the appropriate functions in the API.

The best solution would of course be to write such a function on the mbedtls side. However, I have not found any premises for the mbedtls team to implement such a function.

Copy link
Member

@danielgustafsson danielgustafsson Aug 18, 2021

Thanks for the exhaustive answer! With this in mind I agree that just allocating the known upper limit is clearly the best option.

@bagder
Copy link
Member

@bagder bagder commented Aug 18, 2021

How about trying to do all the frees at a single spot in case of errors? Like this:

diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c
index 576b20a59..822fc18ef 100644
--- a/lib/vtls/mbedtls.c
+++ b/lib/vtls/mbedtls.c
@@ -666,12 +666,12 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
   }
 
   if(pinnedpubkey) {
     int size;
     CURLcode result;
-    mbedtls_x509_crt *p;
-    unsigned char *pubkey;
+    mbedtls_x509_crt *p = NULL;
+    unsigned char *pubkey = NULL;
 
 #if MBEDTLS_VERSION_NUMBER >= 0x03000000
     if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) ||
        !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) {
 #else
@@ -680,19 +680,17 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
       failf(data, "Failed due to missing peer certificate");
       return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
     }
 
     p = calloc(1, sizeof(*p));
-
     if(!p)
       return CURLE_OUT_OF_MEMORY;
 
     pubkey = malloc(PUB_DER_MAX_BYTES);
-
     if(!pubkey) {
-      free(p);
-      return CURLE_OUT_OF_MEMORY;
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
     }
 
     mbedtls_x509_crt_init(p);
 
     /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der
@@ -704,14 +702,12 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
                         peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len))) {
 #else
     if(mbedtls_x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) {
 #endif
       failf(data, "Failed copying peer certificate");
-      mbedtls_x509_crt_free(p);
-      free(p);
-      free(pubkey);
-      return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      goto error;
     }
 
 #if MBEDTLS_VERSION_NUMBER >= 0x03000000
     size = mbedtls_pk_write_pubkey_der(&p->MBEDTLS_PRIVATE(pk), pubkey,
                                        PUB_DER_MAX_BYTES);
@@ -719,20 +715,19 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *:
conn,
     size = mbedtls_pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES);
 #endif
 
     if(size <= 0) {
       failf(data, "Failed copying public key from peer certificate");
-      mbedtls_x509_crt_free(p);
-      free(p);
-      free(pubkey);
-      return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      goto error;
     }
 
     /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */
     result = Curl_pin_peer_pubkey(data,
                                   pinnedpubkey,
                                   &pubkey[PUB_DER_MAX_BYTES - size], size);
+      error:
     if(result) {
       mbedtls_x509_crt_free(p);
       free(p);
       free(pubkey);
       return result;

@MAntoniak
Copy link
Contributor Author

@MAntoniak MAntoniak commented Aug 18, 2021

How about trying to do all the frees at a single spot in case of errors? Like this:

diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c
index 576b20a59..822fc18ef 100644
--- a/lib/vtls/mbedtls.c
+++ b/lib/vtls/mbedtls.c
@@ -666,12 +666,12 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
   }
 
   if(pinnedpubkey) {
     int size;
     CURLcode result;
-    mbedtls_x509_crt *p;
-    unsigned char *pubkey;
+    mbedtls_x509_crt *p = NULL;
+    unsigned char *pubkey = NULL;
 
 #if MBEDTLS_VERSION_NUMBER >= 0x03000000
     if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) ||
        !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) {
 #else
@@ -680,19 +680,17 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
       failf(data, "Failed due to missing peer certificate");
       return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
     }
 
     p = calloc(1, sizeof(*p));
-
     if(!p)
       return CURLE_OUT_OF_MEMORY;
 
     pubkey = malloc(PUB_DER_MAX_BYTES);
-
     if(!pubkey) {
-      free(p);
-      return CURLE_OUT_OF_MEMORY;
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
     }
 
     mbedtls_x509_crt_init(p);
 
     /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der
@@ -704,14 +702,12 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn,
                         peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len))) {
 #else
     if(mbedtls_x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) {
 #endif
       failf(data, "Failed copying peer certificate");
-      mbedtls_x509_crt_free(p);
-      free(p);
-      free(pubkey);
-      return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      goto error;
     }
 
 #if MBEDTLS_VERSION_NUMBER >= 0x03000000
     size = mbedtls_pk_write_pubkey_der(&p->MBEDTLS_PRIVATE(pk), pubkey,
                                        PUB_DER_MAX_BYTES);
@@ -719,20 +715,19 @@ mbed_connect_step2(struct Curl_easy *data, struct connectdata *:
conn,
     size = mbedtls_pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES);
 #endif
 
     if(size <= 0) {
       failf(data, "Failed copying public key from peer certificate");
-      mbedtls_x509_crt_free(p);
-      free(p);
-      free(pubkey);
-      return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+      goto error;
     }
 
     /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */
     result = Curl_pin_peer_pubkey(data,
                                   pinnedpubkey,
                                   &pubkey[PUB_DER_MAX_BYTES - size], size);
+      error:
     if(result) {
       mbedtls_x509_crt_free(p);
       free(p);
       free(pubkey);
       return result;

Code that does not repeat looks much better. I will modify and update the PR.

Use dynamic memory allocation for the buffer used in checking "pinned public key". The PUB_DER_MAX_BYTES parameter with default settings is set to a value greater than 2kB. (co-authored by Daniel Stenberg)
bagder
bagder approved these changes Sep 6, 2021
@bagder
Copy link
Member

@bagder bagder commented Sep 6, 2021

Thanks!

@bagder bagder closed this in 37fb213 Sep 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants