Commit 7782b8876bc for php.net
commit 7782b8876bc0f61b0e8bcce262e22896e423b22e
Author: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Date: Sat Jan 24 23:52:14 2026 +0100
Fix NULL deref when enabling TLS fails and the peer name needs to be reset
The code tries to read the context on NULL when
`php_stream_xport_crypto_setup` fails because by then `stream` is reset
to NULL.
This is also UB, so can cause miscompiles.
```
==1217==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000090 (pc 0x55d829ed3acf bp 0x7fff045f5770 sp 0x7fff045f4df0 T0)
==1217==The signal is caused by a READ memory access.
==1217==Hint: address points to the zero page.
#0 0x55d829ed3acf in php_stream_url_wrap_http_ex /work/php-src/ext/standard/http_fopen_wrapper.c:580
#1 0x55d829ed857e in php_stream_url_wrap_http /work/php-src/ext/standard/http_fopen_wrapper.c:1204
#2 0x55d82a15073d in _php_stream_open_wrapper_ex /work/php-src/main/streams/streams.c:2270
#3 0x55d829e78fa6 in zif_file_get_contents /work/php-src/ext/standard/file.c:409
#4 0x55d829bbfe39 in zif_phar_file_get_contents /work/php-src/ext/phar/func_interceptors.c:226
#5 0x55d82a0b7ed2 in zend_test_execute_internal /work/php-src/ext/zend_test/observer.c:306
#6 0x55d82a3e024a in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER /work/php-src/Zend/zend_vm_execute.h:2154
#7 0x55d82a540995 in execute_ex /work/php-src/Zend/zend_vm_execute.h:116519
#8 0x55d82a5558b0 in zend_execute /work/php-src/Zend/zend_vm_execute.h:121962
#9 0x55d82a6ba0ab in zend_execute_script /work/php-src/Zend/zend.c:1980
#10 0x55d82a0ec8bb in php_execute_script_ex /work/php-src/main/main.c:2645
#11 0x55d82a0ecccb in php_execute_script /work/php-src/main/main.c:2685
#12 0x55d82a6bfc16 in do_cli /work/php-src/sapi/cli/php_cli.c:951
#13 0x55d82a6c21e3 in main /work/php-src/sapi/cli/php_cli.c:1362
#14 0x7f9e770491c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#15 0x7f9e7704928a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#16 0x55d829209b34 in _start (/work/php-src/build-dbg-asan/sapi/cli/php+0x609b34) (BuildId: aa149f943514fff0c491e1f199e30fed0e977f7c)
```
Closes GH-21468.
Closes GH-21031.
diff --git a/NEWS b/NEWS
index c9933ff6b26..1e72166bf93 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,10 @@ PHP NEWS
. Fixed bug GH-21499 (RecursiveArrayIterator getChildren UAF after parent
free). (Girgias)
+- Streams:
+ . Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL
+ and a proxy set). (ndossche)
+
09 Apr 2026, PHP 8.4.20
- Bz2:
diff --git a/ext/openssl/tests/gh21031.phpt b/ext/openssl/tests/gh21031.phpt
new file mode 100644
index 00000000000..a35fab92727
--- /dev/null
+++ b/ext/openssl/tests/gh21031.phpt
@@ -0,0 +1,82 @@
+--TEST--
+GH-21031 (Fix NULL deref when enabling TLS fails and the peer name needs to be reset)
+--EXTENSIONS--
+openssl
+--SKIPIF--
+<?php
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ die("skip not reliable on Windows due to proxy wait limitation");
+}
+?>
+--FILE--
+<?php
+
+$serverCode = <<<'CODE'
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $ctx = stream_context_create(['ssl' => [
+ 'SNI_server_certs' => [
+ "cs.php.net" => __DIR__ . "/sni_server_cs_expired.pem",
+ ]
+ ]]);
+
+ $server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, $serverFlags, $ctx);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server, 3);
+ fclose($conn);
+
+ phpt_wait();
+CODE;
+
+$proxyCode = <<<'CODE'
+ $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $server = stream_socket_server("tcp://127.0.0.1:0", $errornum, $errorstr, $flags);
+ phpt_notify_server_start($server);
+
+ $upstream = stream_socket_client("tcp://{{ ADDR }}", $errornum, $errorstr, 30, STREAM_CLIENT_CONNECT);
+ stream_set_blocking($upstream, false);
+
+ $conn = stream_socket_accept($server);
+ stream_set_blocking($conn, true);
+
+ // reading CONNECT request headers
+ while (($line = fgets($conn)) !== false) {
+ if (rtrim($line) === '') break; // empty line means end of headers
+ }
+
+ // successful CONNECT response
+ fwrite($conn, "HTTP/1.0 200 Connection established\r\n\r\n");
+
+ fclose($conn);
+ fclose($upstream);
+ phpt_wait();
+CODE;
+
+$clientCode = <<<'CODE'
+ $clientCtx = stream_context_create([
+ 'ssl' => [
+ 'cafile' => __DIR__ . '/sni_server_ca.pem',
+ 'verify_peer' => true,
+ 'verify_peer_name' => true,
+ ],
+ "http" => [
+ "proxy" => "tcp://{{ ADDR }}"
+ ],
+ ]);
+
+ var_dump(file_get_contents("https://cs.php.net/", false, $clientCtx));
+
+ phpt_notify('proxy');
+ phpt_notify('server');
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, [
+ 'server' => $serverCode,
+ 'proxy' => $proxyCode,
+]);
+?>
+--EXPECTF--
+Warning: file_get_contents(https://cs.php.net/): Failed to open stream: Cannot connect to HTTPS server through proxy in %s
+bool(false)
diff --git a/ext/openssl/tests/sni_server_cs_expired.pem b/ext/openssl/tests/sni_server_cs_expired.pem
new file mode 100644
index 00000000000..9f5a201b26d
--- /dev/null
+++ b/ext/openssl/tests/sni_server_cs_expired.pem
@@ -0,0 +1,57 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAvy5NhzktzEdsHTbGB6vqYANms5rn1zXFmTJrGlWCwoIsNmTf
+ahvZkrC1cCXTZ7fbPB8XQbpAtz2ZSU7OcwBW9B8okYUPo9zi/ptwcrgsQsN0hrcD
+8MBRUccevwime5fLvg8E9RJ/68y9y3BnRcVWYO2sAK9juTfidNjETU3Bb05oXv8D
+SD/6onXQu4uXDgsQ3cRXeld9UB0xazmQXyyiIqXc/cpTAnaEVYzn28aj7NlUbzNq
+511UXMXY44x9EcXWpPVZ7heNcJNzY5DCNzmtXKrt9yiMpWQcPXEzsESVxAMqib9u
+TFOlvVX17LIPxBG656PjTD9J1h6kBbMCUxzs7wIDAQABAoIBAQC85lBeY0X4ST3v
+I7bJz7kWQ2YP4uhfAdeLhoDDFWjNLffniwYhfwEc6xNri0R2f/jUT9gX7qORKwEx
+qPdeNCC2t67LElGg1FlJv2Z9Q7MgCKYzkdQH5s6y4e9kTHTLO/JpiceZKz1QTQ3f
+XOH9032E6nIAf0wmr6xHTgOwajrN8VI5BuPEMVmEwIw3AtYeqVuPCNKyGR4HUVkC
+2bAydnGngbRJRnNzmKcWJancxpHDGBSFqPyuXMFC7Jgo3ZmyCbGp99vuXVk/sW9x
+5aj94M9nRE0guk05ivH2/JZao2uLYkIgjFWlhNxKdWgWRk8DEuN4djC8mKS9YH1q
+crYRToMhAoGBAOspUTtKP54mpZmyhxuDqj02JaJRzNTskPHsiF1UhtXuw7uT+ryV
+ekUFLNXoFmn9mbx1WVaUvGH4qjilvQOxz7u++lz0ApqJEfyM3jc/cC40Y5zcuGSu
+Etbg+SyDoytlgMCIydJyrS7NNALSo5p5oG6XY2f8yd/DCAmo8LzypaHRAoGBANAf
+R1SlBMc/bOsi6GrJxcBVSCFMiKYiO5woL5aUKa9yM+UQuQ/6xbQ7Q+sOlt0FH3xo
+AJ2L60qTdjyXVtjOdtXs5ZC4l+C6AfnCx6yLr+fNc4SOYXEfqS4LZylgwKd9KyVB
+asspIW9Idbgebmi6vPyt9LDkIp0h1VuFGjkvQJK/AoGBAI4pbS0dprXyARyYW6sb
+fpgAmuG099IkrT9DUfCx/81myTclr2fAKal+BmvOIXaz0/OlMXvw8K19iVIzh7+r
+B70lJ+93p/dKM/BsLI5TsHqOO0YB/QsIXOVAHgJ2FfdPJnW+e9vYba+kZ/Po6PSi
+4ITaykJ8BIJcQgis89QWEGFxAoGBAJhQO+jzuDKF9ZWEf6ofrw0anOZZ16wWY5/e
+PS2rk3JmVxpuibHrKqPDt+ogTELHDAsFJmYmz3VNxHuFmrajK49Wh4/JuMVr/CQo
+6+8YcA1qa/94IFIlBLDBAafjujsZvOjQHnM+z8xcsGKmStF00Pjv6qNG4xoyd646
+FD4DmfOLAoGAWXehpopZKXE9gRAni881ucK6WqxPPBoofbozi09D0MmfarIVaSkv
+jNVVHBfLWd7IEXTjiipPBeUqq6Jc3pscN1Vp4rrl8jTmVTdazEv0LuzpdUFqmNo2
+M+xw17uz9D9Q32/aW1Lar0PdIaL/wGEDEyzEBFwrGppcENLilPz8gzU=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIFIjCCAwqgAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEDAOBgNVBAoMB1BIUC5uZXQxEDAOBgNVBAsMB29w
+ZW5zc2wxEDAOBgNVBAMMB3BocC5uZXQwHhcNMTgwMTE0MTgzNjEyWhcNMjYwNDAy
+MTgzNjEyWjBGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UE
+CgwHUEhQLm5ldDETMBEGA1UEAwwKY3MucGhwLm5ldDCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAL8uTYc5LcxHbB02xger6mADZrOa59c1xZkyaxpVgsKC
+LDZk32ob2ZKwtXAl02e32zwfF0G6QLc9mUlOznMAVvQfKJGFD6Pc4v6bcHK4LELD
+dIa3A/DAUVHHHr8IpnuXy74PBPUSf+vMvctwZ0XFVmDtrACvY7k34nTYxE1NwW9O
+aF7/A0g/+qJ10LuLlw4LEN3EV3pXfVAdMWs5kF8soiKl3P3KUwJ2hFWM59vGo+zZ
+VG8zauddVFzF2OOMfRHF1qT1We4XjXCTc2OQwjc5rVyq7fcojKVkHD1xM7BElcQD
+Kom/bkxTpb1V9eyyD8QRuuej40w/SdYepAWzAlMc7O8CAwEAAaOCAQkwggEFMAkG
+A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVu
+U1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFHPfd8dK
+Lz1R0Ck4WV1B9AWXd5DSMGwGA1UdIwRlMGOAFOPK44Eacedv7HbR2Igcbew+4kUa
+oUekRTBDMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRW5nbGFuZDEQMA4GA1UECgwH
+UEhQLm5ldDEQMA4GA1UEAwwHcGhwLm5ldIICEAAwDgYDVR0PAQH/BAQDAgWgMBMG
+A1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQB6WSIHEyDXLZxH
+hZjqSNQOA7Wc9Z2FCAiD29xYkGTL8WuPVGGP1mu4B92ytj+PMWwqSReDa7eTGLE7
+O7ozw9l+c+gNmHFNikSsGjlV2E8CToQOFMny+jAQYMSXf8UbTp9xDfgG02t/71hv
+SLWqdeHMLcR0xi0nBQH0vDOkwUbuWYqFa3jejHieGhykHM6CkIk6lqnyOEO+ooIF
+ZsLprrg1ss/mXCPI6niP0hze55ERKdxI7Rk8sZ4pVkf2SUWqZrUS0aJ+Ymmwi6Xd
+2V7izq5N30PkJS8MtqII4FAjRBIkwPh0sy8PmW/DzkYU+lYQnDfYLKDFKcj8xJK/
+o8oZUBsQltrSj0KlM9QuqxCTCBCy1nXZ9WHOhq+jdLiTc1Oi60uEHcUMrLK8aYc4
+HqIvZS6C2iwMI0d1OP3VxmAbMQ9yqRi+FbLYavJ3H40jrU9SYqdxa0BrTaz8MJNE
+6AEwgQDPChczSghvHME+Fs4mtGCY3TesbNZKVahQRjaFIhMZIZ4RP4CRc0bJOBG+
+8Me4+KHNsD2ki5b03wAN6C1P2QrMzI+gH9fXLZYp761ciDAsX6YIzrhHHYLxYpJH
+BkQKKs8dCQWE5IzgVrdlvC3Z1/l9om66wHqqx7nKnPfYs/Sfnwe9MpCD6xJrXiTm
+WS7NM6fbQpO9APNr7o0ZOjbbWFzlNw==
+-----END CERTIFICATE-----
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 9fefe153622..89125ed0765 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -540,6 +540,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1);
if (php_stream_write(stream, ZSTR_VAL(header.s), ZSTR_LEN(header.s)) != ZSTR_LEN(header.s)) {
+ if (reset_ssl_peer_name) {
+ php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
+ }
+
php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
php_stream_close(stream);
stream = NULL;
@@ -561,16 +565,18 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
/* enable SSL transport layer */
if (stream) {
+ php_stream_context *old_context = PHP_STREAM_CONTEXT(stream);
+
if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
php_stream_xport_crypto_enable(stream, 1) < 0) {
php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
php_stream_close(stream);
stream = NULL;
}
- }
- if (reset_ssl_peer_name) {
- php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
+ if (reset_ssl_peer_name) {
+ php_stream_context_unset_option(old_context, "ssl", "peer_name");
+ }
}
}