| 1 | Patch-Source: https://github.com/php/php-src/commit/cbc0489126a7682796aad1e5fb4e51de74af162c
|
| 2 | From cbc0489126a7682796aad1e5fb4e51de74af162c Mon Sep 17 00:00:00 2001
|
| 3 | From: David Carlier <[email protected]>
|
| 4 | Date: Fri, 29 May 2026 21:44:14 +0100
|
| 5 | Subject: [PATCH] ext/openssl: openssl_encrypt() zend mm heap overflow on
|
| 6 | AES-WRAP-PAD mode.
|
| 7 |
|
| 8 | Fix #22186
|
| 9 |
|
| 10 | close GH-22187
|
| 11 | ---
|
| 12 | NEWS | 3 +++
|
| 13 | ext/openssl/openssl.c | 17 +++++++++++++++--
|
| 14 | ext/openssl/tests/gh22186.phpt | 32 ++++++++++++++++++++++++++++++++
|
| 15 | 3 files changed, 50 insertions(+), 2 deletions(-)
|
| 16 | create mode 100644 ext/openssl/tests/gh22186.phpt
|
| 17 |
|
| 18 | diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
|
| 19 | index 1d467f5b50af..c3e2ba2438e4 100644
|
| 20 | |
| 21 | |
| 22 | @@ -7472,6 +7472,7 @@ static int php_openssl_cipher_update(const EVP_CIPHER *cipher_type,
|
| 23 | const char *aad, size_t aad_len, int enc) /* {{{ */
|
| 24 | {
|
| 25 | int i = 0;
|
| 26 | + size_t outlen = data_len + EVP_CIPHER_block_size(cipher_type);
|
| 27 |
|
| 28 | if (mode->is_single_run_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, NULL, (int)data_len)) {
|
| 29 | php_openssl_store_errors();
|
| 30 | @@ -7485,7 +7486,19 @@ static int php_openssl_cipher_update(const EVP_CIPHER *cipher_type,
|
| 31 | return FAILURE;
|
| 32 | }
|
| 33 |
|
| 34 | - *poutbuf = zend_string_alloc((int)data_len + EVP_CIPHER_block_size(cipher_type), 0);
|
| 35 | +#ifdef EVP_CIPH_WRAP_MODE
|
| 36 | + if ((EVP_CIPHER_mode(cipher_type)) == EVP_CIPH_WRAP_MODE) {
|
| 37 | + /*
|
| 38 | + * RFC 5649 wrap-with-padding rounds the input up to the block size
|
| 39 | + * and prepends an integrity block, we reserve one extra block.
|
| 40 | + * See EVP_EncryptUpdate(3): wrap mode may write up to
|
| 41 | + * inl + cipher_block_size bytes.
|
| 42 | + */
|
| 43 | + outlen += EVP_CIPHER_block_size(cipher_type);
|
| 44 | + }
|
| 45 | +#endif
|
| 46 | +
|
| 47 | + *poutbuf = zend_string_alloc(outlen, false);
|
| 48 |
|
| 49 | if (!EVP_CipherUpdate(cipher_ctx, (unsigned char*)ZSTR_VAL(*poutbuf),
|
| 50 | &i, (const unsigned char *)data, (int)data_len)) {
|
| 51 | @@ -7497,7 +7510,7 @@ static int php_openssl_cipher_update(const EVP_CIPHER *cipher_type,
|
| 52 | }
|
| 53 | */
|
| 54 | php_openssl_store_errors();
|
| 55 | - zend_string_release_ex(*poutbuf, 0);
|
| 56 | + zend_string_release_ex(*poutbuf, false);
|
| 57 | return FAILURE;
|
| 58 | }
|
| 59 |
|
| 60 | diff --git a/ext/openssl/tests/gh22186.phpt b/ext/openssl/tests/gh22186.phpt
|
| 61 | new file mode 100644
|
| 62 | index 000000000000..8f28e6c45b58
|
| 63 | |
| 64 | |
| 65 | @@ -0,0 +1,32 @@
|
| 66 | +--TEST--
|
| 67 | +GH-22186 (Heap buffer overflow in openssl_encrypt with AES-WRAP-PAD)
|
| 68 | +--EXTENSIONS--
|
| 69 | +openssl
|
| 70 | +--SKIPIF--
|
| 71 | +<?php
|
| 72 | +/* openssl_get_cipher_methods() enumerates provider ciphers, but openssl_encrypt()
|
| 73 | + * resolves names via the legacy EVP_get_cipherbyname(), so on some builds the
|
| 74 | + * cipher is listed yet not usable. Probe the actual call path instead. */
|
| 75 | +if (!@openssl_encrypt("test", "aes-128-wrap-pad", str_repeat("k", 16),
|
| 76 | + OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, str_repeat("\0", 4))) {
|
| 77 | + die('skip aes-128-wrap-pad not usable on this OpenSSL build');
|
| 78 | +}
|
| 79 | +?>
|
| 80 | +--FILE--
|
| 81 | +<?php
|
| 82 | +$pass = str_repeat("k", 16);
|
| 83 | +$iv = str_repeat("\0", 4);
|
| 84 | +
|
| 85 | +for ($i = 1; $i < 258; $i++) {
|
| 86 | + $data = str_repeat("a", $i);
|
| 87 | + $enc = openssl_encrypt($data, 'aes-128-wrap-pad', $pass, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, $iv);
|
| 88 | + $dec = openssl_decrypt($enc, 'aes-128-wrap-pad', $pass, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, $iv);
|
| 89 | + if ($dec !== $data) {
|
| 90 | + die("mismatch at $i\n");
|
| 91 | + }
|
| 92 | +}
|
| 93 | +
|
| 94 | +echo "done\n";
|
| 95 | +?>
|
| 96 | +--EXPECT--
|
| 97 | +done
|