Compare commits

...

11 Commits

Author SHA1 Message Date
openeuler-ci-bot
cd1e0cd6fb
!254 [sync] PR-247: Fix CVE-2025-1217, CVE-2025-1734, CVE-2025-1861, CVE-2025-1736, CVE-2025-1219
From: @openeuler-sync-bot 
Reviewed-by: @fundawang 
Signed-off-by: @fundawang
2025-03-15 04:34:48 +00:00
Funda Wang
4eda8c3fe8 Fix CVE-2025-1217, CVE-2025-1734, CVE-2025-1861, CVE-2025-1736, CVE-2025-1219
(cherry picked from commit 69c270ab0035e358be175d499c8a5c4780eebc51)
2025-03-15 09:19:10 +08:00
openeuler-ci-bot
a88241f3ef
!227 [sync] PR-224: fix CVE-2024-8929
From: @openeuler-sync-bot 
Reviewed-by: @fundawang 
Signed-off-by: @fundawang
2024-11-28 03:02:13 +00:00
Funda Wang
97bb1474d5 fix CVE-2024-8929
(cherry picked from commit 908ebf77a6d7a500559cb64d7769552351bba051)
2024-11-28 10:10:58 +08:00
openeuler-ci-bot
13ffd92850
!223 [sync] PR-220: fix CVE-2024-11233, CVE-2024-11234, CVE-2024-11236, CVE-2024-8983, GHSA-4w77-75f9-2c8w
From: @openeuler-sync-bot 
Reviewed-by: @fundawang 
Signed-off-by: @fundawang
2024-11-27 05:37:54 +00:00
Funda Wang
eb7d359ea6 fix CVE-2024-11233, CVE-2024-11234, CVE-2024-11236, CVE-2024-8983, GHSA-4w77-75f9-2c8w
(cherry picked from commit 7caeacdd4125e6f241676d5b56807d81e24ba100)
2024-11-27 12:11:05 +08:00
openeuler-ci-bot
a54df2785d
!210 [sync] PR-205: fix CVE-2024-8925, CVE-2024-8926, CVE-2024-8927, CVE-2024-9026
From: @openeuler-sync-bot 
Reviewed-by: @dillon_chen 
Signed-off-by: @dillon_chen
2024-10-08 06:07:21 +00:00
Funda Wang
fec80772ba fix CVE-2024-8925, CVE-2024-8926, CVE-2024-8927, CVE-2024-9026
(cherry picked from commit 16d8b59d234157315d22a9eeebd504829439ed63)
2024-10-08 12:05:18 +08:00
openeuler-ci-bot
af0d058b58
!185 license信息规范整改
From: @fundawang 
Reviewed-by: @dillon_chen 
Signed-off-by: @dillon_chen
2024-06-17 09:24:42 +00:00
Funda Wang
2109f63256 Update licenses declaration 2024-06-12 13:24:54 +08:00
openeuler-ci-bot
4d692b2388
!177 fix CVE-2024-5458
From: @fundawang 
Reviewed-by: @dillon_chen 
Signed-off-by: @dillon_chen
2024-06-11 06:52:32 +00:00
17 changed files with 7008 additions and 34 deletions

View File

@ -0,0 +1,43 @@
From c3150fcc89825f50d476b1b1971870aeb71f167d Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Wed, 12 Mar 2025 07:48:05 +0100
Subject: [PATCH 1/2] Relax test expectation for pcre2lib 10.45 Using
e92848789acd8aa5cf32fedb519ba9378ac64e02
---
ext/pcre/tests/bug75457.phpt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext/pcre/tests/bug75457.phpt b/ext/pcre/tests/bug75457.phpt
index ee5ab162f8a6c..87dc12a1ad056 100644
--- a/ext/pcre/tests/bug75457.phpt
+++ b/ext/pcre/tests/bug75457.phpt
@@ -6,5 +6,5 @@ $pattern = "/(((?(?C)0?=))(?!()0|.(?0)0)())/";
var_dump(preg_match($pattern, "hello"));
?>
--EXPECTF--
-Warning: preg_match(): Compilation failed: assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
+Warning: preg_match(): Compilation failed: %r(atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
bool(false)
From 126095700a02b9aa1f33764a63c93a70e8373ad8 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@famillecollet.com>
Date: Wed, 12 Mar 2025 09:36:33 +0100
Subject: [PATCH 2/2] Update ext/pcre/tests/bug75457.phpt
Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
---
ext/pcre/tests/bug75457.phpt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext/pcre/tests/bug75457.phpt b/ext/pcre/tests/bug75457.phpt
index 87dc12a1ad056..1401b25ff6fb7 100644
--- a/ext/pcre/tests/bug75457.phpt
+++ b/ext/pcre/tests/bug75457.phpt
@@ -6,5 +6,5 @@ $pattern = "/(((?(?C)0?=))(?!()0|.(?0)0)())/";
var_dump(preg_match($pattern, "hello"));
?>
--EXPECTF--
-Warning: preg_match(): Compilation failed: %r(atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
+Warning: preg_match(): Compilation failed:%r( atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
bool(false)

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

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,118 @@
From bc1f192102dd8cbda028e40aa31604c4885d387c 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/8] Fix GHSA-c5f2-jwm7-mmq2: stream HTTP fulluri CRLF
injection
(cherry picked from commit 426a6d4539ebee34879ac5de857036bb6ff0e732)
---
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 45677c396ac..6859a4e5181 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -184,6 +184,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)
@@ -203,6 +208,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);
+ zend_string_release(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
@@ -383,12 +395,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..e7dd194dbbe
--- /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
From 8d130e16fbfda7d154fedfa0f1ff1d5ad5e26815 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Fri, 22 Nov 2024 09:41:12 +0100
Subject: [PATCH 8/8] fix transport_string release
---
ext/standard/http_fopen_wrapper.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 6859a4e5181..40e6f3dd4c3 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -211,7 +211,7 @@ 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);
- zend_string_release(transport_string);
+ efree(transport_string);
return NULL;
}
--
2.47.0

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

@ -0,0 +1,117 @@
From 5d9e54065ed18c51e4f25d8900635f90810c7394 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/8] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the dblib
quoter causing OOB writes
(cherry picked from commit d9baa9fed8c3ba692a36b388c0c7762e5102e2e0)
---
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 7f160a402f7..d7d0901ea1a 100644
--- a/ext/pdo_dblib/dblib_driver.c
+++ b/ext/pdo_dblib/dblib_driver.c
@@ -152,6 +152,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) {
@@ -166,7 +167,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;
}
@@ -174,6 +175,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 b4f73be75dbdde970a18cc7a636898b10400fb3f 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/8] Fix GHSA-5hqh-c84r-qjcv: Integer overflow in the firebird
quoter causing OOB writes
(cherry picked from commit 69c5f68fdc3deed9ebce2cc44b4bf5e0c47cd28f)
---
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 e0a424c56ab..fb697978503 100644
--- a/ext/pdo_firebird/firebird_driver.c
+++ b/ext/pdo_firebird/firebird_driver.c
@@ -663,7 +663,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;
@@ -678,6 +678,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

188
php-cve-2024-8925.patch Normal file
View File

@ -0,0 +1,188 @@
From 2b0daf421c162376892832588eccdfa9a286ed09 Mon Sep 17 00:00:00 2001
From: Arnaud Le Blanc <arnaud.lb@gmail.com>
Date: Mon, 9 Sep 2024 15:22:07 +0200
Subject: [PATCH 3/8] Fix GHSA-9pqp-7h25-4f32
multipart/form-data boundaries larger than the read buffer result in erroneous
parsing, which violates data integrity.
Limit boundary size, as allowed by RFC 1521:
Encapsulation boundaries [...] must be no longer than 70 characters, not
counting the two leading hyphens.
We correctly parse payloads with boundaries of length up to
FILLUNIT-strlen("\r\n--") bytes, so allow this for BC.
(cherry picked from commit 19b49258d0c5a61398d395d8afde1123e8d161e0)
---
main/rfc1867.c | 7 ++
tests/basic/GHSA-9pqp-7h25-4f32.inc | 3 +
tests/basic/GHSA-9pqp-7h25-4f32.phpt | 100 +++++++++++++++++++++++++++
3 files changed, 110 insertions(+)
create mode 100644 tests/basic/GHSA-9pqp-7h25-4f32.inc
create mode 100644 tests/basic/GHSA-9pqp-7h25-4f32.phpt
diff --git a/main/rfc1867.c b/main/rfc1867.c
index 3086e8da3db..eafe6a67d2e 100644
--- a/main/rfc1867.c
+++ b/main/rfc1867.c
@@ -752,6 +752,13 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */
boundary_len = boundary_end-boundary;
}
+ /* Boundaries larger than FILLUNIT-strlen("\r\n--") characters lead to
+ * erroneous parsing */
+ if (boundary_len > FILLUNIT-strlen("\r\n--")) {
+ sapi_module.sapi_error(E_WARNING, "Boundary too large in multipart/form-data POST data");
+ return;
+ }
+
/* Initialize the buffer */
if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) {
sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer");
diff --git a/tests/basic/GHSA-9pqp-7h25-4f32.inc b/tests/basic/GHSA-9pqp-7h25-4f32.inc
new file mode 100644
index 00000000000..adf72a361a2
--- /dev/null
+++ b/tests/basic/GHSA-9pqp-7h25-4f32.inc
@@ -0,0 +1,3 @@
+<?php
+print "Hello world\n";
+var_dump($_POST);
diff --git a/tests/basic/GHSA-9pqp-7h25-4f32.phpt b/tests/basic/GHSA-9pqp-7h25-4f32.phpt
new file mode 100644
index 00000000000..af819163705
--- /dev/null
+++ b/tests/basic/GHSA-9pqp-7h25-4f32.phpt
@@ -0,0 +1,100 @@
+--TEST--
+GHSA-9pqp-7h25-4f32
+--SKIPIF--
+<?php
+if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
+ die("skip php-cgi not available");
+}
+?>
+--FILE--
+<?php
+
+const FILLUNIT = 5 * 1024;
+
+function test($boundaryLen) {
+ printf("Boundary len: %d\n", $boundaryLen);
+
+ $cmd = [
+ getenv('TEST_PHP_CGI_EXECUTABLE'),
+ '-C',
+ '-n',
+ __DIR__ . '/GHSA-9pqp-7h25-4f32.inc',
+ ];
+
+ $boundary = str_repeat('A', $boundaryLen);
+ $body = ""
+ . "--$boundary\r\n"
+ . "Content-Disposition: form-data; name=\"koko\"\r\n"
+ . "\r\n"
+ . "BBB\r\n--" . substr($boundary, 0, -1) . "CCC\r\n"
+ . "--$boundary--\r\n"
+ ;
+
+ $env = array_merge($_ENV, [
+ 'REDIRECT_STATUS' => '1',
+ 'CONTENT_TYPE' => "multipart/form-data; boundary=$boundary",
+ 'CONTENT_LENGTH' => strlen($body),
+ 'REQUEST_METHOD' => 'POST',
+ 'SCRIPT_FILENAME' => __DIR__ . '/GHSA-9pqp-7h25-4f32.inc',
+ ]);
+
+ $spec = [
+ 0 => ['pipe', 'r'],
+ 1 => STDOUT,
+ 2 => STDOUT,
+ ];
+
+ $pipes = [];
+
+ print "Starting...\n";
+
+ $handle = proc_open($cmd, $spec, $pipes, getcwd(), $env);
+
+ fwrite($pipes[0], $body);
+
+ $status = proc_close($handle);
+
+ print "\n";
+}
+
+for ($offset = -1; $offset <= 1; $offset++) {
+ test(FILLUNIT - strlen("\r\n--") + $offset);
+}
+
+?>
+--EXPECTF--
+Boundary len: 5115
+Starting...
+X-Powered-By: %s
+Content-type: text/html; charset=UTF-8
+
+Hello world
+array(1) {
+ ["koko"]=>
+ string(5124) "BBB
+--AAA%sCCC"
+}
+
+Boundary len: 5116
+Starting...
+X-Powered-By: %s
+Content-type: text/html; charset=UTF-8
+
+Hello world
+array(1) {
+ ["koko"]=>
+ string(5125) "BBB
+--AAA%sCCC"
+}
+
+Boundary len: 5117
+Starting...
+X-Powered-By: %s
+Content-type: text/html; charset=UTF-8
+
+<br />
+<b>Warning</b>: Boundary too large in multipart/form-data POST data in <b>Unknown</b> on line <b>0</b><br />
+Hello world
+array(0) {
+}
+
--
2.46.1
From c75683864f6e4188439e8ca2adbb05824918be12 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Mon, 23 Sep 2024 18:54:31 +0100
Subject: [PATCH 7/8] Skip GHSA-9pqp-7h25-4f32 test on Windows
(cherry picked from commit c70e25630832fa10d421328eed2b8e1a36af7a64)
---
tests/basic/GHSA-9pqp-7h25-4f32.phpt | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/basic/GHSA-9pqp-7h25-4f32.phpt b/tests/basic/GHSA-9pqp-7h25-4f32.phpt
index af819163705..29bcb6557d5 100644
--- a/tests/basic/GHSA-9pqp-7h25-4f32.phpt
+++ b/tests/basic/GHSA-9pqp-7h25-4f32.phpt
@@ -5,6 +5,9 @@ GHSA-9pqp-7h25-4f32
if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
die("skip php-cgi not available");
}
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ die("skip not for Windows in CI - probably resource issue");
+}
?>
--FILE--
<?php
--
2.46.1

174
php-cve-2024-8926.patch Normal file
View File

@ -0,0 +1,174 @@
From 9f95e17cc0a9a79da82157e34e3effe1bc395037 Mon Sep 17 00:00:00 2001
From: Jan Ehrhardt <github@ehrhardt.nl>
Date: Wed, 5 Jun 2024 20:44:46 +0200
Subject: [PATCH 1/8] Fix GHSA-3qgc-jrrr-25jv
---
sapi/cgi/cgi_main.c | 23 ++++++++++++++-
sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt | 38 +++++++++++++++++++++++++
2 files changed, 60 insertions(+), 1 deletion(-)
create mode 100644 sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index 0d52941c5a1..0d3b54ed8b8 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -1798,8 +1798,13 @@ int main(int argc, char *argv[])
}
}
+ /* Apache CGI will pass the query string to the command line if it doesn't contain a '='.
+ * This can create an issue where a malicious request can pass command line arguments to
+ * the executable. Ideally we skip argument parsing when we're in cgi or fastcgi mode,
+ * but that breaks PHP scripts on Linux with a hashbang: `#!/php-cgi -d option=value`.
+ * Therefore, this code only prevents passing arguments if the query string starts with a '-'.
+ * Similarly, scripts spawned in subprocesses on Windows may have the same issue. */
if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
- /* we've got query string that has no = - apache CGI will pass it to command line */
unsigned char *p;
decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
@@ -1809,6 +1814,22 @@ int main(int argc, char *argv[])
if(*p == '-') {
skip_getopt = 1;
}
+
+ /* On Windows we have to take into account the "best fit" mapping behaviour. */
+#ifdef PHP_WIN32
+ if (*p >= 0x80) {
+ wchar_t wide_buf[1];
+ wide_buf[0] = *p;
+ char char_buf[4];
+ size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]);
+ size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]);
+ if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0
+ || char_buf[0] == '-') {
+ skip_getopt = 1;
+ }
+ }
+#endif
+
free(decoded_query_string);
}
diff --git a/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt b/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt
new file mode 100644
index 00000000000..fd2fcdfbf89
--- /dev/null
+++ b/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GHSA-3qgc-jrrr-25jv
+--SKIPIF--
+<?php
+include 'skipif.inc';
+if (PHP_OS_FAMILY !== "Windows") die("skip Only for Windows");
+
+$codepage = trim(shell_exec("powershell Get-ItemPropertyValue HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage ACP"));
+if ($codepage !== '932' && $codepage !== '936' && $codepage !== '950') die("skip Wrong codepage");
+?>
+--FILE--
+<?php
+include 'include.inc';
+
+$filename = __DIR__."/GHSA-3qgc-jrrr-25jv_tmp.php";
+$script = '<?php echo "hello "; echo "world"; ?>';
+file_put_contents($filename, $script);
+
+$php = get_cgi_path();
+reset_env_vars();
+
+putenv("SERVER_NAME=Test");
+putenv("SCRIPT_FILENAME=$filename");
+putenv("QUERY_STRING=%ads");
+putenv("REDIRECT_STATUS=1");
+
+passthru("$php -s");
+
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__."/GHSA-3qgc-jrrr-25jv_tmp.php");
+?>
+--EXPECTF--
+X-Powered-By: PHP/%s
+Content-type: %s
+
+hello world
--
2.46.1
From 2d2552e092b6ff32cd823692d512f126ee629842 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Fri, 14 Jun 2024 19:49:22 +0200
Subject: [PATCH 4/8] Fix GHSA-p99j-rfp4-xqvq
It's no use trying to work around whatever the operating system and Apache
do because we'll be fighting that until eternity.
Change the skip_getopt condition such that when we're running in
CGI or FastCGI mode we always skip the argument parsing.
This is a BC break, but this seems to be the only way to get rid of this
class of issues.
(cherry picked from commit abcfd980bfa03298792fd3aba051c78d52f10642)
---
sapi/cgi/cgi_main.c | 26 ++++++++------------------
1 file changed, 8 insertions(+), 18 deletions(-)
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index 0d3b54ed8b8..6e148874e4f 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -1748,7 +1748,6 @@ int main(int argc, char *argv[])
int status = 0;
#endif
char *query_string;
- char *decoded_query_string;
int skip_getopt = 0;
#if defined(SIGPIPE) && defined(SIG_IGN)
@@ -1803,10 +1802,15 @@ int main(int argc, char *argv[])
* the executable. Ideally we skip argument parsing when we're in cgi or fastcgi mode,
* but that breaks PHP scripts on Linux with a hashbang: `#!/php-cgi -d option=value`.
* Therefore, this code only prevents passing arguments if the query string starts with a '-'.
- * Similarly, scripts spawned in subprocesses on Windows may have the same issue. */
+ * Similarly, scripts spawned in subprocesses on Windows may have the same issue.
+ * However, Windows has lots of conversion rules and command line parsing rules that
+ * are too difficult and dangerous to reliably emulate. */
if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
+#ifdef PHP_WIN32
+ skip_getopt = cgi || fastcgi;
+#else
unsigned char *p;
- decoded_query_string = strdup(query_string);
+ char *decoded_query_string = strdup(query_string);
php_url_decode(decoded_query_string, strlen(decoded_query_string));
for (p = (unsigned char *)decoded_query_string; *p && *p <= ' '; p++) {
/* skip all leading spaces */
@@ -1815,22 +1819,8 @@ int main(int argc, char *argv[])
skip_getopt = 1;
}
- /* On Windows we have to take into account the "best fit" mapping behaviour. */
-#ifdef PHP_WIN32
- if (*p >= 0x80) {
- wchar_t wide_buf[1];
- wide_buf[0] = *p;
- char char_buf[4];
- size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]);
- size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]);
- if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0
- || char_buf[0] == '-') {
- skip_getopt = 1;
- }
- }
-#endif
-
free(decoded_query_string);
+#endif
}
while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
--
2.46.1

56
php-cve-2024-8927.patch Normal file
View File

@ -0,0 +1,56 @@
From 8aa748ee0657cdee8d883ba50d04b68bc450f686 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Tue, 18 Jun 2024 21:28:26 +0200
Subject: [PATCH 5/8] Fix GHSA-94p6-54jq-9mwp
Apache only generates REDIRECT_STATUS, so explicitly check for that
if the server name is Apache, don't allow other variable names.
Furthermore, redirect.so and Netscape no longer exist, so
remove those entries as we can't check their server name anymore.
We now also check for the configuration override *first* such that it
always take precedence. This would allow for a mitigation path if
something like this happens in the future.
(cherry picked from commit 48808d98f4fc2a05193cdcc1aedd6c66816450f1)
---
sapi/cgi/cgi_main.c | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index 6e148874e4f..5879d0e0f93 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -1910,18 +1910,17 @@ int main(int argc, char *argv[])
/* check force_cgi after startup, so we have proper output */
if (cgi && CGIG(force_redirect)) {
- /* Apache will generate REDIRECT_STATUS,
- * Netscape and redirect.so will generate HTTP_REDIRECT_STATUS.
- * redirect.so and installation instructions available from
- * http://www.koehntopp.de/php.
- * -- kk@netuse.de
- */
- if (!getenv("REDIRECT_STATUS") &&
- !getenv ("HTTP_REDIRECT_STATUS") &&
- /* this is to allow a different env var to be configured
- * in case some server does something different than above */
- (!CGIG(redirect_status_env) || !getenv(CGIG(redirect_status_env)))
- ) {
+ /* This is to allow a different environment variable to be configured
+ * in case the we cannot auto-detect which environment variable to use.
+ * Checking this first to allow user overrides in case the environment
+ * variable can be set by an untrusted party. */
+ const char *redirect_status_env = CGIG(redirect_status_env);
+ if (!redirect_status_env) {
+ /* Apache will generate REDIRECT_STATUS. */
+ redirect_status_env = "REDIRECT_STATUS";
+ }
+
+ if (!getenv(redirect_status_env)) {
zend_try {
SG(sapi_headers).http_response_code = 400;
PUTS("<b>Security Alert!</b> The PHP CGI cannot be accessed directly.\n\n\
--
2.46.1

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

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,130 @@
From 9f367d847989b339c33369737daf573e30bab5f1 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/8] Fix GHSA-g665-fm4p-vhff: OOB access in ldap_escape
(cherry picked from commit f9ecf90070a11dad09ca7671a712f81cc2a7d52f)
---
ext/ldap/ldap.c | 20 ++++++++++++++--
ext/ldap/tests/GHSA-g665-fm4p-vhff-1.phpt | 28 ++++++++++++++++++++++
ext/ldap/tests/GHSA-g665-fm4p-vhff-2.phpt | 29 +++++++++++++++++++++++
3 files changed, 75 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 c4dfe0c5b07..6661310d055 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -3760,13 +3760,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;
}
@@ -3833,7 +3843,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_argument_value_error(1, "is too long");
+ RETURN_THROWS();
+ }
+
+ 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..8e2c4fb160d
--- /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 (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ ldap_escape(str_repeat("#", 1431655758).' ', "", LDAP_ESCAPE_DN);
+} catch (ValueError $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..a69597084be
--- /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 (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+// would allocate a string of length 2
+try {
+ ldap_escape(str_repeat("*", 1431655766), "", LDAP_ESCAPE_FILTER);
+} catch (ValueError $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

136
php-cve-2024-9026.patch Normal file
View File

@ -0,0 +1,136 @@
From 22f4d3504d7613ce78bb96aa53cbfe7d672fa036 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Thu, 12 Sep 2024 13:11:11 +0100
Subject: [PATCH 6/8] Fix GHSA-865w-9rf3-2wh5: FPM: Logs from childrens may be
altered
(cherry picked from commit 1f8e16172c7961045c2b0f34ba7613e3f21cdee8)
---
sapi/fpm/fpm/fpm_stdio.c | 2 +-
.../log-bwp-msg-flush-split-sep-pos-end.phpt | 47 +++++++++++++++++++
...log-bwp-msg-flush-split-sep-pos-start.phpt | 47 +++++++++++++++++++
3 files changed, 95 insertions(+), 1 deletion(-)
create mode 100644 sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt
create mode 100644 sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt
diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c
index d75f9158cda..7983d6217b2 100644
--- a/sapi/fpm/fpm/fpm_stdio.c
+++ b/sapi/fpm/fpm/fpm_stdio.c
@@ -228,7 +228,7 @@ stdio_read:
if ((sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos) <= in_buf &&
!memcmp(buf, &FPM_STDIO_CMD_FLUSH[cmd_pos], sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos)) {
zlog_stream_finish(log_stream);
- start = cmd_pos;
+ start = sizeof(FPM_STDIO_CMD_FLUSH) - cmd_pos;
} else {
zlog_stream_str(log_stream, &FPM_STDIO_CMD_FLUSH[0], cmd_pos);
}
diff --git a/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt
new file mode 100644
index 00000000000..52826320080
--- /dev/null
+++ b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-end.phpt
@@ -0,0 +1,47 @@
+--TEST--
+FPM: Buffered worker output plain log with msg with flush split position towards separator end
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+decorate_workers_output = no
+EOT;
+
+$code = <<<EOT
+<?php
+file_put_contents('php://stderr', str_repeat('a', 1013) . "Quarkslab\0fscf\0Quarkslab");
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectEmptyBody();
+$tester->expectLogLine(str_repeat('a', 1013) . "Quarkslab", decorated: false);
+$tester->expectLogLine("Quarkslab", decorated: false);
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt
new file mode 100644
index 00000000000..34905938553
--- /dev/null
+++ b/sapi/fpm/tests/log-bwp-msg-flush-split-sep-pos-start.phpt
@@ -0,0 +1,47 @@
+--TEST--
+FPM: Buffered worker output plain log with msg with flush split position towards separator start
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+decorate_workers_output = no
+EOT;
+
+$code = <<<EOT
+<?php
+file_put_contents('php://stderr', str_repeat('a', 1009) . "Quarkslab\0fscf\0Quarkslab");
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectEmptyBody();
+$tester->expectLogLine(str_repeat('a', 1009) . "Quarkslab", decorated: false);
+$tester->expectLogLine("Quarkslab", decorated: false);
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
--
2.46.1

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

@ -0,0 +1,909 @@
From 4fec08542748c25573063ffc53ea89cd5de1edf0 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)
---
ext/openssl/tests/ServerClientTestCase.inc | 65 +++-
ext/standard/http_fopen_wrapper.c | 343 ++++++++++++------
.../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 | 30 --
8 files changed, 534 insertions(+), 149 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..61d45385b62 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, string|array $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 40e6f3dd4c3..bfc88a74545 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -114,6 +114,171 @@ 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;
+ 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 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 */
+ 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_concat3(
+ last_header_line, last_header_line_length,
+ " ", 1,
+ http_folded_header_line, http_folded_header_line_length);
+ 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;
+ }
+
+ bool store_header = true;
+ 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 = false;
+ }
+ }
+ }
+ }
+
+ 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,
@@ -126,11 +291,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;
@@ -141,8 +307,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;
@@ -655,8 +819,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);
}
@@ -738,130 +900,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))) {
+ 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 = true;
+ } else if (*http_header_line == '\n') {
+ last_line = true;
} 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 = false;
}
-
- 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 (transfer_encoding) {
- php_stream_filter_free(transfer_encoding);
- transfer_encoding = NULL;
+ if (header_info.transfer_encoding) {
+ php_stream_filter_free(header_info.transfer_encoding);
+ header_info.transfer_encoding = NULL;
}
- if (location[0] != '\0') {
+ 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);
@@ -877,15 +1010,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);
@@ -893,7 +1028,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);
@@ -946,7 +1081,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)
@@ -962,8 +1097,8 @@ out:
/* restore mode */
strlcpy(stream->mode, mode, sizeof(stream->mode));
- if (transfer_encoding) {
- php_stream_filter_append(&stream->readfilters, 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..f935b5a02ca
--- /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(message:"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..078d605b671
--- /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(message:"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..ad5ddc879ce
--- /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(message:"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..d0396e819fb
--- /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(message:"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..037d2002cc5
--- /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(message:"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 c5fe60fa612..00000000000
--- a/ext/standard/tests/http/http_response_header_05.phpt
+++ /dev/null
@@ -1,30 +0,0 @@
---TEST--
-$http_reponse_header (whitespace-only "header")
---SKIPIF--
-<?php require 'server.inc'; http_server_skipif(); ?>
---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' => $pid, 'uri' => $uri] = http_server($responses, $output);
-
-$f = file_get_contents($uri);
-var_dump($f);
-var_dump($http_response_header);
-
-http_server_kill($pid);
-
---EXPECT--
-string(4) "Body"
-array(2) {
- [0]=>
- string(15) "HTTP/1.0 200 Ok"
- [1]=>
- string(0) ""
-}
--
2.48.1

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

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,300 @@
From e81d0cd14bfeb17e899c73e3aece4991bbda76af 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)
---
ext/standard/http_fopen_wrapper.c | 51 ++++++++++++++-----
ext/standard/tests/http/bug47021.phpt | 22 ++++----
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, 154 insertions(+), 25 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 bfc88a74545..7ee22b85f88 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -117,6 +117,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;
+ bool error;
bool follow_location;
char location[HTTP_HEADER_BLOCK_SIZE];
} php_stream_http_response_header_info;
@@ -126,6 +127,7 @@ static void php_stream_http_response_header_info_init(
{
header_info->transfer_encoding = NULL;
header_info->file_size = 0;
+ header_info->error = false;
header_info->follow_location = 1;
header_info->location[0] = '\0';
}
@@ -163,10 +165,11 @@ static 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);
@@ -208,6 +211,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 = true;
+ 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. */
@@ -216,9 +232,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 = true;
+ 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;
}
bool store_header = true;
@@ -928,10 +947,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;
}
@@ -961,8 +986,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 326eceb687a..168721f4ec1 100644
--- a/ext/standard/tests/http/bug47021.phpt
+++ b/ext/standard/tests/http/bug47021.phpt
@@ -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 7b015890d2f..94348d1a027 100644
--- a/ext/standard/tests/http/bug75535.phpt
+++ b/ext/standard/tests/http/bug75535.phpt
@@ -21,9 +21,7 @@ http_server_kill($pid);
--EXPECT--
string(0) ""
-array(2) {
+array(1) {
[0]=>
string(15) "HTTP/1.0 200 Ok"
- [1]=>
- string(14) "Content-Length"
}
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..bb7945ce62d
--- /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(message:"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..1d0e4fa70a2
--- /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(message:"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

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

@ -0,0 +1,241 @@
From 8f65ef50929f6781f4973325f9b619f02cce19d8 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)
---
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 e9b2486a7c9..64703c2f56b 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -107,7 +107,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..c40123560ef
--- /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(message:"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.1"
+ [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..37a47df060a
--- /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(message:"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.1"
+ [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..6c84679ff63
--- /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(message:"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.1"
+ [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

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

@ -0,0 +1,348 @@
From adc7e9f20c9a9aab9cd23ca47ec3fb96287898ae 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)
---
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 7ee22b85f88..e9b2486a7c9 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -67,15 +67,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
@@ -119,17 +120,15 @@ typedef struct _php_stream_http_response_header_info {
size_t file_size;
bool error;
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 = false;
+ 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 */
@@ -255,7 +254,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 = true;
+ 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)) {
@@ -538,6 +552,8 @@ finish:
}
}
+ php_stream_http_response_header_info_init(&header_info);
+
if (stream == NULL)
goto out;
@@ -919,8 +935,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;
@@ -990,12 +1004,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);
@@ -1006,18 +1020,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), '/');
@@ -1035,31 +1048,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;
}
@@ -1071,6 +1088,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++; \
@@ -1086,6 +1104,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);
}
@@ -1098,6 +1117,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..744cff9cc72
--- /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(message:"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..bc71fd4e411
--- /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(message:"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

View File

@ -0,0 +1,86 @@
From 462092a48aa0dbad24d9fa8a4a9d418faa14d309 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/8] Fix GHSA-4w77-75f9-2c8w
(cherry picked from commit 7dd336ae838bbf2c62dc47e3c900d657d3534c02)
---
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 295448f1211..5104318a634 100644
--- a/sapi/cli/php_cli_server.c
+++ b/sapi/cli/php_cli_server.c
@@ -1863,8 +1863,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;
@@ -1872,9 +1870,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..2c8aeff12d5
--- /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 . "/", context: $context);
+
+$options = [
+ "http" => [
+ "method" => "POST",
+ ],
+];
+$context = stream_context_create($options);
+
+echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", context: $context);
+?>
+--EXPECT--
+string(5) "AAAAA"
+string(0) ""
--
2.47.0

105
php.spec
View File

@ -26,9 +26,9 @@
Name: php
Version: %{upver}
Release: 4
Release: 9
Summary: PHP scripting language for creating dynamic web sites
License: PHP-3.01 and Zend-2.0 and BSD and MIT and ASL 1.0 and NCSA
License: PHP-3.01 AND Zend-2.0 AND BSD-2-Clause AND MIT AND Apache-1.0 AND NCSA AND BSL-1.0
URL: http://www.php.net/
Source0: http://www.php.net/distributions/php-%{upver}%{?rcver}.tar.xz
Source1: php.conf
@ -58,6 +58,22 @@ Patch7: php-7.4.0-datetests.patch
Patch9: php-cve-2024-2756.patch
Patch10: php-cve-2024-3096.patch
Patch11: php-cve-2024-5458.patch
Patch12: php-cve-2024-8925.patch
Patch13: php-cve-2024-8926.patch
Patch14: php-cve-2024-8927.patch
Patch15: php-cve-2024-9026.patch
Patch16: php-cve-2024-11236.patch
Patch17: php-cve-2024-11234.patch
Patch18: php-cve-2024-8932.patch
Patch19: php-cve-2024-11233.patch
Patch20: php-ghsa-4w77-75f9-2c8w.patch
Patch21: php-cve-2024-8929.patch
Patch22: php-cve-2025-1217.patch
Patch23: php-cve-2025-1734.patch
Patch24: php-cve-2025-1861.patch
Patch25: php-cve-2025-1736.patch
Patch26: php-cve-2025-1219.patch
Patch27: php-8.0.30-pcretests.patch
BuildRequires: bzip2-devel, curl-devel >= 7.9, httpd-devel >= 2.0.46-1, pam-devel, httpd-filesystem, nginx-filesystem
BuildRequires: libstdc++-devel, openssl-devel, sqlite-devel >= 3.6.0, zlib-devel, smtpdaemon, libedit-devel
@ -92,7 +108,7 @@ which adds support for the PHP language to Apache HTTP Server.
%package cli
Summary: Command-line interface for PHP
License: PHP and Zend and BSD and MIT and ASL 1.0 and NCSA and PostgreSQL
License: PHP-3.01 AND Zend-2.0 AND BSD-2-Clause AND MIT AND Apache-1.0 AND NCSA AND PostgreSQL
Requires: php-common%{?_isa} = %{version}-%{release}
Provides: php-cgi = %{version}-%{release}, php-cgi%{?_isa} = %{version}-%{release}, php-pcntl, php-pcntl%{?_isa}
Provides: php-readline, php-readline%{?_isa}
@ -125,7 +141,7 @@ any size, especially busier sites.
%package common
Summary: Common files for PHP
License: PHP and BSD
License: PHP-3.01 AND BSD-2-Clause
Provides: php(api) = %{apiver}-%{__isa_bits}, php(zend-abi) = %{zendver}-%{__isa_bits}
Provides: php(language) = %{version}, php(language)%{?_isa} = %{version}, php-bz2, php-bz2%{?_isa}
Provides: php-calendar, php-calendar%{?_isa}, php-core = %{version}, php-core%{?_isa} = %{version}
@ -162,7 +178,7 @@ need to install this package.
%package opcache
Summary: The Zend OPcache
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
Provides: php-pecl-zendopcache = %{version}, php-pecl-zendopcache%{?_isa} = %{version}, php-pecl(opcache) = %{version}
Provides: php-pecl(opcache)%{?_isa} = %{version}
@ -177,7 +193,7 @@ bytecode optimization patterns that make code execution faster.
%if %{with_imap}
%package imap
Summary: A module for PHP applications that use IMAP
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: krb5-devel, openssl-devel, libc-client-devel
@ -189,7 +205,7 @@ messages on mail servers. PHP is an HTML-embedded scripting language.
%package ldap
Summary: A module for PHP applications that use LDAP
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: cyrus-sasl-devel, openldap-devel, openssl-devel
@ -201,7 +217,7 @@ language.
%package pdo
Summary: A database access abstraction module for PHP applications
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
Provides: php-pdo-abi = %{pdover}-%{__isa_bits}, php(pdo-abi) = %{pdover}-%{__isa_bits}, php-sqlite3, php-sqlite3%{?_isa}
Provides: php-pdo_sqlite, php-pdo_sqlite%{?_isa}
@ -214,7 +230,7 @@ databases.
%package mysqlnd
Summary: A module for PHP applications that use MySQL databases
License: PHP
License: PHP-3.01
Requires: php-pdo%{?_isa} = %{version}-%{release}
Provides: php_database, php-mysqli = %{version}-%{release}, php-mysqli%{?_isa} = %{version}-%{release},php-pdo_mysql
Provides: php-pdo_mysql%{?_isa}
@ -229,7 +245,7 @@ This package use the MySQL Native Driver
%package pgsql
Summary: A PostgreSQL database module for PHP
License: PHP
License: PHP-3.01
Requires: php-pdo%{?_isa} = %{version}-%{release}
Provides: php_database, php-pdo_pgsql, php-pdo_pgsql%{?_isa}
BuildRequires: krb5-devel, openssl-devel, postgresql-devel
@ -244,7 +260,7 @@ php package.
%package process
Summary: Modules for PHP script using system process interfaces
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
Provides: php-posix, php-posix%{?_isa}, php-shmop, php-shmop%{?_isa}, php-sysvsem, php-sysvsem%{?_isa}
Provides: php-sysvshm, php-sysvshm%{?_isa}, php-sysvmsg, php-sysvmsg%{?_isa}
@ -256,7 +272,7 @@ communication.
%package odbc
Summary: A module for PHP applications that use ODBC databases
License: PHP
License: PHP-3.01
Requires: php-pdo%{?_isa} = %{version}-%{release}
Provides: php_database, php-pdo_odbc, php-pdo_odbc%{?_isa}
BuildRequires: unixODBC-devel
@ -272,7 +288,7 @@ package.
%package soap
Summary: A module for PHP applications that use the SOAP protocol
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: libxml2-devel
@ -283,7 +299,7 @@ support to PHP for using the SOAP web services protocol.
%if %{with_firebird}
%package interbase
Summary: A module for PHP applications that use Interbase/Firebird databases
License: PHP
License: PHP-3.01
BuildRequires: firebird-devel
Requires: php-pdo%{?_isa} = %{version}-%{release}
Provides: php_database, php-firebird, php-firebird%{?_isa}, php-pdo_firebird, php-pdo_firebird%{?_isa}
@ -302,7 +318,7 @@ License.
%package snmp
Summary: A module for PHP applications that query SNMP-managed devices
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}, net-snmp
BuildRequires: net-snmp-devel
@ -314,7 +330,7 @@ will need to install this package and the php package.
%package xml
Summary: A module for PHP applications which use XML
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
Provides: php-dom, php-dom%{?_isa}, php-domxml, php-domxml%{?_isa}, php-simplexml, php-simplexml%{?_isa}
Provides: php-xmlreader, php-xmlreader%{?_isa}, php-xmlwriter, php-xmlwriter%{?_isa}
@ -329,7 +345,7 @@ and performing XSL transformations on XML documents.
%package mbstring
Summary: A module for PHP applications which need multi-byte string handling
License: PHP and LGPLv2 and OpenLDAP
License: PHP-3.01 AND LGPL-2.1-only AND OLDAP-2.8
BuildRequires: oniguruma-devel
Provides: bundled(libmbfl) = 1.3.2
Requires: php-common%{?_isa} = %{version}-%{release}
@ -341,9 +357,9 @@ support for multi-byte string handling to PHP.
%package gd
Summary: A module for PHP applications for using the gd graphics library
%if %{with_libgd}
License: PHP
License: PHP-3.01
%else
License: PHP and BSD
License: PHP-3.01 and BSD-2-Clause
%endif
Requires: php-common%{?_isa} = %{version}-%{release}
%if %{with_libgd}
@ -359,7 +375,7 @@ support for using the gd graphics library to PHP.
%package bcmath
Summary: A module for PHP applications for using the bcmath library
License: PHP and LGPLv2+
License: PHP-3.01 AND LGPL-2.1-or-later
Requires: php-common%{?_isa} = %{version}-%{release}
%description bcmath
@ -368,7 +384,7 @@ support for using the bcmath library to PHP.
%package gmp
Summary: A module for PHP applications for using the GNU MP library
License: PHP
License: PHP-3.01
BuildRequires: gmp-devel
Requires: php-common%{?_isa} = %{version}-%{release}
@ -378,7 +394,7 @@ using the GNU MP library.
%package dba
Summary: A database abstraction layer module for PHP applications
License: PHP
License: PHP-3.01
BuildRequires: lmdb-devel, tokyocabinet-devel
Requires: php-common%{?_isa} = %{version}-%{release}
@ -388,7 +404,7 @@ support for using the DBA database abstraction layer to PHP.
%package tidy
Summary: Standard PHP module provides tidy library support
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: libtidy-devel
@ -399,7 +415,7 @@ support for using the tidy library to PHP.
%if %{with_freetds}
%package pdo-dblib
Summary: PDO driver Microsoft SQL Server and Sybase databases
License: PHP
License: PHP-3.01
Requires: php-pdo%{?_isa} = %{version}-%{release}
BuildRequires: freetds-devel
Provides: php-pdo_dblib, php-pdo_dblib%{?_isa}
@ -422,7 +438,7 @@ into applications to provide PHP scripting language support.
%if %{with_pspell}
%package pspell
Summary: A module for PHP applications for using pspell interfaces
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: aspell-devel >= 0.50.0
@ -433,7 +449,7 @@ support for using the pspell library to PHP.
%package intl
Summary: Internationalization extension for PHP applications
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: libicu-devel >= 4.0
@ -443,7 +459,7 @@ support for using the ICU library to PHP.
%package enchant
Summary: Enchant spelling extension for PHP applications
License: PHP
License: PHP-3.01
Requires: php-common%{?_isa} = %{version}-%{release}
BuildRequires: enchant2-devel
@ -454,7 +470,7 @@ support for using the enchant library to PHP.
%if %{with_sodium}
%package sodium
Summary: Wrapper for the Sodium cryptographic library
License: PHP
License: PHP-3.01
BuildRequires: pkgconfig(libsodium) >= 1.0.9
Requires: php-common%{?_isa} = %{version}-%{release}
@ -469,7 +485,7 @@ low-level PHP extension for the libsodium cryptographic library.
%package ffi
Summary: Foreign Function Interface
# All files licensed under PHP version 3.0.1
License: PHP
License: PHP-3.01
Group: System Environment/Libraries
BuildRequires: pkgconfig(libffi)
Requires: php-common%{?_isa} = %{version}-%{release}
@ -481,11 +497,7 @@ scripting language and therefore develop “system code” more productively.
For PHP, FFI opens a way to write PHP extensions and bindings to C libraries
in pure PHP.
%package help
Summary: help
%description help
help
%package_help
%prep
%autosetup -n php-%{upver}%{?rcver} -p1
@ -1081,6 +1093,31 @@ systemctl try-restart php-fpm.service >/dev/null 2>&1 || :
%{_mandir}/*
%changelog
* Fri Mar 14 2025 Funda Wang <fundawang@yeah.net> - 8.0.30-9
- Fix CVE-2025-1217, CVE-2025-1734, CVE-2025-1861, CVE-2025-1736, CVE-2025-1219
* Thu Nov 28 2024 Funda Wang <fundawang@yeah.net> - 8.0.30-8
- Fix Leak partial content of the heap through heap buffer over-read
CVE-2024-8929
* Sat Nov 23 2024 Funda Wang <fundawang@yeah.net> - 8.0.30-7
- Fix Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface
GHSA-4w77-75f9-2c8w
- Fix OOB access in ldap_escape
CVE-2024-8932
- Fix Integer overflow in the dblib/firebird quoter causing OOB writes
CVE-2024-11236
- 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
* Fri Sep 27 2024 Funda Wang <fundawang@yeah.net> - 8.0.30-6
- fix CVE-2024-8925, CVE-2024-8926, CVE-2024-8927, CVE-2024-9026
* Wed Jun 12 2024 Funda Wang <fundawang@yeah.net> - 8.0.30-5
- Update licenses declaration
* Fri Jun 07 2024 Funda Wang <fundawang@yeah.net> - 8.0.30-4
- fix CVE-2024-5458