Fix Null byte termination in dns_get_record() GHSA-www2-q4fc-65wf

Fix Heap buffer overflow in array_merge()  CVE-2025-14178
Fix Information Leak of Memory in getimagesize  CVE-2025-14177

Resolves: RHEL-141193
This commit is contained in:
Remi Collet 2026-01-16 07:52:04 +01:00
parent 24221dbf44
commit 79b91547ee
4 changed files with 411 additions and 1 deletions

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

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

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

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

View File

@ -0,0 +1,231 @@
From 52c5762a902e8731b7068ded027fbd780f5a1991 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 6 Sep 2025 21:55:13 +0200
Subject: [PATCH 4/5] Fix GHSA-www2-q4fc-65wf
(cherry picked from commit ed70b1ea43a9b7ffa2f53b3e5d6ba403f37ae81c)
---
ext/standard/basic_functions.c | 12 ++--
ext/standard/dns.c | 6 +-
ext/standard/dns_win32.c | 6 +-
.../tests/network/ghsa-www2-q4fc-65wf.phpt | 62 +++++++++++++++++++
4 files changed, 74 insertions(+), 12 deletions(-)
create mode 100644 ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index 876ef347ebf..9eba6d5a14c 100755
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -633,7 +633,7 @@ PHP_FUNCTION(inet_pton)
char buffer[17];
ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STRING(address, address_len)
+ Z_PARAM_PATH(address, address_len)
ZEND_PARSE_PARAMETERS_END();
memset(buffer, 0, sizeof(buffer));
@@ -670,7 +670,7 @@ PHP_FUNCTION(ip2long)
#endif
ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STRING(addr, addr_len)
+ Z_PARAM_PATH(addr, addr_len)
ZEND_PARSE_PARAMETERS_END();
#ifdef HAVE_INET_PTON
@@ -2265,8 +2265,8 @@ PHP_FUNCTION(getservbyname)
struct servent *serv;
ZEND_PARSE_PARAMETERS_START(2, 2)
- Z_PARAM_STRING(name, name_len)
- Z_PARAM_STRING(proto, proto_len)
+ Z_PARAM_PATH(name, name_len)
+ Z_PARAM_PATH(proto, proto_len)
ZEND_PARSE_PARAMETERS_END();
@@ -2309,7 +2309,7 @@ PHP_FUNCTION(getservbyport)
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_LONG(port)
- Z_PARAM_STRING(proto, proto_len)
+ Z_PARAM_PATH(proto, proto_len)
ZEND_PARSE_PARAMETERS_END();
serv = getservbyport(htons((unsigned short) port), proto);
@@ -2332,7 +2332,7 @@ PHP_FUNCTION(getprotobyname)
struct protoent *ent;
ZEND_PARSE_PARAMETERS_START(1, 1)
- Z_PARAM_STRING(name, name_len)
+ Z_PARAM_PATH(name, name_len)
ZEND_PARSE_PARAMETERS_END();
ent = getprotobyname(name);
diff --git a/ext/standard/dns.c b/ext/standard/dns.c
index a81ae3f71fc..4b3fac8e915 100644
--- a/ext/standard/dns.c
+++ b/ext/standard/dns.c
@@ -368,7 +368,7 @@ PHP_FUNCTION(dns_check_record)
#endif
ZEND_PARSE_PARAMETERS_START(1, 2)
- Z_PARAM_STRING(hostname, hostname_len)
+ Z_PARAM_PATH(hostname, hostname_len)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(rectype, rectype_len)
ZEND_PARSE_PARAMETERS_END();
@@ -815,7 +815,7 @@ PHP_FUNCTION(dns_get_record)
zend_bool raw = 0;
ZEND_PARSE_PARAMETERS_START(1, 5)
- Z_PARAM_STRING(hostname, hostname_len)
+ Z_PARAM_PATH(hostname, hostname_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(type_param)
Z_PARAM_ZVAL(authns)
@@ -1053,7 +1053,7 @@ PHP_FUNCTION(dns_get_mx)
#endif
ZEND_PARSE_PARAMETERS_START(2, 3)
- Z_PARAM_STRING(hostname, hostname_len)
+ Z_PARAM_PATH(hostname, hostname_len)
Z_PARAM_ZVAL(mx_list)
Z_PARAM_OPTIONAL
Z_PARAM_ZVAL(weight_list)
diff --git a/ext/standard/dns_win32.c b/ext/standard/dns_win32.c
index d677da0c150..1870998ef1f 100644
--- a/ext/standard/dns_win32.c
+++ b/ext/standard/dns_win32.c
@@ -48,7 +48,7 @@ PHP_FUNCTION(dns_get_mx) /* {{{ */
DNS_STATUS status; /* Return value of DnsQuery_A() function */
PDNS_RECORD pResult, pRec; /* Pointer to DNS_RECORD structure */
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &hostname, &hostname_len, &mx_list, &weight_list) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "pz|z", &hostname, &hostname_len, &mx_list, &weight_list) == FAILURE) {
RETURN_THROWS();
}
@@ -101,7 +101,7 @@ PHP_FUNCTION(dns_check_record)
DNS_STATUS status; /* Return value of DnsQuery_A() function */
PDNS_RECORD pResult; /* Pointer to DNS_RECORD structure */
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) {
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) {
RETURN_THROWS();
}
@@ -353,7 +353,7 @@ PHP_FUNCTION(dns_get_record)
int type, type_to_fetch, first_query = 1, store_results = 1;
zend_bool raw = 0;
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lz!z!b",
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|lz!z!b",
&hostname, &hostname_len, &type_param, &authns, &addtl, &raw) == FAILURE) {
RETURN_THROWS();
}
diff --git a/ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt b/ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt
new file mode 100644
index 00000000000..3d082c8e952
--- /dev/null
+++ b/ext/standard/tests/network/ghsa-www2-q4fc-65wf.phpt
@@ -0,0 +1,62 @@
+--TEST--
+GHSA-www2-q4fc-65wf
+--DESCRIPTION--
+This is a ZPP test but *keep* this as it is security-sensitive!
+--FILE--
+<?php
+try {
+ dns_check_record("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ dns_get_mx("\0", $out);
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ dns_get_record("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getprotobyname("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getservbyname("\0", "tcp");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getservbyname("x", "tcp\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ getservbyport(0, "tcp\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ inet_pton("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+try {
+ ip2long("\0");
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+dns_check_record(): Argument #1 ($hostname) must not contain any null bytes
+dns_get_mx(): Argument #1 ($hostname) must not contain any null bytes
+dns_get_record(): Argument #1 ($hostname) must not contain any null bytes
+getprotobyname(): Argument #1 ($protocol) must not contain any null bytes
+getservbyname(): Argument #1 ($service) must not contain any null bytes
+getservbyname(): Argument #2 ($protocol) must not contain any null bytes
+getservbyport(): Argument #2 ($protocol) must not contain any null bytes
+inet_pton(): Argument #1 ($ip) must not contain any null bytes
+ip2long(): Argument #1 ($ip) must not contain any null bytes
--
2.52.0
From 52b3bdaa74078e4ea8abd9696cdbdc35a8091446 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Thu, 18 Dec 2025 07:17:43 +0100
Subject: [PATCH 5/5] NEWS from 8.1.34
---
NEWS | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/NEWS b/NEWS
index c813f4f357a..ee3b272dfc6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,16 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+Backported from 8.1.34
+
+- Standard:
+ . Fixed GHSA-www2-q4fc-65wf (Null byte termination in dns_get_record()).
+ (ndossche)
+ . Fixed GHSA-h96m-rvf9-jgm2 (Heap buffer overflow in array_merge()).
+ (CVE-2025-14178) (ndossche)
+ . Fixed GHSA-3237-qqm7-mfv7 (Information Leak of Memory in getimagesize).
+ (CVE-2025-14177) (ndossche)
+
Backported from 8.1.33
- PGSQL:
--
2.52.0

View File

@ -62,7 +62,7 @@
Summary: PHP scripting language for creating dynamic web sites
Name: php
Version: %{upver}%{?rcver:~%{rcver}}
Release: 4%{?dist}
Release: 5%{?dist}
# All files licensed under PHP version 3.01, except
# Zend is licensed under Zend
# TSRM is licensed under BSD
@ -151,6 +151,9 @@ Patch217: php-cve-2025-1219.patch
Patch218: php-cve-2025-6491.patch
Patch219: php-cve-2025-1220.patch
Patch220: php-cve-2025-1735.patch
Patch221: php-cve-2025-14177.patch
Patch222: php-cve-2025-14178.patch
Patch223: php-ghsa-www2-q4fc-65wf.patch
# Fixes for tests (300+)
# Factory is droped from system tzdata
@ -776,6 +779,9 @@ rm ext/openssl/tests/p12_with_extra_certs.p12
%patch -P218 -p1 -b .cve6491
%patch -P219 -p1 -b .cve1220
%patch -P220 -p1 -b .cve1735
%patch -P221 -p1 -b .cve14177
%patch -P222 -p1 -b .cve14178
%patch -P223 -p1 -b .ghsawwww2
# Fixes for tests
%patch -P300 -p1 -b .datetests
@ -1584,6 +1590,14 @@ systemctl try-restart php-fpm.service >/dev/null 2>&1 || :
%changelog
* Fri Jan 16 2026 Remi Collet <rcollet@redhat.com> - 8.0.30-5
- Fix Null byte termination in dns_get_record()
GHSA-www2-q4fc-65wf
- Fix Heap buffer overflow in array_merge()
CVE-2025-14178
- Fix Information Leak of Memory in getimagesize
CVE-2025-14177
* Fri Oct 3 2025 Remi Collet <rcollet@redhat.com> - 8.0.30-4
- Fix pgsql extension does not check for errors during escaping
CVE-2025-1735