From 9d66094651e1e719adce0fb5c4f0a0c118f2afda Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 15 Sep 2026 17:18:46 -0700 Subject: [PATCH] Compat layer: fix memory leak in X509_get0_pubkey --- src/internal.c | 6 ++++++ src/x509.c | 25 +++++++++++++++++++++++++ tests/api/test_ossl_x509.c | 34 ++++++++++++++++++++++++++++++++++ tests/api/test_ossl_x509.h | 4 +++- wolfssl/internal.h | 5 +++++ wolfssl/openssl/ssl.h | 2 +- wolfssl/ssl.h | 1 + 7 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 69b12575596..decebb5e70e 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5491,6 +5491,12 @@ static void FreeX509Contents(WOLFSSL_X509* x509) FreeX509Name(&x509->issuer); FreeX509Name(&x509->subject); +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + if (x509->pubKeyCache != NULL) { + wolfSSL_EVP_PKEY_free(x509->pubKeyCache); + x509->pubKeyCache = NULL; + } +#endif if (x509->pubKey.buffer) { XFREE(x509->pubKey.buffer, x509->heap, DYNAMIC_TYPE_PUBLIC_KEY); x509->pubKey.buffer = NULL; diff --git a/src/x509.c b/src/x509.c index 1365cc44074..a6ffa22edb5 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6554,6 +6554,31 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) } return key; } + + +/* Get the certificate's public key without transferring ownership. + * + * The get0 form returns a pointer into the certificate which the caller must + * not free, It is released with the rest of the certificate's contents. + * + * Note: the first call on a given certificate should not race another; the + * cache is built without a lock, as elsewhere in this layer. + * + * @param [in] x509 Certificate. + * @return Public key on success, NULL on error. + */ +WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509) +{ + WOLFSSL_ENTER("wolfSSL_X509_get0_pubkey"); + + if (x509 == NULL) + return NULL; + + if (x509->pubKeyCache == NULL) + x509->pubKeyCache = wolfSSL_X509_get_pubkey(x509); + + return x509->pubKeyCache; +} #endif /* OPENSSL_EXTRA_X509_SMALL */ /* End of smaller subset of X509 compatibility functions. Avoid increasing the diff --git a/tests/api/test_ossl_x509.c b/tests/api/test_ossl_x509.c index dabbe81a29c..9c7ddc5cb1f 100644 --- a/tests/api/test_ossl_x509.c +++ b/tests/api/test_ossl_x509.c @@ -2157,3 +2157,37 @@ int test_wolfSSL_X509_cmp(void) #endif return EXPECT_RESULT(); } + +/* X509_get0_pubkey returns a key the certificate owns, so the caller does + * not free it and repeated calls return the same pointer. Returning a + * freshly allocated key each time leaks one per call, because nothing is + * left to free it: the caller must not, and the certificate never knew + * about it. */ +int test_wolfSSL_X509_get0_pubkey(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) && \ + !defined(NO_CERTS) + X509* x509 = NULL; + EVP_PKEY* first = NULL; + EVP_PKEY* second = NULL; + int i; + + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(svrCertFile, + WOLFSSL_FILETYPE_PEM)); + + ExpectNotNull(first = X509_get0_pubkey(x509)); + ExpectNotNull(second = X509_get0_pubkey(x509)); + /* the certificate owns it, so the same object comes back each time */ + ExpectPtrEq(first, second); + + /* repeated use must not accumulate allocations */ + for (i = 0; i < 100; i++) { + ExpectPtrEq(X509_get0_pubkey(x509), first); + } + + /* freeing the certificate releases the key; the caller frees nothing */ + X509_free(x509); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ossl_x509.h b/tests/api/test_ossl_x509.h index 1d3771ca5e5..2ad7114beb7 100644 --- a/tests/api/test_ossl_x509.h +++ b/tests/api/test_ossl_x509.h @@ -58,6 +58,7 @@ int test_wolfSSL_X509_max_name_constraints(void); int test_wolfSSL_X509_check_ca(void); int test_X509_get_signature_nid(void); int test_wolfSSL_X509_cmp(void); +int test_wolfSSL_X509_get0_pubkey(void); #define TEST_OSSL_X509_DECLS \ TEST_DECL_GROUP("ossl_x509", test_x509_get_key_id), \ @@ -93,6 +94,7 @@ int test_wolfSSL_X509_cmp(void); TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_name_constraints), \ TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_check_ca), \ TEST_DECL_GROUP("ossl_x509", test_X509_get_signature_nid), \ - TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_cmp) + TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_cmp), \ + TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_get0_pubkey) #endif /* WOLFCRYPT_TEST_OSSL_X509_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 0e2b7d63c6a..fdeb18447ca 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5950,6 +5950,11 @@ struct WOLFSSL_X509 { byte certPolicySet; byte certPolicyCrit; #endif /* WOLFSSL_SEP */ +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + /* Public key owned by this certificate and returned by the get0 form, + * which the caller must not free. Built on first use. */ + WOLFSSL_EVP_PKEY* pubKeyCache; +#endif #if defined(WOLFSSL_QT) || defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) WOLFSSL_STACK* ext_sk; /* Store X509_EXTENSIONS from wolfSSL_X509_get_ext */ WOLFSSL_STACK* ext_sk_full; /* Store X509_EXTENSIONS from wolfSSL_X509_get0_extensions */ diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 4b5b482bf1e..c468e19b6b4 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -566,7 +566,7 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define X509_get_subject_name(x) wolfSSL_X509_get_subject_name((WOLFSSL_X509*)(x)) #define X509_REQ_get_subject_name wolfSSL_X509_get_subject_name #define X509_get_pubkey wolfSSL_X509_get_pubkey -#define X509_get0_pubkey wolfSSL_X509_get_pubkey +#define X509_get0_pubkey wolfSSL_X509_get0_pubkey #define X509_REQ_get_pubkey wolfSSL_X509_get_pubkey #define X509_get_notBefore wolfSSL_X509_get_notBefore #define X509_get0_notBefore wolfSSL_X509_get_notBefore diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ef041782084..c316a1f3126 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2507,6 +2507,7 @@ WOLFSSL_API WOLFSSL_ASN1_TIME* wolfSSL_X509_CRL_get_nextUpdate(WOLFSSL_X509_CRL* WOLFSSL_API int wolfSSL_X509_CRL_set_nextUpdate(WOLFSSL_X509_CRL* crl, const WOLFSSL_ASN1_TIME* time); WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_X509_CRL_verify(WOLFSSL_X509_CRL* crl, WOLFSSL_EVP_PKEY* pkey); WOLFSSL_API void wolfSSL_X509_OBJECT_free_contents(WOLFSSL_X509_OBJECT* obj); WOLFSSL_API WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY_bio(