Fix CVEs up to 8.1.34:

Fix Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface  GHSA-4w77-75f9-2c8w
Fix Configuring a proxy in a stream context might allow for CRLF injection in URIs  CVE-2024-11234
Fix Single byte overread with convert.quoted-printable-decode filter  CVE-2024-11233
Fix Leak partial content of the heap through heap buffer over-read  CVE-2024-8929
Fix libxml streams use wrong `content-type` header when requesting a redirected resource  CVE-2025-1219
Fix Stream HTTP wrapper header check might omit basic auth header  CVE-2025-1736
Fix Stream HTTP wrapper truncate redirect location to 1024 bytes  CVE-2025-1861
Fix Streams HTTP wrapper does not fail for headers without colon  CVE-2025-1734
Fix Header parser of `http` stream wrapper does not handle folded headers  CVE-2025-1217
Fix pgsql extension does not check for errors during escaping  CVE-2025-1735
Fix NULL Pointer Dereference in PHP SOAP Extension via Large XML Namespace Prefix  CVE-2025-6491
Fix Null byte termination in hostnames  CVE-2025-1220
Fix Null byte termination in dns_get_record()  GHSA-www2-q4fc-65wf
Fix Heap buffer overflow in array_merge()  CVE-2025-14178
Fix Information Leak of Memory in getimagesize  CVE-2025-14177

Resolves: RHEL-141181
This commit is contained in:
Remi Collet 2026-01-19 08:43:37 +01:00
parent ad6da4ad66
commit 1d1654b533
18 changed files with 8240 additions and 1 deletions

68
php-cve-2024-11233.patch Normal file

File diff suppressed because one or more lines are too long

95
php-cve-2024-11234.patch Normal file
View File

@ -0,0 +1,95 @@
From 494de65139592da0e5e5b6fdf198c2f9c762f4d6 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Fri, 8 Nov 2024 23:43:47 +0100
Subject: [PATCH 3/7] Fix GHSA-c5f2-jwm7-mmq2: stream HTTP fulluri CRLF
injection
(cherry picked from commit 426a6d4539ebee34879ac5de857036bb6ff0e732)
(cherry picked from commit bc1f192102dd8cbda028e40aa31604c4885d387c)
(cherry picked from commit 8d130e16fbfda7d154fedfa0f1ff1d5ad5e26815)
---
ext/standard/http_fopen_wrapper.c | 18 ++++++++----
.../tests/http/ghsa-c5f2-jwm7-mmq2.phpt | 28 +++++++++++++++++++
2 files changed, 40 insertions(+), 6 deletions(-)
create mode 100644 ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 4d918b21e65..aeeb438f0f9 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -186,6 +186,11 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
return NULL;
}
+ /* Should we send the entire path in the request line, default to no. */
+ if (context && (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) {
+ request_fulluri = zend_is_true(tmpzval);
+ }
+
use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 4) && ZSTR_VAL(resource->scheme)[4] == 's';
/* choose default ports */
if (use_ssl && resource->port == 0)
@@ -205,6 +210,13 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
}
}
+ if (request_fulluri && (strchr(path, '\n') != NULL || strchr(path, '\r') != NULL)) {
+ php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper full URI path does not allow CR or LF characters");
+ php_url_free(resource);
+ efree(transport_string);
+ return NULL;
+ }
+
if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) {
double d = zval_get_double(tmpzval);
#ifndef PHP_WIN32
@@ -385,12 +397,6 @@ finish:
smart_str_appends(&req_buf, "GET ");
}
- /* Should we send the entire path in the request line, default to no. */
- if (!request_fulluri && context &&
- (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) {
- request_fulluri = zend_is_true(tmpzval);
- }
-
if (request_fulluri) {
/* Ask for everything */
smart_str_appends(&req_buf, path);
diff --git a/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt
new file mode 100644
index 00000000000..5b2e04f94f2
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-c5f2-jwm7-mmq2.phpt
@@ -0,0 +1,28 @@
+--TEST--
+GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context might allow for CRLF injection in URIs)
+--INI--
+allow_url_fopen=1
+--CONFLICTS--
+server
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+echo $_SERVER['REQUEST_URI'];
+CODE;
+
+include __DIR__."/../../../../sapi/cli/tests/php_cli_server.inc";
+php_cli_server_start($serverCode, null, []);
+
+$host = PHP_CLI_SERVER_ADDRESS;
+$userinput = "index.php HTTP/1.1\r\nHost: $host\r\n\r\nGET /index2.php HTTP/1.1\r\nHost: $host\r\n\r\nGET /index.php";
+$context = stream_context_create(['http' => ['proxy' => 'tcp://' . $host, 'request_fulluri' => true]]);
+echo file_get_contents("http://$host/$userinput", false, $context);
+?>
+--EXPECTF--
+Warning: file_get_contents(http://localhost:%d/index.php HTTP/1.1
+Host: localhost:%d
+
+GET /index2.php HTTP/1.1
+Host: localhost:%d
+
+GET /index.php): failed to open stream: HTTP wrapper full URI path does not allow CR or LF characters in %s on line %d
--
2.47.0

119
php-cve-2024-11236.patch Normal file
View File

@ -0,0 +1,119 @@
From 97546df8d6900b115536c17af9213f1da837b82e Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Thu, 24 Oct 2024 22:02:17 +0200
Subject: [PATCH 1/7] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the dblib
quoter causing OOB writes
(cherry picked from commit d9baa9fed8c3ba692a36b388c0c7762e5102e2e0)
(cherry picked from commit 5d9e54065ed18c51e4f25d8900635f90810c7394)
---
ext/pdo_dblib/dblib_driver.c | 8 ++++++-
ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt | 24 ++++++++++++++++++++
2 files changed, 31 insertions(+), 1 deletion(-)
create mode 100644 ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt
diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c
index f36451afeeb..1dc75a4d2e3 100644
--- a/ext/pdo_dblib/dblib_driver.c
+++ b/ext/pdo_dblib/dblib_driver.c
@@ -154,6 +154,7 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
size_t i;
char * q;
+ size_t extralen = 0;
*quotedlen = 0;
if (H->assume_national_character_set_strings) {
@@ -168,7 +169,7 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
/* Detect quoted length, adding extra char for doubled single quotes */
for (i = 0; i < unquotedlen; i++) {
- if (unquoted[i] == '\'') ++*quotedlen;
+ if (unquoted[i] == '\'') ++extralen;
++*quotedlen;
}
@@ -176,6 +177,11 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
if (use_national_character_set) {
++*quotedlen; /* N prefix */
}
+ if (UNEXPECTED(*quotedlen > ZSTR_MAX_LEN - extralen)) {
+ return 0;
+ }
+
+ *quotedlen += extralen;
q = *quoted = emalloc(*quotedlen + 1); /* Add byte for terminal null */
if (use_national_character_set) {
*q++ = 'N';
diff --git a/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt
new file mode 100644
index 00000000000..431c61951ee
--- /dev/null
+++ b/ext/pdo_dblib/tests/GHSA-5hqh-c84r-qjcv.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing OOB writes)
+--EXTENSIONS--
+pdo_dblib
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE != 4) die("skip for 32bit platforms only");
+if (PHP_OS_FAMILY === "Windows") die("skip not for Windows because the virtual address space for application is only 2GiB");
+if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
+require __DIR__ . '/config.inc';
+getDbConnection();
+?>
+--INI--
+memory_limit=-1
+--FILE--
+<?php
+
+require __DIR__ . '/config.inc';
+$db = getDbConnection();
+var_dump($db->quote(str_repeat("'", 2147483646)));
+
+?>
+--EXPECT--
+bool(false)
--
2.47.0
From 0530cbfe5c3044537de52d8382eba5d69dbac726 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Thu, 24 Oct 2024 22:02:36 +0200
Subject: [PATCH 2/7] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the firebird
quoter causing OOB writes
(cherry picked from commit 69c5f68fdc3deed9ebce2cc44b4bf5e0c47cd28f)
(cherry picked from commit b4f73be75dbdde970a18cc7a636898b10400fb3f)
---
ext/pdo_firebird/firebird_driver.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c
index 3e403afd368..5b74290abcc 100644
--- a/ext/pdo_firebird/firebird_driver.c
+++ b/ext/pdo_firebird/firebird_driver.c
@@ -243,7 +243,7 @@ free_statement:
static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, /* {{{ */
char **quoted, size_t *quotedlen, enum pdo_param_type paramtype)
{
- int qcount = 0;
+ size_t qcount = 0;
char const *co, *l, *r;
char *c;
@@ -258,6 +258,10 @@ static int firebird_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t u
/* count the number of ' characters */
for (co = unquoted; (co = strchr(co,'\'')); qcount++, co++);
+ if (UNEXPECTED(unquotedlen + 2 > ZSTR_MAX_LEN - qcount)) {
+ return 0;
+ }
+
*quotedlen = unquotedlen + qcount + 2;
*quoted = c = emalloc(*quotedlen+1);
*c++ = '\'';
--
2.47.0

2714
php-cve-2024-8929.patch Normal file

File diff suppressed because it is too large Load Diff

139
php-cve-2024-8932.patch Normal file
View File

@ -0,0 +1,139 @@
From 50e9e72530a4805980384b8ea6672877af816145 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Thu, 26 Sep 2024 22:22:27 +0200
Subject: [PATCH 4/7] Fix GHSA-g665-fm4p-vhff: OOB access in ldap_escape
(cherry picked from commit f9ecf90070a11dad09ca7671a712f81cc2a7d52f)
(cherry picked from commit 9f367d847989b339c33369737daf573e30bab5f1)
---
ext/ldap/ldap.c | 21 ++++++++++++++--
ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt | 28 ++++++++++++++++++++++
ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt | 29 +++++++++++++++++++++++
3 files changed, 76 insertions(+), 2 deletions(-)
create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
create mode 100644 ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index 72a39bd93df..75adf1b5df2 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -49,6 +49,7 @@
#include "ext/standard/php_string.h"
#include "ext/standard/info.h"
+#include "Zend/zend_exceptions.h"
#ifdef HAVE_LDAP_SASL
#include <sasl/sasl.h>
@@ -3836,13 +3837,23 @@ static zend_string* php_ldap_do_escape(const zend_bool *map, const char *value,
zend_string *ret;
for (i = 0; i < valuelen; i++) {
- len += (map[(unsigned char) value[i]]) ? 3 : 1;
+ size_t addend = (map[(unsigned char) value[i]]) ? 3 : 1;
+ if (len > ZSTR_MAX_LEN - addend) {
+ return NULL;
+ }
+ len += addend;
}
/* Per RFC 4514, a leading and trailing space must be escaped */
if ((flags & PHP_LDAP_ESCAPE_DN) && (value[0] == ' ')) {
+ if (len > ZSTR_MAX_LEN - 2) {
+ return NULL;
+ }
len += 2;
}
if ((flags & PHP_LDAP_ESCAPE_DN) && ((valuelen > 1) && (value[valuelen - 1] == ' '))) {
+ if (len > ZSTR_MAX_LEN - 2) {
+ return NULL;
+ }
len += 2;
}
@@ -3909,7 +3920,13 @@ PHP_FUNCTION(ldap_escape)
php_ldap_escape_map_set_chars(map, ignores, ignoreslen, 0);
}
- RETURN_NEW_STR(php_ldap_do_escape(map, value, valuelen, flags));
+ zend_string *result = php_ldap_do_escape(map, value, valuelen, flags);
+ if (UNEXPECTED(!result)) {
+ zend_throw_exception(NULL, "Argument #1 ($value) is too long", 0);
+ return;
+ }
+
+ RETURN_NEW_STR(result);
}
#ifdef STR_TRANSLATION
diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
new file mode 100644
index 00000000000..734bbe91d42
--- /dev/null
+++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt
@@ -0,0 +1,28 @@
+--TEST--
+GHSA-g665-fm4p-vhff (OOB access in ldap_escape)
+--EXTENSIONS--
+ldap
+--INI--
+memory_limit=-1
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 4) die("skip only for 32-bit");
+if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
+?>
+--FILE--
+<?php
+try {
+ ldap_escape(' '.str_repeat("#", 1431655758), "", LDAP_ESCAPE_DN);
+} catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN);
+} catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+ldap_escape(): Argument #1 ($value) is too long
+ldap_escape(): Argument #1 ($value) is too long
diff --git a/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
new file mode 100644
index 00000000000..5c1b0fb6611
--- /dev/null
+++ b/ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GHSA-g665-fm4p-vhff (OOB access in ldap_escape)
+--EXTENSIONS--
+ldap
+--INI--
+memory_limit=-1
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE !== 4) die("skip only for 32-bit");
+if (getenv("SKIP_SLOW_TESTS")) die("skip slow test");
+?>
+--FILE--
+<?php
+try {
+ ldap_escape(str_repeat("*", 1431655759), "", LDAP_ESCAPE_FILTER);
+} catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+}
+
+// would allocate a string of length 2
+try {
+ ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER);
+} catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+ldap_escape(): Argument #1 ($value) is too long
+ldap_escape(): Argument #1 ($value) is too long
--
2.47.0

917
php-cve-2025-1217.patch Normal file
View File

@ -0,0 +1,917 @@
From bf4a8df2b3972118c87b05450e9062d3926f6be8 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 31 Dec 2024 18:57:02 +0100
Subject: [PATCH 01/11] Fix GHSA-ghsa-v8xr-gpvj-cx9g: http header folding
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This adds HTTP header folding support for HTTP wrapper response
headers.
Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
(cherry picked from commit d20b4c97a9f883b62b65b82d939c5af9a2028ef1)
(cherry picked from commit 4fec08542748c25573063ffc53ea89cd5de1edf0)
---
ext/openssl/tests/ServerClientTestCase.inc | 65 +++-
ext/standard/http_fopen_wrapper.c | 347 ++++++++++++------
.../tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt | 49 +++
.../tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt | 51 +++
.../tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt | 49 +++
.../tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt | 48 +++
.../tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt | 48 +++
.../tests/http/http_response_header_05.phpt | 35 --
8 files changed, 537 insertions(+), 155 deletions(-)
create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
delete mode 100644 ext/standard/tests/http/http_response_header_05.phpt
diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc
index 753366df6f4..c74da444102 100644
--- a/ext/openssl/tests/ServerClientTestCase.inc
+++ b/ext/openssl/tests/ServerClientTestCase.inc
@@ -4,14 +4,19 @@ const WORKER_ARGV_VALUE = 'RUN_WORKER';
const WORKER_DEFAULT_NAME = 'server';
-function phpt_notify($worker = WORKER_DEFAULT_NAME)
+function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void
{
- ServerClientTestCase::getInstance()->notify($worker);
+ ServerClientTestCase::getInstance()->notify($worker, $message);
}
-function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null)
+function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string
{
- ServerClientTestCase::getInstance()->wait($worker, $timeout);
+ return ServerClientTestCase::getInstance()->wait($worker, $timeout);
+}
+
+function phpt_notify_server_start($server): void
+{
+ ServerClientTestCase::getInstance()->notify_server_start($server);
}
function phpt_has_sslv3() {
@@ -119,43 +124,73 @@ class ServerClientTestCase
eval($code);
}
- public function run($masterCode, $workerCode)
+ /**
+ * Run client and all workers
+ *
+ * @param string $clientCode The client PHP code
+ * @param string|array $workerCode
+ * @param bool $ephemeral Select whether automatic port selection and automatic awaiting is used
+ * @return void
+ * @throws Exception
+ */
+ public function run(string $clientCode, $workerCode, bool $ephemeral = true): void
{
if (!is_array($workerCode)) {
$workerCode = [WORKER_DEFAULT_NAME => $workerCode];
}
- foreach ($workerCode as $worker => $code) {
+ reset($workerCode);
+ $code = current($workerCode);
+ $worker = key($workerCode);
+ while ($worker != null) {
$this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code));
+ $code = next($workerCode);
+ if ($ephemeral) {
+ $addr = trim($this->wait($worker));
+ if (empty($addr)) {
+ throw new \Exception("Failed server start");
+ }
+ if ($code === false) {
+ $clientCode = preg_replace('/{{\s*ADDR\s*}}/', $addr, $clientCode);
+ } else {
+ $code = preg_replace('/{{\s*ADDR\s*}}/', $addr, $code);
+ }
+ }
+ $worker = key($workerCode);
}
- eval($this->stripPhpTagsFromCode($masterCode));
+
+ eval($this->stripPhpTagsFromCode($clientCode));
foreach ($workerCode as $worker => $code) {
$this->cleanupWorkerProcess($worker);
}
}
- public function wait($worker, $timeout = null)
+ public function wait($worker, $timeout = null): ?string
{
$handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker];
if ($timeout === null) {
- fgets($handle);
- return true;
+ return fgets($handle);
}
stream_set_blocking($handle, false);
$read = [$handle];
$result = stream_select($read, $write, $except, $timeout);
if (!$result) {
- return false;
+ return null;
}
- fgets($handle);
+ $result = fgets($handle);
stream_set_blocking($handle, true);
- return true;
+ return $result;
+ }
+
+ public function notify(string $worker, string $message = ""): void
+ {
+ fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n");
}
- public function notify($worker)
+ public function notify_server_start($server): void
{
- fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
+ echo stream_socket_get_name($server, false) . "\n";
}
}
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index aeeb438f0f9..08386cfafcd 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -116,6 +116,172 @@ static zend_bool check_has_header(const char *headers, const char *header) {
return 0;
}
+typedef struct _php_stream_http_response_header_info {
+ php_stream_filter *transfer_encoding;
+ size_t file_size;
+ zend_bool follow_location;
+ char location[HTTP_HEADER_BLOCK_SIZE];
+} php_stream_http_response_header_info;
+
+static void php_stream_http_response_header_info_init(
+ php_stream_http_response_header_info *header_info)
+{
+ header_info->transfer_encoding = NULL;
+ header_info->file_size = 0;
+ header_info->follow_location = 1;
+ header_info->location[0] = '\0';
+}
+
+/* Trim white spaces from response header line and update its length */
+static zend_bool php_stream_http_response_header_trim(char *http_header_line,
+ size_t *http_header_line_length)
+{
+ char *http_header_line_end = http_header_line + *http_header_line_length - 1;
+ while (http_header_line_end >= http_header_line &&
+ (*http_header_line_end == '\n' || *http_header_line_end == '\r')) {
+ http_header_line_end--;
+ }
+
+ /* The primary definition of an HTTP header in RFC 7230 states:
+ * > Each header field consists of a case-insensitive field name followed
+ * > by a colon (":"), optional leading whitespace, the field value, and
+ * > optional trailing whitespace. */
+
+ /* Strip trailing whitespace */
+ zend_bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t');
+ if (space_trim) {
+ do {
+ http_header_line_end--;
+ } while (http_header_line_end >= http_header_line &&
+ (*http_header_line_end == ' ' || *http_header_line_end == '\t'));
+ }
+ http_header_line_end++;
+ *http_header_line_end = '\0';
+ *http_header_line_length = http_header_line_end - http_header_line;
+
+ return space_trim;
+}
+
+/* Process folding headers of the current line and if there are none, parse last full response
+ * header line. It returns NULL if the last header is finished, otherwise it returns updated
+ * last header line. */
+static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ php_stream_context *context, int options, zend_string *last_header_line_str,
+ char *header_line, size_t *header_line_length, int response_code,
+ zval *response_header, php_stream_http_response_header_info *header_info)
+{
+ char *last_header_line = ZSTR_VAL(last_header_line_str);
+ size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
+ char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1;
+
+ /* Process non empty header line. */
+ if (header_line && (*header_line != '\n' && *header_line != '\r')) {
+ /* Removing trailing white spaces. */
+ if (php_stream_http_response_header_trim(header_line, header_line_length) &&
+ *header_line_length == 0) {
+ /* Only spaces so treat as an empty folding header. */
+ return last_header_line_str;
+ }
+
+ /* Process folding headers if starting with a space or a tab. */
+ if (header_line && (*header_line == ' ' || *header_line == '\t')) {
+ char *http_folded_header_line = header_line;
+ size_t http_folded_header_line_length = *header_line_length;
+ /* Remove the leading white spaces. */
+ while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') {
+ http_folded_header_line++;
+ http_folded_header_line_length--;
+ }
+ /* It has to have some characters because it would get returned after the call
+ * php_stream_http_response_header_trim above. */
+ ZEND_ASSERT(http_folded_header_line_length > 0);
+ /* Concatenate last header line, space and current header line. */
+ zend_string *extended_header_str = zend_string_alloc(last_header_line_length + 1 + http_folded_header_line_length, 0);
+ memcpy(ZSTR_VAL(extended_header_str), last_header_line, last_header_line_length);
+ ZSTR_VAL(extended_header_str)[last_header_line_length] = ' ';
+ memcpy(ZSTR_VAL(extended_header_str) + last_header_line_length + 1, http_folded_header_line, http_folded_header_line_length);
+ ZSTR_VAL(extended_header_str)[ZSTR_LEN(extended_header_str)] = 0;
+ zend_string_efree(last_header_line_str);
+ last_header_line_str = extended_header_str;
+ /* Return new header line. */
+ return last_header_line_str;
+ }
+ }
+
+ /* Find header separator position. */
+ char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
+ if (last_header_value) {
+ last_header_value++; /* Skip ':'. */
+
+ /* Strip leading whitespace. */
+ while (last_header_value < last_header_line_end
+ && (*last_header_value == ' ' || *last_header_value == '\t')) {
+ last_header_value++;
+ }
+ } else {
+ /* There is no colon. Set the value to the end of the header line, which is effectively
+ * an empty string. */
+ last_header_value = last_header_line_end;
+ }
+
+ zend_bool store_header = 1;
+ zval *tmpzval = NULL;
+
+ if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) {
+ /* Check if the location should be followed. */
+ if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
+ header_info->follow_location = zval_is_true(tmpzval);
+ } else if (!((response_code >= 300 && response_code < 304)
+ || 307 == response_code || 308 == response_code)) {
+ /* The redirection should not be automatic if follow_location is not set and
+ * response_code not in (300, 301, 302, 303 and 307)
+ * see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
+ * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
+ header_info->follow_location = 0;
+ }
+ strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
+ } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
+ php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
+ } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
+ header_info->file_size = atoi(last_header_value);
+ php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0);
+ } else if (
+ !strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
+ && !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1)
+ ) {
+ /* Create filter to decode response body. */
+ if (!(options & STREAM_ONLY_GET_HEADERS)) {
+ zend_long decode = 1;
+
+ if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
+ decode = zend_is_true(tmpzval);
+ }
+ if (decode) {
+ if (header_info->transfer_encoding != NULL) {
+ /* Prevent a memory leak in case there are more transfer-encoding headers. */
+ php_stream_filter_free(header_info->transfer_encoding);
+ }
+ header_info->transfer_encoding = php_stream_filter_create(
+ "dechunk", NULL, php_stream_is_persistent(stream));
+ if (header_info->transfer_encoding != NULL) {
+ /* Do not store transfer-encoding header. */
+ store_header = 0;
+ }
+ }
+ }
+ }
+
+ if (store_header) {
+ zval http_header;
+ ZVAL_NEW_STR(&http_header, last_header_line_str);
+ zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
+ } else {
+ zend_string_efree(last_header_line_str);
+ }
+
+ return NULL;
+}
+
static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
const char *path, const char *mode, int options, zend_string **opened_path,
php_stream_context *context, int redirect_max, int flags,
@@ -128,11 +294,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
zend_string *tmp = NULL;
char *ua_str = NULL;
zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
- char location[HTTP_HEADER_BLOCK_SIZE];
int reqok = 0;
char *http_header_line = NULL;
+ zend_string *last_header_line_str = NULL;
+ php_stream_http_response_header_info header_info;
char tmp_line[128];
- size_t chunk_size = 0, file_size = 0;
+ size_t chunk_size = 0;
int eol_detect = 0;
char *transport_string;
zend_string *errstr = NULL;
@@ -143,8 +310,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
char *user_headers = NULL;
int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
- zend_bool follow_location = 1;
- php_stream_filter *transfer_encoding = NULL;
int response_code;
smart_str req_buf = {0};
zend_bool custom_request_method;
@@ -657,8 +822,6 @@ finish:
/* send it */
php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
- location[0] = '\0';
-
if (Z_ISUNDEF_P(response_header)) {
array_init(response_header);
}
@@ -736,125 +899,101 @@ finish:
}
}
- /* read past HTTP headers */
+ php_stream_http_response_header_info_init(&header_info);
+ /* read past HTTP headers */
while (!php_stream_eof(stream)) {
size_t http_header_line_length;
if (http_header_line != NULL) {
efree(http_header_line);
}
- if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') {
- char *e = http_header_line + http_header_line_length - 1;
- char *http_header_value;
-
- while (e >= http_header_line && (*e == '\n' || *e == '\r')) {
- e--;
- }
-
- /* The primary definition of an HTTP header in RFC 7230 states:
- * > Each header field consists of a case-insensitive field name followed
- * > by a colon (":"), optional leading whitespace, the field value, and
- * > optional trailing whitespace. */
-
- /* Strip trailing whitespace */
- while (e >= http_header_line && (*e == ' ' || *e == '\t')) {
- e--;
- }
-
- /* Terminate header line */
- e++;
- *e = '\0';
- http_header_line_length = e - http_header_line;
-
- http_header_value = memchr(http_header_line, ':', http_header_line_length);
- if (http_header_value) {
- http_header_value++; /* Skip ':' */
-
- /* Strip leading whitespace */
- while (http_header_value < e
- && (*http_header_value == ' ' || *http_header_value == '\t')) {
- http_header_value++;
+ if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) {
+ zend_bool last_line;
+ if (*http_header_line == '\r') {
+ if (http_header_line[1] != '\n') {
+ php_stream_close(stream);
+ stream = NULL;
+ php_stream_wrapper_log_error(wrapper, options,
+ "HTTP invalid header name (cannot start with CR character)!");
+ goto out;
}
+ last_line = 1;
+ } else if (*http_header_line == '\n') {
+ last_line = 1;
} else {
- /* There is no colon. Set the value to the end of the header line, which is
- * effectively an empty string. */
- http_header_value = e;
+ last_line = 0;
}
-
- if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) {
- if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
- follow_location = zval_is_true(tmpzval);
- } else if (!((response_code >= 300 && response_code < 304)
- || 307 == response_code || 308 == response_code)) {
- /* we shouldn't redirect automatically
- if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
- RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
- follow_location = 0;
+
+ if (last_header_line_str != NULL) {
+ /* Parse last header line. */
+ last_header_line_str = php_stream_http_response_headers_parse(stream, context,
+ options, last_header_line_str, http_header_line, &http_header_line_length,
+ response_code, response_header, &header_info);
+ if (last_header_line_str != NULL) {
+ /* Folding header present so continue. */
+ continue;
}
- strlcpy(location, http_header_value, sizeof(location));
- } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
- php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0);
- } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
- file_size = atoi(http_header_value);
- php_stream_notify_file_size(context, file_size, http_header_line, 0);
- } else if (
- !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
- && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1)
- ) {
-
- /* create filter to decode response body */
- if (!(options & STREAM_ONLY_GET_HEADERS)) {
- zend_long decode = 1;
-
- if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
- decode = zend_is_true(tmpzval);
- }
- if (decode) {
- transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream));
- if (transfer_encoding) {
- /* don't store transfer-encodeing header */
- continue;
- }
- }
+ } else if (!last_line) {
+ /* The first line cannot start with spaces. */
+ if (*http_header_line == ' ' || *http_header_line == '\t') {
+ php_stream_close(stream);
+ stream = NULL;
+ php_stream_wrapper_log_error(wrapper, options,
+ "HTTP invalid response format (folding header at the start)!");
+ goto out;
}
+ /* Trim the first line if it is not the last line. */
+ php_stream_http_response_header_trim(http_header_line, &http_header_line_length);
}
-
- {
- zval http_header;
- ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length);
- zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
+ if (last_line) {
+ /* For the last line the last header line must be NULL. */
+ ZEND_ASSERT(last_header_line_str == NULL);
+ break;
}
+ /* Save current line as the last line so it gets parsed in the next round. */
+ last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0);
} else {
break;
}
}
- if (!reqok || (location[0] != '\0' && follow_location)) {
- if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
+ /* If the stream was closed early, we still want to process the last line to keep BC. */
+ if (last_header_line_str != NULL) {
+ php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
+ NULL, NULL, response_code, response_header, &header_info);
+ }
+
+ if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+ if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
goto out;
}
- if (location[0] != '\0')
- php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
+ if (header_info.location[0] != '\0')
+ php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
php_stream_close(stream);
stream = NULL;
- if (location[0] != '\0') {
+ if (header_info.transfer_encoding) {
+ php_stream_filter_free(header_info.transfer_encoding);
+ header_info.transfer_encoding = NULL;
+ }
+
+ if (header_info.location[0] != '\0') {
char new_path[HTTP_HEADER_BLOCK_SIZE];
char loc_path[HTTP_HEADER_BLOCK_SIZE];
*new_path='\0';
- if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
- strncasecmp(location, "https://", sizeof("https://")-1) &&
- strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
- strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
+ if (strlen(header_info.location) < 8 ||
+ (strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
+ strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
+ strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
+ strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
{
- if (*location != '/') {
- if (*(location+1) != '\0' && resource->path) {
+ if (*header_info.location != '/') {
+ if (*(header_info.location+1) != '\0' && resource->path) {
char *s = strrchr(ZSTR_VAL(resource->path), '/');
if (!s) {
s = ZSTR_VAL(resource->path);
@@ -870,15 +1009,17 @@ finish:
if (resource->path &&
ZSTR_VAL(resource->path)[0] == '/' &&
ZSTR_VAL(resource->path)[1] == '\0') {
- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location);
+ snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
+ ZSTR_VAL(resource->path), header_info.location);
} else {
- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location);
+ snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
+ ZSTR_VAL(resource->path), header_info.location);
}
} else {
- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
+ snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
}
} else {
- strlcpy(loc_path, location, sizeof(loc_path));
+ strlcpy(loc_path, header_info.location, sizeof(loc_path));
}
if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
@@ -886,7 +1027,7 @@ finish:
snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
}
} else {
- strlcpy(new_path, location, sizeof(new_path));
+ strlcpy(new_path, header_info.location, sizeof(new_path));
}
php_url_free(resource);
@@ -939,7 +1080,7 @@ out:
if (header_init) {
ZVAL_COPY(&stream->wrapperdata, response_header);
}
- php_stream_notify_progress_init(context, 0, file_size);
+ php_stream_notify_progress_init(context, 0, header_info.file_size);
/* Restore original chunk size now that we're done with headers */
if (options & STREAM_WILL_CAST)
@@ -955,12 +1096,8 @@ out:
/* restore mode */
strlcpy(stream->mode, mode, sizeof(stream->mode));
- if (transfer_encoding) {
- php_stream_filter_append(&stream->readfilters, transfer_encoding);
- }
- } else {
- if (transfer_encoding) {
- php_stream_filter_free(transfer_encoding);
+ if (header_info.transfer_encoding) {
+ php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding);
}
}
diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
new file mode 100644
index 00000000000..64904bfcd1d
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (single)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n charset=utf-8\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html; charset=utf-8
+string(4) "body"
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(38) "Content-Type: text/html; charset=utf-8"
+}
diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
new file mode 100644
index 00000000000..a6d9d00fd58
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
@@ -0,0 +1,51 @@
+--TEST--
+GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (multiple)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\nCustom-Header: somevalue;\r\n param1=value1; \r\n param2=value2\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html;
+string(4) "body"
+array(3) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(24) "Content-Type: text/html;"
+ [2]=>
+ string(54) "Custom-Header: somevalue; param1=value1; param2=value2"
+}
diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
new file mode 100644
index 00000000000..4eff7fc63f3
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
@@ -0,0 +1,49 @@
+--TEST--
+GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (empty)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html; charset=utf-8
+string(4) "body"
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(38) "Content-Type: text/html; charset=utf-8"
+}
diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
new file mode 100644
index 00000000000..71aed2fa2e8
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
@@ -0,0 +1,48 @@
+--TEST--
+GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (first line)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\n Content-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+
+Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid response format (folding header at the start)! in %s
+bool(false)
+array(1) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+}
diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
new file mode 100644
index 00000000000..49d845d84b4
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
@@ -0,0 +1,48 @@
+--TEST--
+GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (CR before header name)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\n\rIgnored: ignored\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+
+Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid header name (cannot start with CR character)! in %s
+bool(false)
+array(1) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+}
diff --git a/ext/standard/tests/http/http_response_header_05.phpt b/ext/standard/tests/http/http_response_header_05.phpt
deleted file mode 100644
index dbdd7b8b1a0..00000000000
--- a/ext/standard/tests/http/http_response_header_05.phpt
+++ /dev/null
@@ -1,35 +0,0 @@
---TEST--
-$http_reponse_header (whitespace-only "header")
---SKIPIF--
-<?php require 'server.inc'; http_server_skipif('tcp://127.0.0.1:22350'); ?>
---INI--
-allow_url_fopen=1
---FILE--
-<?php
-require 'server.inc';
-
-$responses = array(
- "data://text/plain,HTTP/1.0 200 Ok\r\n \r\n\r\nBody",
-);
-
-$pid = http_server("tcp://127.0.0.1:22350", $responses, $output);
-
-function test() {
- $f = file_get_contents('http://127.0.0.1:22350/');
- var_dump($f);
- var_dump($http_response_header);
-}
-test();
-
-http_server_kill($pid);
-?>
-==DONE==
---EXPECT--
-string(4) "Body"
-array(2) {
- [0]=>
- string(15) "HTTP/1.0 200 Ok"
- [1]=>
- string(0) ""
-}
-==DONE==
--
2.48.1

1906
php-cve-2025-1219.patch Normal file

File diff suppressed because it is too large Load Diff

154
php-cve-2025-1220.patch Normal file
View File

@ -0,0 +1,154 @@
From d407d8a8735ebf43bee3e6b49fb013b8aa4b6bfc Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Thu, 10 Apr 2025 15:15:36 +0200
Subject: [PATCH 2/4] Fix GHSA-3cr5-j632-f35r: Null byte in hostnames
This fixes stream_socket_client() and fsockopen().
Specifically it adds a check to parse_ip_address_ex and it also makes
sure that the \0 is not ignored in fsockopen() hostname formatting.
(cherry picked from commit cac8f7f1cf4939f55f06b68120040f057682d89c)
(cherry picked from commit 36150278addd8686a9899559241296094bd57282)
---
ext/standard/fsock.c | 27 +++++++++++++++++--
.../tests/network/ghsa-3cr5-j632-f35r.phpt | 21 +++++++++++++++
.../tests/streams/ghsa-3cr5-j632-f35r.phpt | 26 ++++++++++++++++++
main/streams/xp_socket.c | 9 ++++---
4 files changed, 78 insertions(+), 5 deletions(-)
create mode 100644 ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
create mode 100644 ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c
index fe8fbea85ca..df6a74b078f 100644
--- a/ext/standard/fsock.c
+++ b/ext/standard/fsock.c
@@ -25,6 +25,28 @@
#include "php_network.h"
#include "file.h"
+static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len,
+ const char *host, size_t host_len, zend_long port)
+{
+ char portbuf[32];
+ int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port);
+ size_t total_len = prefix_len + host_len + portlen;
+
+ char *result = emalloc(total_len + 1);
+
+ if (prefix_len > 0) {
+ memcpy(result, prefix, prefix_len);
+ }
+ memcpy(result + prefix_len, host, host_len);
+ memcpy(result + prefix_len + host_len, portbuf, portlen);
+
+ result[total_len] = '\0';
+
+ *message = result;
+
+ return total_len;
+}
+
/* {{{ php_fsockopen() */
static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
@@ -59,11 +81,12 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (persistent) {
- spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, host, port);
+ php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host,
+ host_len, port);
}
if (port > 0) {
- hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port);
+ hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port);
} else {
hostname_len = host_len;
hostname = host;
diff --git a/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
new file mode 100644
index 00000000000..e16d3fa9060
--- /dev/null
+++ b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
@@ -0,0 +1,21 @@
+--TEST--
+GHSA-3cr5-j632-f35r: Null byte termination in fsockopen()
+--FILE--
+<?php
+
+$server = stream_socket_server("tcp://localhost:0");
+
+if (preg_match('/:(\d+)$/', stream_socket_get_name($server, false), $m)) {
+ $client = fsockopen("localhost\0.example.com", intval($m[1]));
+ var_dump($client);
+ if ($client) {
+ fclose($client);
+ }
+}
+fclose($server);
+
+?>
+--EXPECTF--
+
+Warning: fsockopen(): unable to connect to localhost:%d (The hostname must not contain null bytes) in %s
+bool(false)
diff --git a/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
new file mode 100644
index 00000000000..bc1f34eaf58
--- /dev/null
+++ b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
@@ -0,0 +1,26 @@
+--TEST--
+GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client()
+--FILE--
+<?php
+
+$server = stream_socket_server("tcp://localhost:0");
+$socket_name = stream_socket_get_name($server, false);
+
+if (preg_match('/:(\d+)$/', $socket_name, $m)) {
+ $port = $m[1];
+ $client = stream_socket_client("tcp://localhost\0.example.com:$port");
+ var_dump($client);
+ if ($client) {
+ fclose($client);
+ }
+} else {
+ echo "Could not extract port from socket name: $socket_name\n";
+}
+
+fclose($server);
+
+?>
+--EXPECTF--
+
+Warning: stream_socket_client(): unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s
+bool(false)
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index 46b23b63ada..7a192ea6c0b 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -580,12 +580,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po
char *colon;
char *host = NULL;
-#ifdef HAVE_IPV6
- char *p;
+ if (memchr(str, '\0', str_len)) {
+ *err = strpprintf(0, "The hostname must not contain null bytes");
+ return NULL;
+ }
+#ifdef HAVE_IPV6
if (*(str) == '[' && str_len > 1) {
/* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
- p = memchr(str + 1, ']', str_len - 2);
+ char *p = memchr(str + 1, ']', str_len - 2);
if (!p || *(p + 1) != ':') {
if (get_err) {
*err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);
--
2.50.0

107
php-cve-2025-14177.patch Normal file
View File

@ -0,0 +1,107 @@
From 6cf0fb3c48fda6a236359304e4db663ae77d858c Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+ndossche@users.noreply.github.com>
Date: Tue, 25 Nov 2025 23:11:38 +0100
Subject: [PATCH 1/5] Fix GH-20584: Information Leak of Memory
The string added had uninitialized memory due to
php_read_stream_all_chunks() not moving the buffer position, resulting
in the same data always being overwritten instead of new data being
added to the end of the buffer.
This is backport as there is a security impact as described in
GHSA-3237-qqm7-mfv7 .
(cherry picked from commit c5f28c7cf0a052f48e47877c7aa5c5bcc54f1cfc)
(cherry picked from commit ed665eb1903737d2b52b27368b155f6208604ed9)
---
ext/standard/image.c | 20 +++++++++++---
ext/standard/tests/image/gh20584.phpt | 39 +++++++++++++++++++++++++++
2 files changed, 56 insertions(+), 3 deletions(-)
create mode 100644 ext/standard/tests/image/gh20584.phpt
diff --git a/ext/standard/image.c b/ext/standard/image.c
index 4f2c5a505b4..800c5de8da3 100644
--- a/ext/standard/image.c
+++ b/ext/standard/image.c
@@ -434,8 +434,22 @@ static int php_skip_variable(php_stream * stream)
}
/* }}} */
-/* {{{ php_read_APP
- */
+static size_t php_read_stream_all_chunks(php_stream *stream, char *buffer, size_t length)
+{
+ size_t read_total = 0;
+ do {
+ ssize_t read_now = php_stream_read(stream, buffer, length - read_total);
+ read_total += read_now;
+ if (read_now < stream->chunk_size && read_total != length) {
+ return 0;
+ }
+ buffer += read_now;
+ } while (read_total < length);
+
+ return read_total;
+}
+
+/* {{{ php_read_APP */
static int php_read_APP(php_stream * stream, unsigned int marker, zval *info)
{
unsigned short length;
@@ -451,7 +465,7 @@ static int php_read_APP(php_stream * stream, unsigned int marker, zval *info)
buffer = emalloc((size_t)length);
- if (php_stream_read(stream, buffer, (size_t) length) != length) {
+ if (php_read_stream_all_chunks(stream, buffer, length) != length) {
efree(buffer);
return 0;
}
diff --git a/ext/standard/tests/image/gh20584.phpt b/ext/standard/tests/image/gh20584.phpt
new file mode 100644
index 00000000000..d117f218202
--- /dev/null
+++ b/ext/standard/tests/image/gh20584.phpt
@@ -0,0 +1,39 @@
+--TEST--
+GH-20584 (Information Leak of Memory)
+--CREDITS--
+Nikita Sveshnikov (Positive Technologies)
+--FILE--
+<?php
+// Minimal PoC: corruption/uninitialized memory leak when reading APP1 via php://filter
+$file = __DIR__ . '/gh20584.jpg';
+
+// Make APP1 large enough so it is read in multiple chunks
+$chunk = 8192;
+$tail = 123;
+$payload = str_repeat('A', $chunk) . str_repeat('B', $chunk) . str_repeat('Z',
+$tail);
+$app1Len = 2 + strlen($payload);
+
+// Minimal JPEG: SOI + APP1 + SOF0(1x1) + EOI
+$sof = "\xFF\xC0" . pack('n', 11) . "\x08" . pack('n',1) . pack('n',1) .
+"\x01\x11\x00";
+$jpeg = "\xFF\xD8" . "\xFF\xE1" . pack('n', $app1Len) . $payload . $sof .
+"\xFF\xD9";
+file_put_contents($file, $jpeg);
+
+// Read through a filter to enforce multiple reads
+$src = 'php://filter/read=string.rot13|string.rot13/resource=' . $file;
+$info = null;
+@getimagesize($src, $info);
+$exp = $payload;
+$ret = $info['APP1'];
+
+var_dump($ret === $exp);
+
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh20584.jpg');
+?>
+--EXPECT--
+bool(true)
--
2.52.0

63
php-cve-2025-14178.patch Normal file
View File

@ -0,0 +1,63 @@
From 84b83e2979bad57618528d4e669636117022f37c Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+ndossche@users.noreply.github.com>
Date: Sun, 9 Nov 2025 13:23:11 +0100
Subject: [PATCH 3/5] Fix GHSA-h96m-rvf9-jgm2
(cherry picked from commit 8b801151bd54b36aae4593ed6cfc096e8122b415)
(cherry picked from commit e4516e52979e8b67d9d35dfdbcc5dc7368263fa2)
---
ext/standard/array.c | 7 ++++++-
.../tests/array/GHSA-h96m-rvf9-jgm2.phpt | 16 ++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
create mode 100644 ext/standard/tests/array/GHSA-h96m-rvf9-jgm2.phpt
diff --git a/ext/standard/array.c b/ext/standard/array.c
index cd2e5287daf..153a4d39d15 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -3813,7 +3813,7 @@ static zend_always_inline void php_array_merge_wrapper(INTERNAL_FUNCTION_PARAMET
int argc, i;
zval *src_entry;
HashTable *src, *dest;
- uint32_t count = 0;
+ uint64_t count = 0;
ZEND_PARSE_PARAMETERS_START(0, -1)
Z_PARAM_VARIADIC('+', args, argc)
@@ -3833,6 +3833,11 @@ static zend_always_inline void php_array_merge_wrapper(INTERNAL_FUNCTION_PARAMET
count += zend_hash_num_elements(Z_ARRVAL_P(arg));
}
+ if (UNEXPECTED(count >= HT_MAX_SIZE)) {
+ zend_throw_error(NULL, "The total number of elements must be lower than %u", HT_MAX_SIZE);
+ return;
+ }
+
if (argc == 2) {
zval *ret = NULL;
diff --git a/ext/standard/tests/array/GHSA-h96m-rvf9-jgm2.phpt b/ext/standard/tests/array/GHSA-h96m-rvf9-jgm2.phpt
new file mode 100644
index 00000000000..2e3e85357e1
--- /dev/null
+++ b/ext/standard/tests/array/GHSA-h96m-rvf9-jgm2.phpt
@@ -0,0 +1,16 @@
+--TEST--
+GHSA-h96m-rvf9-jgm2
+--FILE--
+<?php
+
+$power = 20; // Chosen to be well within a memory_limit
+$arr = range(0, 2**$power);
+try {
+ array_merge(...array_fill(0, 2**(32-$power), $arr));
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+The total number of elements must be lower than %d
--
2.52.0

314
php-cve-2025-1734.patch Normal file
View File

@ -0,0 +1,314 @@
From 0b965cf85f512b1a7b87f100ac77e4aa13f7f421 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Sun, 19 Jan 2025 17:49:53 +0100
Subject: [PATCH 02/11] Fix GHSA-pcmh-g36c-qc44: http headers without colon
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The header line must contain colon otherwise it is invalid and it needs
to fail.
Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
(cherry picked from commit 0548c4c1756724a89ef8310709419b08aadb2b3b)
(cherry picked from commit e81d0cd14bfeb17e899c73e3aece4991bbda76af)
---
ext/standard/http_fopen_wrapper.c | 51 ++++++++++++++-----
ext/standard/tests/http/bug47021.phpt | 26 ++++++----
ext/standard/tests/http/bug75535.phpt | 4 +-
.../tests/http/ghsa-pcmh-g36c-qc44-001.phpt | 51 +++++++++++++++++++
.../tests/http/ghsa-pcmh-g36c-qc44-002.phpt | 51 +++++++++++++++++++
5 files changed, 156 insertions(+), 27 deletions(-)
create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 08386cfafcd..071d6a4d119 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -119,6 +119,7 @@ static zend_bool check_has_header(const char *headers, const char *header) {
typedef struct _php_stream_http_response_header_info {
php_stream_filter *transfer_encoding;
size_t file_size;
+ zend_bool error;
zend_bool follow_location;
char location[HTTP_HEADER_BLOCK_SIZE];
} php_stream_http_response_header_info;
@@ -128,6 +129,7 @@ static void php_stream_http_response_header_info_init(
{
header_info->transfer_encoding = NULL;
header_info->file_size = 0;
+ header_info->error = 0;
header_info->follow_location = 1;
header_info->location[0] = '\0';
}
@@ -165,10 +167,11 @@ static zend_bool php_stream_http_response_header_trim(char *http_header_line,
/* Process folding headers of the current line and if there are none, parse last full response
* header line. It returns NULL if the last header is finished, otherwise it returns updated
* last header line. */
-static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
- php_stream_context *context, int options, zend_string *last_header_line_str,
- char *header_line, size_t *header_line_length, int response_code,
- zval *response_header, php_stream_http_response_header_info *header_info)
+static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper,
+ php_stream *stream, php_stream_context *context, int options,
+ zend_string *last_header_line_str, char *header_line, size_t *header_line_length,
+ int response_code, zval *response_header,
+ php_stream_http_response_header_info *header_info)
{
char *last_header_line = ZSTR_VAL(last_header_line_str);
size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
@@ -211,6 +214,19 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
/* Find header separator position. */
char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
if (last_header_value) {
+ /* Verify there is no space in header name */
+ char *last_header_name = last_header_line + 1;
+ while (last_header_name < last_header_value) {
+ if (*last_header_name == ' ' || *last_header_name == '\t') {
+ header_info->error = 1;
+ php_stream_wrapper_log_error(wrapper, options,
+ "HTTP invalid response format (space in header name)!");
+ zend_string_efree(last_header_line_str);
+ return NULL;
+ }
+ ++last_header_name;
+ }
+
last_header_value++; /* Skip ':'. */
/* Strip leading whitespace. */
@@ -219,9 +235,12 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
last_header_value++;
}
} else {
- /* There is no colon. Set the value to the end of the header line, which is effectively
- * an empty string. */
- last_header_value = last_header_line_end;
+ /* There is no colon which means invalid response so error. */
+ header_info->error = 1;
+ php_stream_wrapper_log_error(wrapper, options,
+ "HTTP invalid response format (no colon in header line)!");
+ zend_string_efree(last_header_line_str);
+ return NULL;
}
zend_bool store_header = 1;
@@ -927,10 +946,16 @@ finish:
if (last_header_line_str != NULL) {
/* Parse last header line. */
- last_header_line_str = php_stream_http_response_headers_parse(stream, context,
- options, last_header_line_str, http_header_line, &http_header_line_length,
- response_code, response_header, &header_info);
- if (last_header_line_str != NULL) {
+ last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream,
+ context, options, last_header_line_str, http_header_line,
+ &http_header_line_length, response_code, response_header, &header_info);
+ if (EXPECTED(last_header_line_str == NULL)) {
+ if (UNEXPECTED(header_info.error)) {
+ php_stream_close(stream);
+ stream = NULL;
+ goto out;
+ }
+ } else {
/* Folding header present so continue. */
continue;
}
@@ -960,8 +985,8 @@ finish:
/* If the stream was closed early, we still want to process the last line to keep BC. */
if (last_header_line_str != NULL) {
- php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
- NULL, NULL, response_code, response_header, &header_info);
+ php_stream_http_response_headers_parse(wrapper, stream, context, options,
+ last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
}
if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
diff --git a/ext/standard/tests/http/bug47021.phpt b/ext/standard/tests/http/bug47021.phpt
index f3db3e1be23..a287b714c62 100644
--- a/ext/standard/tests/http/bug47021.phpt
+++ b/ext/standard/tests/http/bug47021.phpt
@@ -47,9 +47,9 @@ function do_test($num_spaces, $leave_trailing_space=false) {
];
$pid = http_server('tcp://127.0.0.1:12342', $responses);
- echo file_get_contents('http://127.0.0.1:12342/', false, $ctx);
+ echo file_get_contents('http://127.0.0.1:12342', false, $ctx);
echo "\n";
- echo file_get_contents('http://127.0.0.1:12342/', false, $ctx);
+ echo file_get_contents('http://127.0.0.1:12342', false, $ctx);
echo "\n";
http_server_kill($pid);
@@ -70,23 +70,27 @@ do_test(1, true);
echo "\n";
?>
---EXPECT--
+--EXPECTF--
+
Type='text/plain'
Hello
-Size=5
-World
+
+Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+
Type='text/plain'
Hello
-Size=5
-World
+
+Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+
Type='text/plain'
Hello
-Size=5
-World
+
+Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+
Type='text/plain'
Hello
-Size=5
-World
+
+Warning: file_get_contents(http://%s:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt
index 9bf298cc065..e3757ba4f1d 100644
--- a/ext/standard/tests/http/bug75535.phpt
+++ b/ext/standard/tests/http/bug75535.phpt
@@ -22,10 +22,8 @@ http_server_kill($pid);
==DONE==
--EXPECT--
string(0) ""
-array(2) {
+array(1) {
[0]=>
string(15) "HTTP/1.0 200 Ok"
- [1]=>
- string(14) "Content-Length"
}
==DONE==
diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
new file mode 100644
index 00000000000..53baa1c92d6
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
@@ -0,0 +1,51 @@
+--TEST--
+GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html
+
+Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+bool(false)
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(23) "Content-Type: text/html"
+}
diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
new file mode 100644
index 00000000000..5aa0ee00618
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
@@ -0,0 +1,51 @@
+--TEST--
+GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html
+
+Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP invalid response format (space in header name)! in %s
+bool(false)
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(23) "Content-Type: text/html"
+}
--
2.48.1

492
php-cve-2025-1735.patch Normal file
View File

@ -0,0 +1,492 @@
From df2ecf34256c4a301e8959fe2eed0323f8b1b57a Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 4 Mar 2025 17:23:01 +0100
Subject: [PATCH 3/4] Fix GHSA-hrwm-9436-5mv3: pgsql escaping no error checks
This adds error checks for escape function is pgsql and pdo_pgsql
extensions. It prevents possibility of storing not properly escaped
data which could potentially lead to some security issues.
(cherry picked from commit 9376aeef9f8ff81f2705b8016237ec3e30bdee44)
(cherry picked from commit 7633d987cc11ee2601223e73cfdb8b31fed5980f)
---
ext/pdo_pgsql/pgsql_driver.c | 10 +-
ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 22 ++++
ext/pgsql/pgsql.c | 129 +++++++++++++++----
ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 64 +++++++++
4 files changed, 202 insertions(+), 23 deletions(-)
create mode 100644 ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
create mode 100644 ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c
index e578bbc2720..021471cefc0 100644
--- a/ext/pdo_pgsql/pgsql_driver.c
+++ b/ext/pdo_pgsql/pgsql_driver.c
@@ -323,11 +323,15 @@ static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
unsigned char *escaped;
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
size_t tmp_len;
+ int err;
switch (paramtype) {
case PDO_PARAM_LOB:
/* escapedlen returned by PQescapeBytea() accounts for trailing 0 */
escaped = PQescapeByteaConn(H->server, (unsigned char *)unquoted, unquotedlen, &tmp_len);
+ if (escaped == NULL) {
+ return 0;
+ }
*quotedlen = tmp_len + 1;
*quoted = emalloc(*quotedlen + 1);
memcpy((*quoted)+1, escaped, *quotedlen-2);
@@ -339,7 +343,11 @@ static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu
default:
*quoted = safe_emalloc(2, unquotedlen, 3);
(*quoted)[0] = '\'';
- *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, NULL);
+ *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, &err);
+ if (err) {
+ efree(*quoted);
+ return 0;
+ }
(*quoted)[*quotedlen + 1] = '\'';
(*quoted)[*quotedlen + 2] = '\0';
*quotedlen += 2;
diff --git a/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
new file mode 100644
index 00000000000..60e13613d04
--- /dev/null
+++ b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
@@ -0,0 +1,22 @@
+--TEST--
+#GHSA-hrwm-9436-5mv3: pdo_pgsql extension does not check for errors during escaping
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
+require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+require_once dirname(__FILE__) . '/config.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+require_once dirname(__FILE__) . '/config.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+$invalid = "ABC\xff\x30';";
+var_dump($db->quote($invalid));
+
+?>
+--EXPECT--
+bool(false)
diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
index 7dcd56cf144..9e06497125c 100644
--- a/ext/pgsql/pgsql.c
+++ b/ext/pgsql/pgsql.c
@@ -4393,10 +4393,16 @@ PHP_FUNCTION(pg_escape_string)
to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0);
#ifdef HAVE_PQESCAPE_CONN
if (link) {
+ int err;
if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) {
RETURN_FALSE;
}
- ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL);
+ ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), &err);
+ if (err) {
+ zend_throw_exception(zend_ce_exception, "Escaping string failed", 0);
+ zend_string_efree(to);
+ return;
+ }
} else
#endif
{
@@ -4445,6 +4451,10 @@ PHP_FUNCTION(pg_escape_bytea)
} else
#endif
to = (char *)PQescapeBytea((unsigned char*)from, from_len, &to_len);
+ if (to == NULL) {
+ zend_throw_exception(zend_ce_exception, "Escape failure", 0);
+ return;
+ }
RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */
PQfreemem(to);
@@ -5529,7 +5539,7 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
char *escaped;
smart_str querystr = {0};
size_t new_len;
- int i, num_rows;
+ int i, num_rows, err;
zval elem;
if (!*table_name) {
@@ -5570,7 +5580,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
"WHERE a.attnum > 0 AND c.relname = '");
}
escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1);
- new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL);
+ new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), &err);
+ if (err) {
+ php_error_docref(NULL, E_WARNING, "Escaping table name '%s' failed", table_name);
+ efree(src);
+ efree(escaped);
+ smart_str_free(&querystr);
+ return FAILURE;
+ }
if (new_len) {
smart_str_appendl(&querystr, escaped, new_len);
}
@@ -5578,7 +5595,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z
smart_str_appends(&querystr, "' AND n.nspname = '");
escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1);
- new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL);
+ new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), &err);
+ if (err) {
+ php_error_docref(NULL, E_WARNING, "Escaping table namespace '%s' failed", table_name);
+ efree(src);
+ efree(escaped);
+ smart_str_free(&querystr);
+ return FAILURE;
+ }
if (new_len) {
smart_str_appendl(&querystr, escaped, new_len);
}
@@ -5850,7 +5874,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
{
zend_string *field = NULL;
zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val;
- int err = 0, skip_field;
+ int err = 0, escape_err = 0, skip_field;
php_pgsql_data_type data_type;
assert(pg_link != NULL);
@@ -6101,10 +6125,14 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
/* PostgreSQL ignores \0 */
str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0);
/* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */
- ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
- str = zend_string_truncate(str, ZSTR_LEN(str), 0);
- ZVAL_NEW_STR(&new_val, str);
- php_pgsql_add_quotes(&new_val, 1);
+ ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err);
+ if (escape_err) {
+ err = 1;
+ } else {
+ str = zend_string_truncate(str, ZSTR_LEN(str), 0);
+ ZVAL_NEW_STR(&new_val, str);
+ php_pgsql_add_quotes(&new_val, 1);
+ }
}
break;
@@ -6126,7 +6154,15 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
}
PGSQL_CONV_CHECK_IGNORE();
if (err) {
- php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field));
+ if (escape_err) {
+ php_error_docref(NULL, E_NOTICE,
+ "String value escaping failed for PostgreSQL '%s' (%s)",
+ Z_STRVAL_P(type), ZSTR_VAL(field));
+ } else {
+ php_error_docref(NULL, E_NOTICE,
+ "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)",
+ Z_STRVAL_P(type), ZSTR_VAL(field));
+ }
}
break;
@@ -6406,6 +6442,11 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
#else
tmp = PQescapeBytea(Z_STRVAL_P(val), (unsigned char *)Z_STRLEN_P(val), &to_len);
#endif
+ if (tmp == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Escaping value failed for %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field));
+ err = 1;
+ break;
+ }
ZVAL_STRINGL(&new_val, (char *)tmp, to_len - 1); /* PQescapeBytea's to_len includes additional '\0' */
PQfreemem(tmp);
php_pgsql_add_quotes(&new_val, 1);
@@ -6488,6 +6529,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con
zend_hash_update(Z_ARRVAL_P(result), field, &new_val);
} else {
char *escaped = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field));
+ if (escaped == NULL) {
+ /* This cannot fail because of invalid string but only due to failed memory allocation */
+ php_error_docref(NULL, E_NOTICE, "Escaping field '%s' failed", ZSTR_VAL(field));
+ err = 1;
+ break;
+ }
add_assoc_zval(result, escaped, &new_val);
PGSQLfree(escaped);
}
@@ -6566,7 +6613,7 @@ static int do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link,
}
/* }}} */
-static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
+static inline int build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
{
size_t table_len = strlen(table);
@@ -6577,6 +6624,10 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const c
smart_str_appendl(querystr, table, len);
} else {
char *escaped = PGSQLescapeIdentifier(pg_link, table, len);
+ if (escaped == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table);
+ return FAILURE;
+ }
smart_str_appends(querystr, escaped);
PGSQLfree(escaped);
}
@@ -6589,11 +6640,17 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const c
smart_str_appendl(querystr, after_dot, len);
} else {
char *escaped = PGSQLescapeIdentifier(pg_link, after_dot, len);
+ if (escaped == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table);
+ return FAILURE;
+ }
smart_str_appendc(querystr, '.');
smart_str_appends(querystr, escaped);
PGSQLfree(escaped);
}
}
+
+ return SUCCESS;
}
/* }}} */
@@ -6615,7 +6672,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
ZVAL_UNDEF(&converted);
if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) {
smart_str_appends(&querystr, "INSERT INTO ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " DEFAULT VALUES");
goto no_values;
@@ -6631,7 +6690,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
}
smart_str_appends(&querystr, "INSERT INTO ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " (");
ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) {
@@ -6641,6 +6702,10 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
}
if (opt & PGSQL_DML_ESCAPE) {
tmp = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1);
+ if (tmp == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld));
+ goto cleanup;
+ }
smart_str_appends(&querystr, tmp);
PGSQLfree(tmp);
} else {
@@ -6652,15 +6717,19 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var
smart_str_appends(&querystr, ") VALUES (");
/* make values string */
- ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) {
+ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(var_array), fld, val) {
/* we can avoid the key_type check here, because we tested it in the other loop */
switch (Z_TYPE_P(val)) {
case IS_STRING:
if (opt & PGSQL_DML_ESCAPE) {
- size_t new_len;
- char *tmp;
- tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1);
- new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
+ int error;
+ char *tmp = safe_emalloc(Z_STRLEN_P(val), 2, 1);
+ size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error);
+ if (error) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld));
+ efree(tmp);
+ goto cleanup;
+ }
smart_str_appendc(&querystr, '\'');
smart_str_appendl(&querystr, tmp, new_len);
smart_str_appendc(&querystr, '\'');
@@ -6810,6 +6879,10 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr,
}
if (opt & PGSQL_DML_ESCAPE) {
char *tmp = PGSQLescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1);
+ if (tmp == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld));
+ return -1;
+ }
smart_str_appends(querystr, tmp);
PGSQLfree(tmp);
} else {
@@ -6824,8 +6897,14 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr,
switch (Z_TYPE_P(val)) {
case IS_STRING:
if (opt & PGSQL_DML_ESCAPE) {
+ int error;
char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1);
- size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
+ size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error);
+ if (error) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld));
+ efree(tmp);
+ return -1;
+ }
smart_str_appendc(querystr, '\'');
smart_str_appendl(querystr, tmp, new_len);
smart_str_appendc(querystr, '\'');
@@ -6894,7 +6973,9 @@ PHP_PGSQL_API int php_pgsql_update(PGconn *pg_link, const char *table, zval *var
}
smart_str_appends(&querystr, "UPDATE ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " SET ");
if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt))
@@ -6992,7 +7073,9 @@ PHP_PGSQL_API int php_pgsql_delete(PGconn *pg_link, const char *table, zval *ids
}
smart_str_appends(&querystr, "DELETE FROM ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " WHERE ");
if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt))
@@ -7130,7 +7213,9 @@ PHP_PGSQL_API int php_pgsql_result2array(PGresult *pg_result, zval *ret_array, l
}
smart_str_appends(&querystr, "SELECT * FROM ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " WHERE ");
if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt))
diff --git a/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
new file mode 100644
index 00000000000..c1c5e05dce6
--- /dev/null
+++ b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
@@ -0,0 +1,64 @@
+--TEST--
+#GHSA-hrwm-9436-5mv3: pgsql extension does not check for errors during escaping
+--EXTENSIONS--
+pgsql
+--SKIPIF--
+<?php include("skipif.inc"); ?>
+--FILE--
+<?php
+
+include 'config.inc';
+define('FILE_NAME', __DIR__ . '/php.gif');
+
+$db = pg_connect($conn_str);
+pg_query($db, "DROP TABLE IF EXISTS ghsa_hrmw_9436_5mv3");
+pg_query($db, "CREATE TABLE ghsa_hrmw_9436_5mv3 (bar text);");
+
+// pg_escape_literal/pg_escape_identifier
+
+$invalid = "ABC\xff\x30';";
+$flags = PGSQL_DML_NO_CONV | PGSQL_DML_ESCAPE;
+
+var_dump(pg_insert($db, $invalid, ['bar' => 'test'])); // table name str escape in php_pgsql_meta_data
+var_dump(pg_insert($db, "$invalid.tbl", ['bar' => 'test'])); // schema name str escape in php_pgsql_meta_data
+var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid])); // converted value str escape in php_pgsql_convert
+var_dump(pg_insert($db, $invalid, [])); // ident escape in build_tablename
+var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', [$invalid => 'foo'], $flags)); // ident escape for field php_pgsql_insert
+var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid], $flags)); // str escape for field value in php_pgsql_insert
+var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], [$invalid => 'test'], $flags)); // ident escape in build_assignment_string
+var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], ['bar' => $invalid], $flags)); // invalid str escape in build_assignment_string
+var_dump(pg_escape_literal($db, $invalid)); // pg_escape_literal escape
+var_dump(pg_escape_identifier($db, $invalid)); // pg_escape_identifier escape
+
+?>
+--EXPECTF--
+
+Warning: pg_insert(): Escaping table name 'ABC%s';' failed in %s on line %d
+bool(false)
+
+Warning: pg_insert(): Escaping table namespace 'ABC%s';.tbl' failed in %s on line %d
+bool(false)
+
+Notice: pg_insert(): String value escaping failed for PostgreSQL 'text' (bar) in %s on line %d
+bool(false)
+
+Notice: pg_insert(): Failed to escape table name 'ABC%s';' in %s on line %d
+bool(false)
+
+Notice: pg_insert(): Failed to escape field 'ABC%s';' in %s on line %d
+bool(false)
+
+Notice: pg_insert(): Failed to escape field 'bar' value in %s on line %d
+bool(false)
+
+Notice: pg_update(): Failed to escape field 'ABC%s';' in %s on line %d
+bool(false)
+
+Notice: pg_update(): Failed to escape field 'bar' value in %s on line %d
+bool(false)
+
+Warning: pg_escape_literal(): Failed to escape in %s on line %d
+bool(false)
+
+Warning: pg_escape_identifier(): Failed to escape in %s on line %d
+bool(false)
--
2.50.0
From d52bcc1e66edd421dfea1698b1f897ad26c5f15f Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Thu, 3 Jul 2025 09:32:25 +0200
Subject: [PATCH 4/4] NEWS
(cherry picked from commit 970548b94b7f23be32154d05a9545b10c98bfd62)
---
NEWS | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/NEWS b/NEWS
index fda646c7010..a9dd716c003 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,20 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+Backported from 8.1.33
+
+- PGSQL:
+ . Fixed GHSA-hrwm-9436-5mv3 (pgsql extension does not check for errors during
+ escaping). (CVE-2025-1735) (Jakub Zelenka)
+
+- SOAP:
+ . Fixed GHSA-453j-q27h-5p8x (NULL Pointer Dereference in PHP SOAP Extension
+ via Large XML Namespace Prefix). (CVE-2025-6491) (Lekssays, nielsdos)
+
+- Standard:
+ . Fixed GHSA-3cr5-j632-f35r (Null byte termination in hostnames).
+ (CVE-2025-1220) (Jakub Zelenka)
+
Backported from 8.1.32
- LibXML:
--
2.50.0

242
php-cve-2025-1736.patch Normal file
View File

@ -0,0 +1,242 @@
From 134f821622e2d2b68d66bea16e16c05b7b0f5114 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Fri, 14 Feb 2025 19:17:22 +0100
Subject: [PATCH 04/11] Fix GHSA-hgf5-96fm-v528: http user header check of crlf
(cherry picked from commit 41d49abbd99dab06cdae4834db664435f8177174)
(cherry picked from commit 8f65ef50929f6781f4973325f9b619f02cce19d8)
---
ext/standard/http_fopen_wrapper.c | 2 +-
.../tests/http/ghsa-hgf5-96fm-v528-001.phpt | 65 +++++++++++++++++++
.../tests/http/ghsa-hgf5-96fm-v528-002.phpt | 62 ++++++++++++++++++
.../tests/http/ghsa-hgf5-96fm-v528-003.phpt | 64 ++++++++++++++++++
4 files changed, 192 insertions(+), 1 deletion(-)
create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index b64a7c95446..46f7c7ebcee 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -109,7 +109,7 @@ static inline void strip_header(char *header_bag, char *lc_header_bag,
static zend_bool check_has_header(const char *headers, const char *header) {
const char *s = headers;
while ((s = strstr(s, header))) {
- if (s == headers || *(s-1) == '\n') {
+ if (s == headers || (*(s-1) == '\n' && *(s-2) == '\r')) {
return 1;
}
s++;
diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
new file mode 100644
index 00000000000..c8dcd47a4a4
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
@@ -0,0 +1,65 @@
+--TEST--
+GHSA-hgf5-96fm-v528: Stream HTTP wrapper header check might omit basic auth header (incorrect inside pos)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ $result = fread($conn, 1024);
+ $encoded_result = base64_encode($result);
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
+
+CODE;
+
+$clientCode = <<<'CODE'
+ $opts = [
+ "http" => [
+ "method" => "GET",
+ "header" => "Cookie: foo=bar\nauthorization:x\r\n"
+ ]
+ ];
+ $ctx = stream_context_create($opts);
+ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+array(7) {
+ [0]=>
+ string(14) "GET / HTTP/1.%d"
+ [1]=>
+ string(33) "Authorization: Basic dXNlcjpwd2Q="
+ [2]=>
+ string(21) "Host: 127.0.0.1:%d"
+ [3]=>
+ string(17) "Connection: close"
+ [4]=>
+ string(31) "Cookie: foo=bar
+authorization:x"
+ [5]=>
+ string(0) ""
+ [6]=>
+ string(0) ""
+}
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(38) "Content-Type: text/html; charset=utf-8"
+}
diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
new file mode 100644
index 00000000000..ca8f75f0327
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
@@ -0,0 +1,62 @@
+--TEST--
+GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct start pos)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ $result = fread($conn, 1024);
+ $encoded_result = base64_encode($result);
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
+
+CODE;
+
+$clientCode = <<<'CODE'
+ $opts = [
+ "http" => [
+ "method" => "GET",
+ "header" => "Authorization: Bearer x\r\n"
+ ]
+ ];
+ $ctx = stream_context_create($opts);
+ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+array(6) {
+ [0]=>
+ string(14) "GET / HTTP/1.%d"
+ [1]=>
+ string(21) "Host: 127.0.0.1:%d"
+ [2]=>
+ string(17) "Connection: close"
+ [3]=>
+ string(23) "Authorization: Bearer x"
+ [4]=>
+ string(0) ""
+ [5]=>
+ string(0) ""
+}
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(38) "Content-Type: text/html; charset=utf-8"
+}
diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
new file mode 100644
index 00000000000..4cfbc7ee804
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
@@ -0,0 +1,64 @@
+--TEST--
+GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct middle pos)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ $result = fread($conn, 1024);
+ $encoded_result = base64_encode($result);
+
+ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
+
+CODE;
+
+$clientCode = <<<'CODE'
+ $opts = [
+ "http" => [
+ "method" => "GET",
+ "header" => "Cookie: x=y\r\nAuthorization: Bearer x\r\n"
+ ]
+ ];
+ $ctx = stream_context_create($opts);
+ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+array(7) {
+ [0]=>
+ string(14) "GET / HTTP/1.%d"
+ [1]=>
+ string(21) "Host: 127.0.0.1:%d"
+ [2]=>
+ string(17) "Connection: close"
+ [3]=>
+ string(11) "Cookie: x=y"
+ [4]=>
+ string(23) "Authorization: Bearer x"
+ [5]=>
+ string(0) ""
+ [6]=>
+ string(0) ""
+}
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+ [1]=>
+ string(38) "Content-Type: text/html; charset=utf-8"
+}
--
2.48.1

349
php-cve-2025-1861.patch Normal file
View File

@ -0,0 +1,349 @@
From 5418040dcaaca46965ed6f8a4ad1541709c32e9f Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 4 Mar 2025 09:01:34 +0100
Subject: [PATCH 03/11] Fix GHSA-52jp-hrpf-2jff: http redirect location
truncation
It converts the allocation of location to be on heap instead of stack
and errors if the location length is greater than 8086 bytes.
(cherry picked from commit ac1a054bb3eb5994a199e8b18cca28cbabf5943e)
(cherry picked from commit adc7e9f20c9a9aab9cd23ca47ec3fb96287898ae)
---
ext/standard/http_fopen_wrapper.c | 87 ++++++++++++-------
.../tests/http/ghsa-52jp-hrpf-2jff-001.phpt | 58 +++++++++++++
.../tests/http/ghsa-52jp-hrpf-2jff-002.phpt | 55 ++++++++++++
3 files changed, 168 insertions(+), 32 deletions(-)
create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 071d6a4d119..b64a7c95446 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -69,15 +69,16 @@
#include "php_fopen_wrappers.h"
-#define HTTP_HEADER_BLOCK_SIZE 1024
-#define PHP_URL_REDIRECT_MAX 20
-#define HTTP_HEADER_USER_AGENT 1
-#define HTTP_HEADER_HOST 2
-#define HTTP_HEADER_AUTH 4
-#define HTTP_HEADER_FROM 8
-#define HTTP_HEADER_CONTENT_LENGTH 16
-#define HTTP_HEADER_TYPE 32
-#define HTTP_HEADER_CONNECTION 64
+#define HTTP_HEADER_BLOCK_SIZE 1024
+#define HTTP_HEADER_MAX_LOCATION_SIZE 8182 /* 8192 - 10 (size of "Location: ") */
+#define PHP_URL_REDIRECT_MAX 20
+#define HTTP_HEADER_USER_AGENT 1
+#define HTTP_HEADER_HOST 2
+#define HTTP_HEADER_AUTH 4
+#define HTTP_HEADER_FROM 8
+#define HTTP_HEADER_CONTENT_LENGTH 16
+#define HTTP_HEADER_TYPE 32
+#define HTTP_HEADER_CONNECTION 64
#define HTTP_WRAPPER_HEADER_INIT 1
#define HTTP_WRAPPER_REDIRECTED 2
@@ -121,17 +122,15 @@ typedef struct _php_stream_http_response_header_info {
size_t file_size;
zend_bool error;
zend_bool follow_location;
- char location[HTTP_HEADER_BLOCK_SIZE];
+ char *location;
+ size_t location_len;
} php_stream_http_response_header_info;
static void php_stream_http_response_header_info_init(
php_stream_http_response_header_info *header_info)
{
- header_info->transfer_encoding = NULL;
- header_info->file_size = 0;
- header_info->error = 0;
+ memset(header_info, 0, sizeof(php_stream_http_response_header_info));
header_info->follow_location = 1;
- header_info->location[0] = '\0';
}
/* Trim white spaces from response header line and update its length */
@@ -258,7 +257,22 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w
* RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
header_info->follow_location = 0;
}
- strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
+ size_t last_header_value_len = strlen(last_header_value);
+ if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) {
+ header_info->error = 1;
+ php_stream_wrapper_log_error(wrapper, options,
+ "HTTP Location header size is over the limit of %d bytes",
+ HTTP_HEADER_MAX_LOCATION_SIZE);
+ zend_string_efree(last_header_line_str);
+ return NULL;
+ }
+ if (header_info->location_len == 0) {
+ header_info->location = emalloc(last_header_value_len + 1);
+ } else if (header_info->location_len <= last_header_value_len) {
+ header_info->location = erealloc(header_info->location, last_header_value_len + 1);
+ }
+ header_info->location_len = last_header_value_len;
+ memcpy(header_info->location, last_header_value, last_header_value_len + 1);
} else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
} else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
@@ -541,6 +555,8 @@ finish:
}
}
+ php_stream_http_response_header_info_init(&header_info);
+
if (stream == NULL)
goto out;
@@ -918,8 +934,6 @@ finish:
}
}
- php_stream_http_response_header_info_init(&header_info);
-
/* read past HTTP headers */
while (!php_stream_eof(stream)) {
size_t http_header_line_length;
@@ -989,12 +1003,12 @@ finish:
last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
}
- if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+ if (!reqok || (header_info.location != NULL && header_info.follow_location)) {
if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
goto out;
}
- if (header_info.location[0] != '\0')
+ if (header_info.location != NULL)
php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
php_stream_close(stream);
@@ -1005,18 +1019,17 @@ finish:
header_info.transfer_encoding = NULL;
}
- if (header_info.location[0] != '\0') {
+ if (header_info.location != NULL) {
- char new_path[HTTP_HEADER_BLOCK_SIZE];
- char loc_path[HTTP_HEADER_BLOCK_SIZE];
+ char *new_path = NULL;
- *new_path='\0';
if (strlen(header_info.location) < 8 ||
(strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
{
+ char *loc_path = NULL;
if (*header_info.location != '/') {
if (*(header_info.location+1) != '\0' && resource->path) {
char *s = strrchr(ZSTR_VAL(resource->path), '/');
@@ -1034,31 +1047,35 @@ finish:
if (resource->path &&
ZSTR_VAL(resource->path)[0] == '/' &&
ZSTR_VAL(resource->path)[1] == '\0') {
- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
- ZSTR_VAL(resource->path), header_info.location);
+ spprintf(&loc_path, 0, "%s%s", ZSTR_VAL(resource->path), header_info.location);
} else {
- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
- ZSTR_VAL(resource->path), header_info.location);
+ spprintf(&loc_path, 0, "%s/%s", ZSTR_VAL(resource->path), header_info.location);
}
} else {
- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
+ spprintf(&loc_path, 0, "/%s", header_info.location);
}
} else {
- strlcpy(loc_path, header_info.location, sizeof(loc_path));
+ loc_path = header_info.location;
+ header_info.location = NULL;
}
if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
- snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
+ spprintf(&new_path, 0, "%s://%s:%d%s", ZSTR_VAL(resource->scheme),
+ ZSTR_VAL(resource->host), resource->port, loc_path);
} else {
- snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
+ spprintf(&new_path, 0, "%s://%s%s", ZSTR_VAL(resource->scheme),
+ ZSTR_VAL(resource->host), loc_path);
}
+ efree(loc_path);
} else {
- strlcpy(new_path, header_info.location, sizeof(new_path));
+ new_path = header_info.location;
+ header_info.location = NULL;
}
php_url_free(resource);
/* check for invalid redirection URLs */
if ((resource = php_url_parse(new_path)) == NULL) {
php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path);
+ efree(new_path);
goto out;
}
@@ -1070,6 +1087,7 @@ finish:
while (s < e) { \
if (iscntrl(*s)) { \
php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \
+ efree(new_path); \
goto out; \
} \
s++; \
@@ -1085,6 +1103,7 @@ finish:
stream = php_stream_url_wrap_http_ex(
wrapper, new_path, mode, options, opened_path, context,
--redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC);
+ efree(new_path);
} else {
php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
}
@@ -1097,6 +1116,10 @@ out:
efree(http_header_line);
}
+ if (header_info.location != NULL) {
+ efree(header_info.location);
+ }
+
if (resource) {
php_url_free(resource);
}
diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
new file mode 100644
index 00000000000..46d77ec4aff
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
@@ -0,0 +1,58 @@
+--TEST--
+GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (success)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+$ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ $loc = str_repeat("y", 8000);
+ fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ case STREAM_NOTIFY_REDIRECTED:
+ echo "Redirected: ";
+ var_dump($message);
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html;
+Redirected: string(8000) "%s"
+
+Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: %s
+string(0) ""
+array(3) {
+ [0]=>
+ string(15) "HTTP/1.0 301 Ok"
+ [1]=>
+ string(24) "Content-Type: text/html;"
+ [2]=>
+ string(8010) "Location: %s"
+}
diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
new file mode 100644
index 00000000000..d25c89d06e5
--- /dev/null
+++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
@@ -0,0 +1,55 @@
+--TEST--
+GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (over limit)
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+$ctxt = stream_context_create([
+ "socket" => [
+ "tcp_nodelay" => true
+ ]
+ ]);
+
+ $server = stream_socket_server(
+ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
+ phpt_notify_server_start($server);
+
+ $conn = stream_socket_accept($server);
+
+ phpt_notify(WORKER_DEFAULT_NAME, "server-accepted");
+
+ $loc = str_repeat("y", 9000);
+ fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
+ switch($notification_code) {
+ case STREAM_NOTIFY_MIME_TYPE_IS:
+ echo "Found the mime-type: ", $message, PHP_EOL;
+ break;
+ case STREAM_NOTIFY_REDIRECTED:
+ echo "Redirected: ";
+ var_dump($message);
+ }
+ }
+
+ $ctx = stream_context_create();
+ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
+ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
+ var_dump($http_response_header);
+CODE;
+
+include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+?>
+--EXPECTF--
+Found the mime-type: text/html;
+
+Warning: file_get_contents(http://127.0.0.1:%d): failed to open stream: HTTP Location header size is over the limit of 8182 bytes in %s
+string(0) ""
+array(2) {
+ [0]=>
+ string(15) "HTTP/1.0 301 Ok"
+ [1]=>
+ string(24) "Content-Type: text/html;"
+}
--
2.48.1

103
php-cve-2025-6491.patch Normal file
View File

@ -0,0 +1,103 @@
From c13a3b2a3710c66231f0cad16ff74ef75c8672a7 Mon Sep 17 00:00:00 2001
From: Ahmed Lekssays <lekssaysahmed@gmail.com>
Date: Tue, 3 Jun 2025 09:00:55 +0000
Subject: [PATCH 1/4] Fix GHSA-453j-q27h-5p8x
Libxml versions prior to 2.13 cannot correctly handle a call to
xmlNodeSetName() with a name longer than 2G. It will leave the node
object in an invalid state with a NULL name. This later causes a NULL
pointer dereference when using the name during message serialization.
To solve this, implement a workaround that resets the name to the
sentinel name if this situation arises.
Versions of libxml of 2.13 and higher are not affected.
This can be exploited if a SoapVar is created with a fully qualified
name that is longer than 2G. This would be possible if some application
code uses a namespace prefix from an untrusted source like from a remote
SOAP service.
Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
(cherry picked from commit 9cb3d8d200f0c822b17bda35a2a67a97b039d3e1)
(cherry picked from commit 1b7410a57f8a5fd1dd43854bcf7b9200517c9fd2)
---
ext/soap/soap.c | 6 ++--
ext/soap/tests/soap_qname_crash.phpt | 48 ++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 2 deletions(-)
create mode 100644 ext/soap/tests/soap_qname_crash.phpt
diff --git a/ext/soap/soap.c b/ext/soap/soap.c
index 7429aebbf70..94f1db526c6 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -4457,8 +4457,10 @@ static xmlNodePtr serialize_zval(zval *val, sdlParamPtr param, char *paramName,
}
xmlParam = master_to_xml(enc, val, style, parent);
zval_ptr_dtor(&defval);
- if (!strcmp((char*)xmlParam->name, "BOGUS")) {
- xmlNodeSetName(xmlParam, BAD_CAST(paramName));
+ if (xmlParam != NULL) {
+ if (xmlParam->name == NULL || strcmp((char*)xmlParam->name, "BOGUS") == 0) {
+ xmlNodeSetName(xmlParam, BAD_CAST(paramName));
+ }
}
return xmlParam;
}
diff --git a/ext/soap/tests/soap_qname_crash.phpt b/ext/soap/tests/soap_qname_crash.phpt
new file mode 100644
index 00000000000..7a1bf026022
--- /dev/null
+++ b/ext/soap/tests/soap_qname_crash.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Test SoapClient with excessively large QName prefix in SoapVar
+--EXTENSIONS--
+soap
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE != 8) die("skip: 64-bit only");
+?>
+--INI--
+memory_limit=8G
+--FILE--
+<?php
+
+class TestSoapClient extends SoapClient {
+ public function __doRequest(
+ $request,
+ $location,
+ $action,
+ $version,
+ $one_way = false
+ ): ?string {
+ die($request);
+ }
+}
+
+$prefix = str_repeat("A", 2 * 1024 * 1024 * 1024);
+$qname = "{$prefix}:tag";
+
+echo "Attempting to create SoapVar with very large QName\n";
+
+$var = new SoapVar("value", XSD_QNAME, null, null, $qname);
+
+echo "Attempting encoding\n";
+
+$options = [
+ 'location' => 'http://127.0.0.1/',
+ 'uri' => 'urn:dummy',
+ 'trace' => 1,
+ 'exceptions' => true,
+];
+$client = new TestSoapClient(null, $options);
+$client->__soapCall("DummyFunction", [$var]);
+?>
+--EXPECT--
+Attempting to create SoapVar with very large QName
+Attempting encoding
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:dummy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:DummyFunction><param0 xsi:type="xsd:QName">value</param0></ns1:DummyFunction></SOAP-ENV:Body></SOAP-ENV:Envelope>
--
2.50.0

View File

@ -0,0 +1,135 @@
From 56488a8a4ec68e58eecc9e78dd75e41adf56984c Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 9 Nov 2024 15:29:52 +0100
Subject: [PATCH 6/7] Fix GHSA-4w77-75f9-2c8w
(cherry picked from commit 7dd336ae838bbf2c62dc47e3c900d657d3534c02)
(cherry picked from commit 462092a48aa0dbad24d9fa8a4a9d418faa14d309)
---
sapi/cli/php_cli_server.c | 6 +---
sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt | 41 +++++++++++++++++++++++++
2 files changed, 42 insertions(+), 5 deletions(-)
create mode 100644 sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt
diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c
index c3097861e3f..8717dc57418 100644
--- a/sapi/cli/php_cli_server.c
+++ b/sapi/cli/php_cli_server.c
@@ -1923,8 +1923,6 @@ static size_t php_cli_server_client_send_through(php_cli_server_client *client,
static void php_cli_server_client_populate_request_info(const php_cli_server_client *client, sapi_request_info *request_info) /* {{{ */
{
- char *val;
-
request_info->request_method = php_http_method_str(client->request.request_method);
request_info->proto_num = client->request.protocol_version;
request_info->request_uri = client->request.request_uri;
@@ -1932,9 +1930,7 @@ static void php_cli_server_client_populate_request_info(const php_cli_server_cli
request_info->query_string = client->request.query_string;
request_info->content_length = client->request.content_len;
request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL;
- if (NULL != (val = zend_hash_str_find_ptr(&client->request.headers, "content-type", sizeof("content-type")-1))) {
- request_info->content_type = val;
- }
+ request_info->content_type = zend_hash_str_find_ptr(&client->request.headers, "content-type", sizeof("content-type")-1);
} /* }}} */
static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
diff --git a/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt
new file mode 100644
index 00000000000..80944c3d14f
--- /dev/null
+++ b/sapi/cli/tests/ghsa-4w77-75f9-2c8w.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface)
+--INI--
+allow_url_fopen=1
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+include "php_cli_server.inc";
+
+$serverCode = <<<'CODE'
+var_dump(file_get_contents('php://input'));
+CODE;
+
+php_cli_server_start($serverCode, null, []);
+
+$options = [
+ "http" => [
+ "method" => "POST",
+ "header" => "Content-Type: application/x-www-form-urlencoded",
+ "content" => "AAAAA",
+ ],
+];
+$context = stream_context_create($options);
+
+echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", false, $context);
+
+$options = [
+ "http" => [
+ "method" => "POST",
+ ],
+];
+$context = stream_context_create($options);
+
+echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", false, $context);
+?>
+--EXPECT--
+string(5) "AAAAA"
+string(0) ""
--
2.47.0
From d8d682d3d6a4d027771806c8fc77128cae078d29 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Fri, 22 Nov 2024 08:58:10 +0100
Subject: [PATCH 7/7] NEWS for 8.1.31 backports
(cherry picked from commit 22bdb43da0ecd6e72d63b63aa6c1f3a25d1bca3a)
---
NEWS | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/NEWS b/NEWS
index 62616d6312d..f600d6aea65 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,30 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+Backported from 8.1.31
+
+- CLI:
+ . Fixed bug GHSA-4w77-75f9-2c8w (Heap-Use-After-Free in sapi_read_post_data
+ Processing in CLI SAPI Interface). (nielsdos)
+
+- LDAP:
+ . Fixed bug GHSA-g665-fm4p-vhff (OOB access in ldap_escape). (CVE-2024-8932)
+ (nielsdos)
+
+- PDO DBLIB:
+ . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing
+ OOB writes). (CVE-2024-11236) (nielsdos)
+
+- PDO Firebird:
+ . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the firebird quoter
+ causing OOB writes). (CVE-2024-11236) (nielsdos)
+
+- Streams:
+ . Fixed bug GHSA-c5f2-jwm7-mmq2 (Configuring a proxy in a stream context
+ might allow for CRLF injection in URIs). (CVE-2024-11234) (Jakub Zelenka)
+ . Fixed bug GHSA-r977-prxv-hc43 (Single byte overread with
+ convert.quoted-printable-decode filter). (CVE-2024-11233) (nielsdos)
+
Backported from 8.1.30
- CGI:
--
2.47.0

View File

@ -0,0 +1,256 @@
From 3e9d47f6cc04c9978bb384e2d487cf28d37889f0 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 6 Sep 2025 21:55:13 +0200
Subject: [PATCH 4/5] Fix GHSA-www2-q4fc-65wf
(cherry picked from commit ed70b1ea43a9b7ffa2f53b3e5d6ba403f37ae81c)
(cherry picked from commit 52c5762a902e8731b7068ded027fbd780f5a1991)
---
ext/standard/basic_functions.c | 12 ++--
ext/standard/dns.c | 6 +-
ext/standard/dns_win32.c | 6 +-
.../tests/network/ghsa-www2-q4fc-65wf.phpt | 71 +++++++++++++++++++
ext/standard/tests/network/ip_x86_64.phpt | 2 +-
5 files changed, 84 insertions(+), 13 deletions(-)
create mode 100644 ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index 64f27ef5af7..45746335689 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -3960,7 +3960,7 @@ PHP_NAMED_FUNCTION(php_inet_pton)
char buffer[17];
ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STRING(address, address_len)
+ Z_PARAM_PATH(address, address_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
memset(buffer, 0, sizeof(buffer));
@@ -3998,7 +3998,7 @@ PHP_FUNCTION(ip2long)
#endif
ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STRING(addr, addr_len)
+ Z_PARAM_PATH(addr, addr_len)
ZEND_PARSE_PARAMETERS_END();
#ifdef HAVE_INET_PTON
@@ -5714,8 +5714,8 @@ PHP_FUNCTION(getservbyname)
struct servent *serv;
ZEND_PARSE_PARAMETERS_START(2, 2)
- Z_PARAM_STRING(name, name_len)
- Z_PARAM_STRING(proto, proto_len)
+ Z_PARAM_PATH(name, name_len)
+ Z_PARAM_PATH(proto, proto_len)
ZEND_PARSE_PARAMETERS_END();
@@ -5759,7 +5759,7 @@ PHP_FUNCTION(getservbyport)
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_LONG(port)
- Z_PARAM_STRING(proto, proto_len)
+ Z_PARAM_PATH(proto, proto_len)
ZEND_PARSE_PARAMETERS_END();
serv = getservbyport(htons((unsigned short) port), proto);
@@ -5783,7 +5783,7 @@ PHP_FUNCTION(getprotobyname)
struct protoent *ent;
ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STRING(name, name_len)
+ Z_PARAM_PATH(name, name_len)
ZEND_PARSE_PARAMETERS_END();
ent = getprotobyname(name);
diff --git a/ext/standard/dns.c b/ext/standard/dns.c
index dc85c45e1d7..698ad4f661d 100644
--- a/ext/standard/dns.c
+++ b/ext/standard/dns.c
@@ -377,7 +377,7 @@ PHP_FUNCTION(dns_check_record)
#endif
ZEND_PARSE_PARAMETERS_START(1, 2)
- Z_PARAM_STRING(hostname, hostname_len)
+ Z_PARAM_PATH(hostname, hostname_len)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(rectype, rectype_len)
ZEND_PARSE_PARAMETERS_END();
@@ -825,7 +825,7 @@ PHP_FUNCTION(dns_get_record)
zend_bool raw = 0;
ZEND_PARSE_PARAMETERS_START(1, 5)
- Z_PARAM_STRING(hostname, hostname_len)
+ Z_PARAM_PATH(hostname, hostname_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(type_param)
Z_PARAM_ZVAL(authns)
@@ -1065,7 +1065,7 @@ PHP_FUNCTION(dns_get_mx)
#endif
ZEND_PARSE_PARAMETERS_START(2, 3)
- Z_PARAM_STRING(hostname, hostname_len)
+ Z_PARAM_PATH(hostname, hostname_len)
Z_PARAM_ZVAL(mx_list)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(weight_list)
diff --git a/ext/standard/dns_win32.c b/ext/standard/dns_win32.c
index 466d927ea3e..f5b6e03a128 100644
--- a/ext/standard/dns_win32.c
+++ b/ext/standard/dns_win32.c
@@ -50,7 +50,7 @@ PHP_FUNCTION(dns_get_mx) /* {{{ */
DNS_STATUS status; /* Return value of DnsQuery_A() function */
PDNS_RECORD pResult, pRec; /* Pointer to DNS_RECORD structure */
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &hostname, &hostname_len, &mx_list, &weight_list) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "pz|z", &hostname, &hostname_len, &mx_list, &weight_list) == FAILURE) {
return;
}
@@ -104,7 +104,7 @@ PHP_FUNCTION(dns_check_record)
DNS_STATUS status; /* Return value of DnsQuery_A() function */
PDNS_RECORD pResult; /* Pointer to DNS_RECORD structure */
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) {
return;
}
@@ -357,7 +357,7 @@ PHP_FUNCTION(dns_get_record)
int type, type_to_fetch, first_query = 1, store_results = 1;
zend_bool raw = 0;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lz!z!b",
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|lz!z!b",
&hostname, &hostname_len, &type_param, &authns, &addtl, &raw) == FAILURE) {
return;
}
diff --git a/ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt b/ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt
new file mode 100644
index 00000000000..b14c8ca719b
--- /dev/null
+++ b/ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt
@@ -0,0 +1,71 @@
+--TEST--
+GHSA-www2-q4fc-65wf
+--DESCRIPTION--
+This is a ZPP test but *keep* this as it is security-sensitive!
+--FILE--
+<?php
+try {
+ dns_check_record("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ dns_get_mx("\0", $out);
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ dns_get_record("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getprotobyname("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getservbyname("\0", "tcp");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getservbyname("x", "tcp\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getservbyport(0, "tcp\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ inet_pton("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ ip2long("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECTF--
+Warning: dns_check_record() expects parameter 1 to be a valid path, string given in %s
+
+Warning: dns_get_mx() expects parameter 1 to be a valid path, string given in %s
+
+Warning: dns_get_record() expects parameter 1 to be a valid path, string given in %s
+
+Warning: getprotobyname() expects parameter 1 to be a valid path, string given in %s
+
+Warning: getservbyname() expects parameter 1 to be a valid path, string given in %s
+
+Warning: getservbyname() expects parameter 2 to be a valid path, string given in %s
+
+Warning: getservbyport() expects parameter 2 to be a valid path, string given in %s
+
+Warning: inet_pton() expects parameter 1 to be a valid path, string given in %s
+
+Warning: ip2long() expects parameter 1 to be a valid path, string given in %s
+
diff --git a/ext/standard/tests/network/ip_x86_64.phpt b/ext/standard/tests/network/ip_x86_64.phpt
index 3c530b83713..2158e289bae 100644
--- a/ext/standard/tests/network/ip_x86_64.phpt
+++ b/ext/standard/tests/network/ip_x86_64.phpt
@@ -54,7 +54,7 @@ bool(false)
bool(false)
int(1869573999)
-Warning: ip2long() expects parameter 1 to be string, array given in %sip_x86_64.php on line %d
+Warning: ip2long() expects parameter 1 to be a valid path, array given in %sip_x86_64.php on line %d
NULL
Warning: long2ip() expects exactly 1 parameter, 0 given in %sip_x86_64.php on line %d
--
2.52.0
From f2cb8ad2342a7b58b8e467ed60233bb9be30f42e Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Thu, 18 Dec 2025 07:17:43 +0100
Subject: [PATCH 5/5] NEWS from 8.1.34
(cherry picked from commit 52b3bdaa74078e4ea8abd9696cdbdc35a8091446)
---
NEWS | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/NEWS b/NEWS
index a9dd716c003..f212d40b2e9 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,16 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+Backported from 8.1.34
+
+- Standard:
+ . Fixed GHSA-www2-q4fc-65wf (Null byte termination in dns_get_record()).
+ (ndossche)
+ . Fixed GHSA-h96m-rvf9-jgm2 (Heap buffer overflow in array_merge()).
+ (CVE-2025-14178) (ndossche)
+ . Fixed GHSA-3237-qqm7-mfv7 (Information Leak of Memory in getimagesize).
+ (CVE-2025-14177) (ndossche)
+
Backported from 8.1.33
- PGSQL:
--
2.52.0

View File

@ -60,7 +60,7 @@
Summary: PHP scripting language for creating dynamic web sites
Name: php
Version: %{upver}%{?rcver:~%{rcver}}
Release: 2%{?dist}
Release: 3%{?dist}
# All files licensed under PHP version 3.01, except
# Zend is licensed under Zend
# TSRM is licensed under BSD
@ -122,6 +122,23 @@ Patch210: php-cve-2024-8925.patch
Patch211: php-cve-2024-8926.patch
Patch212: php-cve-2024-8927.patch
Patch213: php-cve-2024-9026.patch
Patch214: php-cve-2024-11236.patch
Patch215: php-cve-2024-11234.patch
Patch216: php-cve-2024-8932.patch
Patch217: php-cve-2024-11233.patch
Patch218: php-ghsa-4w77-75f9-2c8w.patch
Patch219: php-cve-2024-8929.patch
Patch220: php-cve-2025-1217.patch
Patch221: php-cve-2025-1734.patch
Patch222: php-cve-2025-1861.patch
Patch223: php-cve-2025-1736.patch
Patch224: php-cve-2025-1219.patch
Patch225: php-cve-2025-6491.patch
Patch226: php-cve-2025-1220.patch
Patch227: php-cve-2025-1735.patch
Patch228: php-cve-2025-14177.patch
Patch229: php-cve-2025-14178.patch
Patch230: php-ghsa-www2-q4fc-65wf.patch
# Fixes for tests (300+)
# Factory is droped from system tzdata
@ -745,6 +762,23 @@ in pure PHP.
%patch -P211 -p1 -b .cve8926
%patch -P212 -p1 -b .cve8927
%patch -P213 -p1 -b .cve9026
%patch -P214 -p1 -b .cve11236
%patch -P215 -p1 -b .cve11234
%patch -P216 -p1 -b .cve8932
%patch -P217 -p1 -b .cve11233
%patch -P218 -p1 -b .ghsa4w77
%patch -P219 -p1 -b .cve8929
%patch -P220 -p1 -b .cve1217
%patch -P221 -p1 -b .cve1734
%patch -P222 -p1 -b .cve1861
%patch -P223 -p1 -b .cve1736
%patch -P224 -p1 -b .cve1219
%patch -P225 -p1 -b .cve6491
%patch -P226 -p1 -b .cve1220
%patch -P227 -p1 -b .cve1735
%patch -P228 -p1 -b .cve14177
%patch -P229 -p1 -b .cve14178
%patch -P230 -p1 -b .ghsawwww2
# Fixes for tests
%patch -P300 -p1 -b .datetests
@ -1534,6 +1568,38 @@ systemctl try-restart php-fpm.service >/dev/null 2>&1 || :
%changelog
* Mon Jan 19 2026 Remi Collet <rcollet@redhat.com> - 7.4.33-3
- Fix Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface
GHSA-4w77-75f9-2c8w
- Fix Configuring a proxy in a stream context might allow for CRLF injection in URIs
CVE-2024-11234
- Fix Single byte overread with convert.quoted-printable-decode filter
CVE-2024-11233
- Fix Leak partial content of the heap through heap buffer over-read
CVE-2024-8929
- Fix libxml streams use wrong `content-type` header when requesting a redirected resource
CVE-2025-1219
- Fix Stream HTTP wrapper header check might omit basic auth header
CVE-2025-1736
- Fix Stream HTTP wrapper truncate redirect location to 1024 bytes
CVE-2025-1861
- Fix Streams HTTP wrapper does not fail for headers without colon
CVE-2025-1734
- Fix Header parser of `http` stream wrapper does not handle folded headers
CVE-2025-1217
- Fix pgsql extension does not check for errors during escaping
CVE-2025-1735
- Fix NULL Pointer Dereference in PHP SOAP Extension via Large XML Namespace Prefix
CVE-2025-6491
- Fix Null byte termination in hostnames
CVE-2025-1220
- Fix Null byte termination in dns_get_record()
GHSA-www2-q4fc-65wf
- Fix Heap buffer overflow in array_merge()
CVE-2025-14178
- Fix Information Leak of Memory in getimagesize
CVE-2025-14177
* Wed Nov 13 2024 Remi Collet <rcollet@redhat.com> - 7.4.33-2
- fix low/moderate CVEs
RHEL-66589