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;