Commit aad00a888c for openssl.org

commit aad00a888ccb47ec49afeb7545f2dddf9397fbb5
Author: Hansaem Wi <hansaemw@gmail.com>
Date:   Thu Oct 29 10:10:34 2026 +0900

    providers: clear the PBKDF1 derived key in kdf_pbkdf1_derive_skey()

    kdf_pbkdf1_derive_skey() derives the key into a temporary buffer,
    imports it into the skeymgmt and then frees the buffer
    with OPENSSL_free(), so EVP_KDF_derive_SKEY() with PBKDF1 leaves
    the derived key in freed memory.  The generic fallback
    in EVP_KDF_derive_SKEY() and the generic skeymgmt clear their copies.

    Clear the buffer on both paths, and add test_kdf_pbkdf1_skey,
    that checks that no buffer freed while deriving still holds the key.

    Fixes: 55b2bf1abc38 "Implement EVP_KDF_CTX_set_SKEY"
    Assisted-by: Claude Code:claude-opus-5-5
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
    Merge-date: Sun Nov  1 16:08:07 2026
    Merged-from: https://github.com/openssl/openssl/pull/33444

diff --git a/providers/implementations/kdfs/pbkdf1.c b/providers/implementations/kdfs/pbkdf1.c
index 3eefbc8215..c603a94c92 100644
--- a/providers/implementations/kdfs/pbkdf1.c
+++ b/providers/implementations/kdfs/pbkdf1.c
@@ -307,7 +307,7 @@ static void *kdf_pbkdf1_derive_skey(void *vctx, const char *key_type ossl_unused
         return NULL;

     if (kdf_pbkdf1_derive(vctx, key, keylen, params) == 0) {
-        OPENSSL_free(key);
+        OPENSSL_clear_free(key, keylen);
         return NULL;
     }

@@ -315,7 +315,7 @@ static void *kdf_pbkdf1_derive_skey(void *vctx, const char *key_type ossl_unused
         (void *)key, keylen);

     ret = import(provctx, OSSL_SKEYMGMT_SELECT_SECRET_KEY, import_params);
-    OPENSSL_free(key);
+    OPENSSL_clear_free(key, keylen);

     return ret;
 }
diff --git a/test/evp_kdf_test.c b/test/evp_kdf_test.c
index 01e2726b71..ec77564699 100644
--- a/test/evp_kdf_test.c
+++ b/test/evp_kdf_test.c
@@ -787,6 +787,77 @@ err:
     return ret;
 }

+/*
+ * While watching, remember the blocks of |watch_len| bytes that are
+ * allocated, and note if one of them still holds |watch_key| when freed.
+ */
+static CRYPTO_malloc_fn saved_malloc;
+static CRYPTO_realloc_fn saved_realloc;
+static CRYPTO_free_fn saved_free;
+static void *watched[16];
+static size_t nwatched;
+static const unsigned char *watch_key;
+static size_t watch_len;
+static int key_left;
+
+static void watch_forget(void *ptr, int check)
+{
+    size_t i;
+
+    for (i = 0; i < nwatched; i++) {
+        if (watched[i] != ptr)
+            continue;
+        if (check && memcmp(ptr, watch_key, watch_len) == 0)
+            key_left = 1;
+        watched[i] = watched[--nwatched];
+        return;
+    }
+}
+
+static void *watch_malloc(size_t num, const char *file, int line)
+{
+    void *ret = saved_malloc(num, file, line);
+
+    if (ret != NULL && num == watch_len && nwatched < OSSL_NELEM(watched))
+        watched[nwatched++] = ret;
+    return ret;
+}
+
+static void *watch_realloc(void *ptr, size_t num, const char *file, int line)
+{
+    void *ret = saved_realloc(ptr, num, file, line);
+
+    if (ptr != NULL && (ret != NULL || num == 0))
+        watch_forget(ptr, 0);
+    if (ret != NULL && num == watch_len && nwatched < OSSL_NELEM(watched))
+        watched[nwatched++] = ret;
+    return ret;
+}
+
+static void watch_free(void *ptr, const char *file, int line)
+{
+    if (ptr != NULL)
+        watch_forget(ptr, 1);
+    saved_free(ptr, file, line);
+}
+
+static int watch_start(const unsigned char *key, size_t len)
+{
+    CRYPTO_get_mem_functions(&saved_malloc, &saved_realloc, &saved_free);
+    if (!CRYPTO_set_mem_functions(watch_malloc, watch_realloc, watch_free))
+        return 0;
+    nwatched = 0;
+    watch_key = key;
+    watch_len = len;
+    key_left = 0;
+    return 1;
+}
+
+static void watch_stop(void)
+{
+    CRYPTO_set_mem_functions(saved_malloc, saved_realloc, saved_free);
+}
+
 static int test_kdf_pbkdf1_skey(void)
 {
     int ret = 0;
@@ -806,6 +877,7 @@ static int test_kdf_pbkdf1_skey(void)
     };
     const unsigned char *export = NULL;
     size_t export_size = 0;
+    int watching;

     if (!TEST_ptr(libctx = OSSL_LIB_CTX_new()))
         goto err;
@@ -829,9 +901,19 @@ static int test_kdf_pbkdf1_skey(void)
                          salt, strlen((char *)salt), NULL))
         || !TEST_ptr(kctx = get_kdfbyname_libctx(libctx, OSSL_KDF_NAME_PBKDF1))
         || !TEST_true(EVP_KDF_CTX_set_params(kctx, params))
-        || !TEST_true(EVP_KDF_CTX_set_SKEY(kctx, skey_in, OSSL_KDF_PARAM_SALT))
-        || !TEST_ptr(skey_out = EVP_KDF_derive_SKEY(kctx, NULL, OSSL_SKEY_TYPE_GENERIC,
-                         NULL, sizeof(out), NULL))
+        || !TEST_true(EVP_KDF_CTX_set_SKEY(kctx, skey_in, OSSL_KDF_PARAM_SALT)))
+        goto err;
+
+    /* The derived key must not be left in a buffer freed while deriving */
+    if (!(watching = watch_start(expected, sizeof(expected))))
+        TEST_info("cannot replace the allocator, not checking the key is cleared");
+    skey_out = EVP_KDF_derive_SKEY(kctx, NULL, OSSL_SKEY_TYPE_GENERIC,
+        NULL, sizeof(out), NULL);
+    if (watching)
+        watch_stop();
+
+    if (!TEST_ptr(skey_out)
+        || (watching && !TEST_false(key_left))
         || !TEST_int_eq(EVP_SKEY_get0_raw_key(skey_out, &export, &export_size), 1)
         || !TEST_mem_eq(export, export_size, expected, sizeof(expected)))
         goto err;