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;
 }