Commit b9a8b0e828 for openssl.org
commit b9a8b0e82805a951e8ecba64d8eb6bf7f82d708a
Author: Denis Mingulov <denis@mingulov.com>
Date: Wed Apr 1 23:46:02 2026 +0300
Fix heap buffer overflow in CRYPTO_128_unwrap_pad error paths
OPENSSL_cleanse on error paths used inlen (ciphertext length) instead
of padded_len (output buffer size = inlen - 8), writing 8 bytes past
the caller-provided output buffer.
Add regression tests using guard bytes to detect the overflow in any
build configuration, including when ASan cannot instrument the
assembly OPENSSL_cleanse implementation.
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Andrew Dinh <andrewd@openssl.org>
MergeDate: Wed Aug 19 03:20:07 2026
(Merged from https://github.com/openssl/openssl/pull/30663)
diff --git a/crypto/modes/wrap128.c b/crypto/modes/wrap128.c
index 6aa564a8b3..2f6d10f9cc 100644
--- a/crypto/modes/wrap128.c
+++ b/crypto/modes/wrap128.c
@@ -288,7 +288,7 @@ size_t CRYPTO_128_unwrap_pad(void *key, const unsigned char *icv,
padded_len = inlen - 8;
ret = crypto_128_unwrap_raw(key, aiv, out, in, inlen, block);
if (padded_len != ret) {
- OPENSSL_cleanse(out, inlen);
+ OPENSSL_cleanse(out, padded_len);
return 0;
}
}
@@ -300,7 +300,7 @@ size_t CRYPTO_128_unwrap_pad(void *key, const unsigned char *icv,
*/
if ((!icv && CRYPTO_memcmp(aiv, default_aiv, 4))
|| (icv && CRYPTO_memcmp(aiv, icv, 4))) {
- OPENSSL_cleanse(out, inlen);
+ OPENSSL_cleanse(out, padded_len);
return 0;
}
@@ -314,7 +314,7 @@ size_t CRYPTO_128_unwrap_pad(void *key, const unsigned char *icv,
| ((unsigned int)aiv[6] << 8)
| (unsigned int)aiv[7];
if (8 * (n - 1) >= ptext_len || ptext_len > 8 * n) {
- OPENSSL_cleanse(out, inlen);
+ OPENSSL_cleanse(out, padded_len);
return 0;
}
@@ -324,7 +324,7 @@ size_t CRYPTO_128_unwrap_pad(void *key, const unsigned char *icv,
*/
padding_len = padded_len - ptext_len;
if (CRYPTO_memcmp(out + ptext_len, zeros, padding_len) != 0) {
- OPENSSL_cleanse(out, inlen);
+ OPENSSL_cleanse(out, padded_len);
return 0;
}
diff --git a/test/aeswrap_test.c b/test/aeswrap_test.c
index 1ec763e8f5..f15cfe9d0b 100644
--- a/test/aeswrap_test.c
+++ b/test/aeswrap_test.c
@@ -7,6 +7,7 @@
* https://www.openssl.org/source/license.html
*/
+#include <string.h>
#include "testutil.h"
#include "internal/nelem.h"
@@ -83,11 +84,84 @@ err:
return ret;
}
+#define GUARD_BYTE 0x7f
+#define GUARD_LEN 8
+
+/*
+ * Wrap with one ICV, unwrap with a different ICV so the AIV check fails.
+ * The output buffer has guard bytes to detect buffer overwriting on error.
+ */
+static int aeswrap_unwrap_pad_overflow_test(int plaintext_len)
+{
+ int ret = 0;
+ EVP_CIPHER_CTX *ctx = NULL;
+ EVP_CIPHER *cipher = NULL;
+ static const unsigned char aeswrap_test_key[16] = { 0 };
+ unsigned char plaintext[16] = { 0 }, ciphertext[40], expected[24] = { 0 };
+ int ct_len = 0, tmplen = 0;
+ size_t out_len;
+ unsigned char *out = NULL;
+ static const unsigned char wrap_icv[4] = { 0xA6, 0x59, 0x59, 0xA7 };
+ static const unsigned char unwrap_icv[4] = { 0xA6, 0x59, 0x59, 0xA6 };
+
+ if (!TEST_ptr(ctx = EVP_CIPHER_CTX_new())
+ || !TEST_ptr(cipher = EVP_CIPHER_fetch(NULL, "AES-128-WRAP-PAD", NULL)))
+ goto err;
+
+ if (!TEST_int_eq(EVP_CipherInit_ex2(ctx, cipher, aeswrap_test_key,
+ wrap_icv, 1, NULL),
+ 1)
+ || !TEST_int_eq(EVP_CipherUpdate(ctx, ciphertext, &ct_len,
+ plaintext, plaintext_len),
+ 1)
+ || !TEST_int_gt(ct_len, GUARD_LEN))
+ goto err;
+
+ out_len = (size_t)(ct_len - 8);
+ if (!TEST_ptr(out = OPENSSL_malloc(out_len + GUARD_LEN)))
+ goto err;
+ memset(out, GUARD_BYTE, out_len + GUARD_LEN);
+
+ if (!TEST_int_eq(EVP_CipherInit_ex2(ctx, cipher, aeswrap_test_key,
+ unwrap_icv, 0, NULL),
+ 1)
+ || !TEST_int_eq(EVP_CipherUpdate(ctx, out, &tmplen,
+ ciphertext, ct_len),
+ 0))
+ goto err;
+
+ /* output area cleansed to zero, guard bytes untouched */
+ memset(expected + out_len, GUARD_BYTE, GUARD_LEN);
+ if (!TEST_mem_eq(out, out_len + GUARD_LEN, expected, out_len + GUARD_LEN))
+ goto err;
+
+ ret = 1;
+err:
+ OPENSSL_free(out);
+ EVP_CIPHER_free(cipher);
+ EVP_CIPHER_CTX_free(ctx);
+ return ret;
+}
+
+/* 1 byte plaintext -> 16-byte ciphertext */
+static int aeswrap_unwrap_pad_n1_overflow_test(void)
+{
+ return aeswrap_unwrap_pad_overflow_test(1);
+}
+
+/* 16 bytes plaintext -> 24-byte ciphertext */
+static int aeswrap_unwrap_pad_n2_overflow_test(void)
+{
+ return aeswrap_unwrap_pad_overflow_test(16);
+}
+
int setup_tests(void)
{
ADD_TEST(aeswrap_input_size_fail_test);
ADD_TEST(aeswrap_multi_update_fail_test);
ADD_ALL_TESTS(aeswrap_null_key_init_fail_test,
OSSL_NELEM(aeswrap_null_key_ciphers));
+ ADD_TEST(aeswrap_unwrap_pad_n1_overflow_test);
+ ADD_TEST(aeswrap_unwrap_pad_n2_overflow_test);
return 1;
}