From da43645515a0b1c2e33012a0a3ccfe1e2819c7c6 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:37:53 +0100 Subject: [PATCH 1/9] Fix GH-21244: use after free in zend_test_class_free_obj Not really a bug, as zend_test shouldn't be used outside of debugging, but let's silence fuzzers. The problem is that the arg info should be cloned as well when the class is cloned; or we fix it the simple way in this patch and make it uncloneable. --- ext/zend_test/test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index d99c40bc72be5..0faf65f36437f 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1527,6 +1527,7 @@ PHP_MINIT_FUNCTION(zend_test) memcpy(&zend_test_class_handlers, &std_object_handlers, sizeof(zend_object_handlers)); zend_test_class_handlers.get_method = zend_test_class_method_get; + zend_test_class_handlers.clone_obj = NULL; zend_test_class_handlers.free_obj = zend_test_class_free_obj; zend_test_class_handlers.offset = XtOffsetOf(zend_test_object, std); From 4b9e80eae9485e2eacb894566ab18dae6ce70ec5 Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 15:02:06 +0100 Subject: [PATCH 2/9] Fix memory leak in php_openssl_load_all_certs_from_file() when push fails --- ext/openssl/openssl.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 12383ac8c2c80..62ffa7f920002 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -2291,21 +2291,13 @@ static STACK_OF(X509) *php_openssl_load_all_certs_from_file( X509_INFO *xi; char cert_path[MAXPATHLEN]; - if(!(stack = sk_X509_new_null())) { - php_openssl_store_errors(); - php_error_docref(NULL, E_ERROR, "Memory allocation failure"); - goto end; - } - if (!php_openssl_check_path(cert_file, cert_file_len, cert_path, arg_num)) { - sk_X509_free(stack); goto end; } if (!(in = BIO_new_file(cert_path, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)))) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Error opening the file, %s", cert_path); - sk_X509_free(stack); goto end; } @@ -2313,7 +2305,11 @@ static STACK_OF(X509) *php_openssl_load_all_certs_from_file( if (!(sk = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL))) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Error reading the file, %s", cert_path); - sk_X509_free(stack); + goto end; + } + + if(!(stack = sk_X509_new_reserve(NULL, sk_X509_INFO_num(sk)))) { + php_openssl_store_errors(); goto end; } From 01d598aea34825f67089801ea61234de5d499aaf Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 15:14:13 +0100 Subject: [PATCH 3/9] Fix memory leaks in php_array_to_X509_sk() when push fails --- ext/openssl/openssl.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 62ffa7f920002..39bfe912fc427 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -2581,7 +2581,10 @@ static STACK_OF(X509) *php_array_to_X509_sk(zval * zcerts, uint32_t arg_num, con } } - sk_X509_push(sk, cert); + if (sk_X509_push(sk, cert) <= 0) { + X509_free(cert); + goto push_fail_exit; + } } ZEND_HASH_FOREACH_END(); } else { /* a single certificate */ @@ -2599,11 +2602,20 @@ static STACK_OF(X509) *php_array_to_X509_sk(zval * zcerts, uint32_t arg_num, con goto clean_exit; } } - sk_X509_push(sk, cert); + if (sk_X509_push(sk, cert) <= 0) { + X509_free(cert); + goto push_fail_exit; + } } clean_exit: return sk; + +push_fail_exit: + php_openssl_store_errors(); + php_sk_X509_free(sk); + sk = NULL; + goto clean_exit; } /* }}} */ From ef54becb3e11a6fb05cdcd79c2a712f87f90d4dc Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 15:14:25 +0100 Subject: [PATCH 4/9] Fix missing error propagation when php_array_to_X509_sk() fails Execution shouldn't continue if this fails because it can give the wrong results. --- ext/openssl/openssl.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 39bfe912fc427..65e4b7b56d376 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -2684,6 +2684,9 @@ PHP_FUNCTION(openssl_pkcs12_export_to_file) if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { ca = php_array_to_X509_sk(item, 5, "extracerts"); + if (!ca) { + goto cleanup; + } } /* end parse extra config */ @@ -2777,6 +2780,9 @@ PHP_FUNCTION(openssl_pkcs12_export) if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { ca = php_array_to_X509_sk(item, 5, "extracerts"); + if (!ca) { + goto cleanup; + } } /* end parse extra config */ From 556ec779512b3851970fedf0a6769bddf719305b Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 15:20:11 +0100 Subject: [PATCH 5/9] Fix memory leaks in openssl_pkcs7_encrypt() when push fails --- ext/openssl/openssl.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 65e4b7b56d376..c99ce2931fd9b 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5849,7 +5849,10 @@ PHP_FUNCTION(openssl_pkcs7_encrypt) goto clean_exit; } } - sk_X509_push(recipcerts, cert); + if (sk_X509_push(recipcerts, cert) <= 0) { + X509_free(cert); + goto clean_exit; + } } ZEND_HASH_FOREACH_END(); } else { /* a single certificate */ @@ -5870,7 +5873,10 @@ PHP_FUNCTION(openssl_pkcs7_encrypt) goto clean_exit; } } - sk_X509_push(recipcerts, cert); + if (sk_X509_push(recipcerts, cert) <= 0) { + X509_free(cert); + goto clean_exit; + } } /* sanity check the cipher */ From f6887f04f62301fa91b7da945491bd4c859e6802 Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 20 Jan 2026 15:21:13 +0100 Subject: [PATCH 6/9] Fix memory leaks in openssl_cms_encrypt() when push fails --- ext/openssl/openssl.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index c99ce2931fd9b..2f2aae1e7335b 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -6464,7 +6464,10 @@ PHP_FUNCTION(openssl_cms_encrypt) goto clean_exit; } } - sk_X509_push(recipcerts, cert); + if (sk_X509_push(recipcerts, cert) <= 0) { + php_openssl_store_errors(); + goto clean_exit; + } } ZEND_HASH_FOREACH_END(); } else { /* a single certificate */ @@ -6484,7 +6487,10 @@ PHP_FUNCTION(openssl_cms_encrypt) goto clean_exit; } } - sk_X509_push(recipcerts, cert); + if (sk_X509_push(recipcerts, cert) <= 0) { + php_openssl_store_errors(); + goto clean_exit; + } } /* sanity check the cipher */ From b911748fefcb67598c4a0ccef185499bae821020 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:40:24 +0100 Subject: [PATCH 7/9] Update NEWS for OpenSSL changes --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 3ddd141ab9baf..3f9e5c1354d7a 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,9 @@ PHP NEWS . Fixed bug GH-21227 (Borked SCCP of array containing partial object). (ilutov) +- OpenSSL: + . Fix a bunch of leaks and error propagation. (ndossche) + - PDO_PGSQL: . Fixed bug GH-21055 (connection attribute status typo for GSS negotiation). (lsaos) From e2059a4697a63ba82bd0265a9752158b066fb34a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 21 Dec 2025 01:48:46 +0100 Subject: [PATCH 8/9] curl: Don't truncate length Truncating to an int seems dangerous, esp. in combination with a MIN macro. I don't see a reason to truncate the length from size_t to int, and especially no reason to change the signedness. Closes GH-20747. --- NEWS | 1 + ext/curl/interface.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 3f9e5c1354d7a..3c7e82dece6e2 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,7 @@ PHP NEWS - Curl: . Fixed bug GH-21023 (CURLOPT_XFERINFOFUNCTION crash with a null callback). (David Carlier) + . Don't truncate length. (ndossche) - Date: . Fixed bug GH-20936 (DatePeriod::__set_state() cannot handle null start). diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 12db566c089ad..7fc1c77e9a9a3 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -583,7 +583,7 @@ static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx) return fwrite(data, size, nmemb, write_handler->fp); case PHP_CURL_RETURN: if (length > 0) { - smart_str_appendl(&write_handler->buf, data, (int) length); + smart_str_appendl(&write_handler->buf, data, length); } break; case PHP_CURL_USER: { @@ -860,7 +860,7 @@ static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx) if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); if (Z_TYPE(retval) == IS_STRING) { - length = MIN((size * nmemb), Z_STRLEN(retval)); + length = MIN(size * nmemb, Z_STRLEN(retval)); memcpy(data, Z_STRVAL(retval), length); } else if (Z_TYPE(retval) == IS_LONG) { length = Z_LVAL_P(&retval); @@ -891,7 +891,7 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx /* Handle special case write when we're returning the entire transfer */ if (ch->handlers.write->method == PHP_CURL_RETURN && length > 0) { - smart_str_appendl(&ch->handlers.write->buf, data, (int) length); + smart_str_appendl(&ch->handlers.write->buf, data, length); } else { PHPWRITE(data, length); } From 5150641f9a86be564f449d6802d271b2c04276c9 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 17 Feb 2026 20:05:04 +0100 Subject: [PATCH 9/9] Fix enabling of opcache in CI Closes GH-21246 --- .github/actions/configure-x64/action.yml | 29 +++++++++-------- .github/actions/test-linux/action.yml | 4 +++ .github/actions/test-macos/action.yml | 4 +++ .github/workflows/nightly.yml | 41 ++++++++++-------------- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/.github/actions/configure-x64/action.yml b/.github/actions/configure-x64/action.yml index 0610571b13210..150ccba9af915 100644 --- a/.github/actions/configure-x64/action.yml +++ b/.github/actions/configure-x64/action.yml @@ -3,6 +3,9 @@ inputs: configurationParameters: default: '' required: false + skipSlow: + default: false + required: false asan: default: false required: false @@ -20,9 +23,9 @@ runs: --enable-fpm \ --with-pdo-mysql=mysqlnd \ --with-mysqli=mysqlnd \ - --with-pgsql \ - --with-pdo-pgsql \ - --with-pdo-sqlite \ + ${{ inputs.skipSlow == 'false' && '--with-pgsql' || '' }} \ + ${{ inputs.skipSlow == 'false' && '--with-pdo-pgsql' || '' }} \ + ${{ inputs.skipSlow == 'false' && '--with-pdo-sqlite' || '' }} \ --enable-intl \ --without-pear \ --enable-gd \ @@ -37,7 +40,7 @@ runs: --enable-soap \ --enable-xmlreader \ --with-xsl \ - --with-tidy \ + ${{ inputs.skipSlow == 'false' && '--with-tidy' || '' }} \ --enable-sysvsem \ --enable-sysvshm \ --enable-shmop \ @@ -54,14 +57,14 @@ runs: --enable-calendar \ --enable-ftp \ --with-pspell=/usr \ - --with-enchant=/usr \ + ${{ inputs.skipSlow == 'false' && '--with-enchant=/usr' || '' }} \ --with-kerberos \ --enable-sysvmsg \ --with-ffi \ --enable-zend-test \ - --enable-dl-test=shared \ - --with-ldap \ - --with-ldap-sasl \ + ${{ inputs.skipSlow == 'false' && '--enable-dl-test=shared' || '' }} \ + ${{ inputs.skipSlow == 'false' && '--with-ldap' || '' }} \ + ${{ inputs.skipSlow == 'false' && '--with-ldap-sasl' || '' }} \ --with-password-argon2 \ --with-mhash \ --with-sodium \ @@ -72,16 +75,16 @@ runs: --with-tcadb \ --with-lmdb \ --with-qdbm \ - --with-snmp \ - --with-unixODBC \ + ${{ inputs.skipSlow == 'false' && '--with-snmp' || '' }} \ + ${{ inputs.skipSlow == 'false' && '--with-unixODBC' || '' }} \ --with-imap \ --with-imap-ssl \ - --with-pdo-odbc=unixODBC,/usr \ + ${{ inputs.skipSlow == 'false' && '--with-pdo-odbc=unixODBC,/usr' || '' }} \ $([ -d "/opt/oracle/instantclient" ] && echo '--with-pdo-oci=shared,instantclient,/opt/oracle/instantclient') \ $([ -d "/opt/oracle/instantclient" ] && echo '--with-oci8=shared,instantclient,/opt/oracle/instantclient') \ --with-config-file-path=/etc \ --with-config-file-scan-dir=/etc/php.d \ - --with-pdo-firebird \ - --with-pdo-dblib \ + ${{ inputs.skipSlow == 'false' && '--with-pdo-firebird' || '' }} \ + ${{ inputs.skipSlow == 'false' && '--with-pdo-dblib' || '' }} \ --enable-werror \ ${{ inputs.configurationParameters }} diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml index 350127da61e11..d20c0dafa5107 100644 --- a/.github/actions/test-linux/action.yml +++ b/.github/actions/test-linux/action.yml @@ -3,6 +3,9 @@ inputs: runTestsParameters: default: '' required: false + enableOpcache: + default: 'false' + required: false jitType: default: 'disable' required: false @@ -37,6 +40,7 @@ runs: fi export SKIP_IO_CAPTURE_TESTS=1 sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ + ${{ inputs.enableOpcache == 'true' && '-d zend_extension=opcache.so -d opcache.enable_cli=1' }} \ -d opcache.jit=${{ inputs.jitType }} \ -d opcache.jit_buffer_size=16M \ ${{ inputs.idleCpu == 'true' && '-j$(($(/usr/bin/nproc) - 1))' || '-j$(/usr/bin/nproc)' }} \ diff --git a/.github/actions/test-macos/action.yml b/.github/actions/test-macos/action.yml index c4987bbd670a8..2258847023c97 100644 --- a/.github/actions/test-macos/action.yml +++ b/.github/actions/test-macos/action.yml @@ -3,6 +3,9 @@ inputs: runTestsParameters: default: '' required: false + enableOpcache: + default: 'false' + required: false jitType: default: 'disable' required: false @@ -15,6 +18,7 @@ runs: export SKIP_IO_CAPTURE_TESTS=1 export CI_NO_IPV6=1 sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ + ${{ inputs.enableOpcache == 'true' && '-d zend_extension=opcache.so -d opcache.enable_cli=1' }} \ -d opcache.jit=${{ inputs.jitType }} \ -d opcache.jit_buffer_size=16M \ -j$(($(sysctl -n hw.ncpu) - 1)) \ diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f0f93b5a40a1b..1d066ec7f9a01 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -90,10 +90,10 @@ jobs: - name: Test Tracing JIT uses: ./.github/actions/test-alpine with: + enableOpcache: true jitType: tracing runTestsParameters: >- --asan -x - -d opcache.enable_cli=1 - name: Extra tests uses: ./.github/actions/extra-tests LINUX_X64: @@ -185,33 +185,33 @@ jobs: - name: Test Tracing JIT uses: ./.github/actions/test-linux with: + enableOpcache: true jitType: tracing runTestsParameters: >- ${{ matrix.asan && '--asan' || '' }} ${{ (matrix.asan && !inputs.all_variations) && '-x' || '' }} ${{ matrix.repeat && '--repeat 2' || '' }} ${{ matrix.variation && '-d zend_test.observer.enabled=1 -d zend_test.observer.show_output=0' || '' }} - -d opcache.enable_cli=1 - name: Test OpCache if: ${{ inputs.all_variations }} uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- ${{ matrix.asan && '--asan' || '' }} ${{ matrix.repeat && '--repeat 2' || '' }} ${{ matrix.variation && '-d zend_test.observer.enabled=1 -d zend_test.observer.show_output=0' || '' }} - -d opcache.enable_cli=1 - name: Test Function JIT # ASAN frequently timeouts. Each test run takes ~90 minutes, we can # avoid running into the 6 hour timeout by skipping the function JIT. if: ${{ inputs.all_variations && !matrix.asan }} uses: ./.github/actions/test-linux with: + enableOpcache: true jitType: function runTestsParameters: >- ${{ matrix.repeat && '--repeat 2' || '' }} ${{ matrix.variation && '-d zend_test.observer.enabled=1 -d zend_test.observer.show_output=0' || '' }} - -d opcache.enable_cli=1 - name: Extra tests uses: ./.github/actions/extra-tests - name: Verify generated files are up to date @@ -282,22 +282,19 @@ jobs: - name: Test Tracing JIT uses: ./.github/actions/test-linux with: + enableOpcache: true jitType: tracing - runTestsParameters: >- - -d opcache.enable_cli=1 - name: Test OpCache if: ${{ inputs.all_variations }} uses: ./.github/actions/test-linux with: - runTestsParameters: >- - -d opcache.enable_cli=1 + enableOpcache: true - name: Test Function JIT if: ${{ inputs.all_variations }} uses: ./.github/actions/test-linux with: + enableOpcache: true jitType: function - runTestsParameters: >- - -d opcache.enable_cli=1 - name: Extra tests uses: ./.github/actions/extra-tests MACOS: @@ -340,22 +337,19 @@ jobs: if: ${{ matrix.arch == 'X64' || !matrix.zts }} uses: ./.github/actions/test-macos with: + enableOpcache: true jitType: tracing - runTestsParameters: >- - -d opcache.enable_cli=1 - name: Test OpCache if: ${{ inputs.all_variations || (matrix.arch == 'ARM64' && matrix.zts) }} uses: ./.github/actions/test-macos with: - runTestsParameters: >- - -d opcache.enable_cli=1 + enableOpcache: true - name: Test Function JIT if: ${{ inputs.all_variations && (matrix.arch == 'X64' || !matrix.zts) }} uses: ./.github/actions/test-macos with: + enableOpcache: true jitType: function - runTestsParameters: >- - -d opcache.enable_cli=1 - name: Extra tests uses: ./.github/actions/extra-tests - name: Verify generated files are up to date @@ -411,9 +405,8 @@ jobs: - name: Test OpCache uses: ./.github/actions/test-linux with: + enableOpcache: true jitType: tracing - runTestsParameters: >- - -d opcache.enable_cli=1 - uses: codecov/codecov-action@v5 if: ${{ !cancelled() }} with: @@ -655,34 +648,34 @@ jobs: - name: Test File Cache (prime shm) uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- - -d opcache.enable_cli=1 --file-cache-prime - name: Test File Cache (prime shm, use shm) uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- - -d opcache.enable_cli=1 --file-cache-use - name: Test File Cache (prime shm, use file) uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- - -d opcache.enable_cli=1 --file-cache-use -d opcache.file_cache_only=1 - name: Test File Cache Only (prime) uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- - -d opcache.enable_cli=1 --file-cache-prime -d opcache.file_cache_only=1 - name: Test File Cache Only (use) uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- - -d opcache.enable_cli=1 --file-cache-use -d opcache.file_cache_only=1 - name: Verify generated files are up to date @@ -769,9 +762,9 @@ jobs: - name: Test Opcache uses: ./.github/actions/test-linux with: + enableOpcache: true runTestsParameters: >- --msan - -d opcache.enable_cli=1 - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files LIBMYSQLCLIENT: