Commit 8194b426f9 for openssl.org
commit 8194b426f9a8a3e92723fb838157985001eed85f
Author: Weidong Wang <kenazcharisma@gmail.com>
Date: Sat Mar 28 03:33:47 2026 -0500
Add regression test for negative EVP_CIPHER_get_iv_length() in PKCS5_pbe2_set_scrypt
A malicious provider returning SIZE_MAX as IV length causes
evp_cipher_cache_constants() to store -1 via size_t->int truncation.
Without the ivlen > 0 guard, this leads to memcpy(iv[16], aiv, SIZE_MAX)
— a stack buffer overflow.
The test registers a fake provider with SIZE_MAX IV length, then calls
PKCS5_pbe2_set_scrypt() and asserts it returns NULL without crashing.
test for #30510
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Daniel Kubec <kubec@openssl.foundation>
MergeDate: Mon Jul 20 11:15:18 2026
(Merged from https://github.com/openssl/openssl/pull/30615)
diff --git a/test/pbetest.c b/test/pbetest.c
index 42ae4a4da9..66c67e603d 100644
--- a/test/pbetest.c
+++ b/test/pbetest.c
@@ -8,6 +8,8 @@
*/
#include <string.h>
+#include <stdint.h>
+#include <limits.h>
#include "testutil.h"
@@ -17,6 +19,7 @@
#include <openssl/md5.h>
#include <openssl/configuration.h>
#include <openssl/provider.h>
+#include <openssl/core_names.h>
#if !defined OPENSSL_NO_RC4 && !defined OPENSSL_NO_MD5 \
|| !defined OPENSSL_NO_DES && !defined OPENSSL_NO_SHA1
@@ -125,6 +128,179 @@ static int test_pkcs5_pbe_des_sha1(void)
}
#endif
+/*
+ * Regression test for negative EVP_CIPHER_get_iv_length() return in
+ * PKCS5_pbe2_set_scrypt().
+ *
+ * A malicious/buggy provider advertises SIZE_MAX as IV length.
+ * evp_cipher_cache_constants() casts (size_t)SIZE_MAX to int => -1.
+ * Without the ivlen > 0 guard, this -1 is implicitly converted to SIZE_MAX
+ * in the memcpy call, causing a stack buffer overflow.
+ *
+ * This test verifies that PKCS5_pbe2_set_scrypt() handles negative IV
+ * lengths gracefully (returns NULL, no crash).
+ */
+#ifndef OPENSSL_NO_SCRYPT
+
+static void *bad_iv_cipher_newctx(void *provctx)
+{
+ static int dummy;
+ return &dummy;
+}
+
+static void bad_iv_cipher_freectx(void *vctx)
+{
+}
+
+static int bad_iv_cipher_cipher(void *vctx,
+ unsigned char *out, size_t *outl,
+ size_t outsz,
+ const unsigned char *in, size_t inl)
+{
+ if (outl != NULL)
+ *outl = 0;
+ return 1;
+}
+
+/*
+ * Advertise SIZE_MAX as IV length. After evp_cipher_cache_constants()
+ * stores (int)SIZE_MAX, EVP_CIPHER_get_iv_length() returns -1.
+ */
+static int bad_iv_cipher_get_params(OSSL_PARAM params[])
+{
+ OSSL_PARAM *p;
+
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_BLOCK_SIZE);
+ if (p != NULL && !OSSL_PARAM_set_size_t(p, 16))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN);
+ if (p != NULL && !OSSL_PARAM_set_size_t(p, 32))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_MODE);
+ if (p != NULL && !OSSL_PARAM_set_uint(p, EVP_CIPH_CBC_MODE))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
+ if (p != NULL && !OSSL_PARAM_set_size_t(p, SIZE_MAX))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_AEAD);
+ if (p != NULL && !OSSL_PARAM_set_int(p, 0))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_CUSTOM_IV);
+ if (p != NULL && !OSSL_PARAM_set_int(p, 0))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_CTS);
+ if (p != NULL && !OSSL_PARAM_set_int(p, 0))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_TLS1_MULTIBLOCK);
+ if (p != NULL && !OSSL_PARAM_set_int(p, 0))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_HAS_RAND_KEY);
+ if (p != NULL && !OSSL_PARAM_set_int(p, 0))
+ return 0;
+ p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_ENCRYPT_THEN_MAC);
+ if (p != NULL && !OSSL_PARAM_set_int(p, 0))
+ return 0;
+ return 1;
+}
+
+static const OSSL_DISPATCH bad_iv_cipher_fns[] = {
+ { OSSL_FUNC_CIPHER_NEWCTX, (void (*)(void))bad_iv_cipher_newctx },
+ { OSSL_FUNC_CIPHER_FREECTX, (void (*)(void))bad_iv_cipher_freectx },
+ { OSSL_FUNC_CIPHER_CIPHER, (void (*)(void))bad_iv_cipher_cipher },
+ { OSSL_FUNC_CIPHER_GET_PARAMS, (void (*)(void))bad_iv_cipher_get_params },
+ OSSL_DISPATCH_END
+};
+
+static const OSSL_ALGORITHM bad_iv_cipher_algs[] = {
+ { "AES-256-CBC:AES256", "provider=bad-iv-prov", bad_iv_cipher_fns,
+ "Bad IV length cipher for regression testing" },
+ { NULL, NULL, NULL, NULL }
+};
+
+static const OSSL_ALGORITHM *bad_iv_query(void *provctx,
+ int operation_id,
+ int *no_cache)
+{
+ *no_cache = 0;
+ if (operation_id == OSSL_OP_CIPHER)
+ return bad_iv_cipher_algs;
+ return NULL;
+}
+
+static void bad_iv_teardown(void *provctx) { }
+
+static const OSSL_DISPATCH bad_iv_provider_fns[] = {
+ { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))bad_iv_teardown },
+ { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))bad_iv_query },
+ OSSL_DISPATCH_END
+};
+
+static int bad_iv_provider_init(const OSSL_CORE_HANDLE *handle,
+ const OSSL_DISPATCH *in,
+ const OSSL_DISPATCH **out,
+ void **provctx)
+{
+ static int ctx;
+
+ *provctx = &ctx;
+ *out = bad_iv_provider_fns;
+ return 1;
+}
+
+/*
+ * Test that PKCS5_pbe2_set_scrypt() does not crash when
+ * EVP_CIPHER_get_iv_length() returns a negative value.
+ */
+static int test_pkcs5_scrypt_bad_iv_length(void)
+{
+ int ret = 0;
+ OSSL_LIB_CTX *libctx = NULL;
+ OSSL_PROVIDER *bad_prov = NULL;
+ EVP_CIPHER *cipher = NULL;
+ X509_ALGOR *alg = NULL;
+ unsigned char salt[16] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10
+ };
+ unsigned char iv[16] = { 0xAA };
+
+ if (!TEST_ptr(libctx = OSSL_LIB_CTX_new()))
+ goto err;
+
+ if (!TEST_true(OSSL_PROVIDER_add_builtin(libctx, "bad-iv-prov",
+ bad_iv_provider_init)))
+ goto err;
+
+ if (!TEST_ptr(bad_prov = OSSL_PROVIDER_load(libctx, "bad-iv-prov")))
+ goto err;
+
+ if (!TEST_ptr(cipher = EVP_CIPHER_fetch(libctx, "AES-256-CBC",
+ "provider=bad-iv-prov")))
+ goto err;
+
+ if (!TEST_int_lt(EVP_CIPHER_get_iv_length(cipher), 0))
+ goto err;
+
+ /*
+ * Before the fix, this would trigger memcpy(iv[16], aiv, SIZE_MAX)
+ * — a stack buffer overflow. After the fix, the function must
+ * return NULL.
+ */
+ alg = PKCS5_pbe2_set_scrypt(cipher, salt, (int)sizeof(salt),
+ iv, 1024, 8, 1);
+ if (!TEST_ptr_null(alg))
+ goto err;
+
+ ret = 1;
+err:
+ X509_ALGOR_free(alg);
+ EVP_CIPHER_free(cipher);
+ OSSL_PROVIDER_unload(bad_prov);
+ OSSL_LIB_CTX_free(libctx);
+ return ret;
+}
+#endif /* OPENSSL_NO_SCRYPT */
+
#ifdef OPENSSL_NO_AUTOLOAD_CONFIG
/*
* For configurations where we are not autoloading configuration, we need
@@ -152,6 +328,9 @@ int setup_tests(void)
#if !defined OPENSSL_NO_DES && !defined OPENSSL_NO_SHA1
ADD_TEST(test_pkcs5_pbe_des_sha1);
#endif
+#ifndef OPENSSL_NO_SCRYPT
+ ADD_TEST(test_pkcs5_scrypt_bad_iv_length);
+#endif
return 1;
}