[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

Gina Peter Banyard <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Gina Peter Banyard (Girgias)
Date: 2026-07-02T11:56:05+01:00

Commit: https://github.com/php/php-src/commit/c5ea0eb18bbceb2a7a36adc5213a36c02ca4e71e
Raw diff: https://github.com/php/php-src/commit/c5ea0eb18bbceb2a7a36adc5213a36c02ca4e71e.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Update NEWS for bugfixes
  ext/dba: fix oob read on malformed length field in dba flatfile handler
  ext/exif: Fix GH-11020: spurious "Illegal IFD size" warning in exif_read_data()
  ext/session: fix GH-21314 (session GC behaviour is different since 8.4)

Changed paths:
  A  ext/dba/tests/dba_flatfile_oob.phpt
  A  ext/exif/tests/gh11020.jpg
  A  ext/exif/tests/gh11020.phpt
  M  NEWS
  M  ext/dba/libflatfile/flatfile.c
  M  ext/exif/exif.c
  M  ext/exif/tests/bug72094.phpt
  M  ext/session/php_session.h
  M  ext/session/session.c


Diff:

diff --git a/NEWS b/NEWS
index 7cabb876d208..fceef3f9a3c1 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,13 @@ PHP                                                                        NEWS
   . Fixed bug GH-22206 (missing return in global register detection).
     (P3p111n0)
 
+- DBA:
+  . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz)
+
+- Exif:
+  . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
+    warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman)
+
 - Hash:
   . Fixed bug GH-18173 (ext/hash relies on implementation-defined malloc
     alignment). (iliaal)
@@ -55,6 +62,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-22441 (ReflectionClass::hasProperty() and getProperty() ignore
     dynamic properties shadowing a private parent property). (iliaal)
 
+- Session:
+  . Fixed bug GH-21314 (Different session garbage collector behavior between
+    PHP 8.3 and PHP 8.5). (jorgsowa)
+
 - SPL:
   . Fix	class_parents for classes with leading slash in non-autoload mode.                                                                     
     (jorgsowa)
diff --git a/ext/dba/libflatfile/flatfile.c b/ext/dba/libflatfile/flatfile.c
index bd76ecfdd0ae..a68fed7e38c4 100644
--- a/ext/dba/libflatfile/flatfile.c
+++ b/ext/dba/libflatfile/flatfile.c
@@ -37,6 +37,18 @@
 
 #define FLATFILE_BLOCK_SIZE 1024
 
+/* Parse the length prefix in `buf` into `num` and grow `buf` to hold it.
+ * atoi() narrows a malformed (e.g. negative) length to a huge size_t whose
+ * `+ FLATFILE_BLOCK_SIZE` would overflow erealloc(); the macro yields true in
+ * that case so the caller stops reading and the read stays within `buf_size`. */
+#define FLATFILE_GROW_BUF(num, buf, buf_size) ( \
+	(num) = atoi(buf), \
+	(num) >= (buf_size) && ( \
+		(num) > SIZE_MAX - FLATFILE_BLOCK_SIZE \
+		|| ((buf) = erealloc((buf), (buf_size) = (num) + FLATFILE_BLOCK_SIZE), 0) \
+	) \
+)
+
 /*
  * ret = -1 means that database was opened for read-only
  * ret = 0  success
@@ -112,10 +124,8 @@ int flatfile_delete(flatfile *dba, datum key_datum) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		pos = php_stream_tell(dba->fp);
 
@@ -135,10 +145,8 @@ int flatfile_delete(flatfile *dba, datum key_datum) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		/* read in the value */
 		num = php_stream_read(dba->fp, buf, num);
@@ -162,10 +170,8 @@ int flatfile_findkey(flatfile *dba, datum key_datum) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		num = php_stream_read(dba->fp, buf, num);
 
@@ -178,10 +184,8 @@ int flatfile_findkey(flatfile *dba, datum key_datum) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		num = php_stream_read(dba->fp, buf, num);
 	}
@@ -202,10 +206,8 @@ datum flatfile_firstkey(flatfile *dba) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		num = php_stream_read(dba->fp, buf, num);
 
@@ -218,10 +220,8 @@ datum flatfile_firstkey(flatfile *dba) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		num = php_stream_read(dba->fp, buf, num);
 	}
@@ -244,20 +244,16 @@ datum flatfile_nextkey(flatfile *dba) {
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		num = php_stream_read(dba->fp, buf, num);
 
 		if (!php_stream_gets(dba->fp, buf, 15)) {
 			break;
 		}
-		num = atoi(buf);
-		if (num >= buf_size) {
-			buf_size = num + FLATFILE_BLOCK_SIZE;
-			buf = erealloc(buf, buf_size);
+		if (FLATFILE_GROW_BUF(num, buf, buf_size)) {
+			break;
 		}
 		num = php_stream_read(dba->fp, buf, num);
 
diff --git a/ext/dba/tests/dba_flatfile_oob.phpt b/ext/dba/tests/dba_flatfile_oob.phpt
new file mode 100644
index 000000000000..3328e1dcba90
--- /dev/null
+++ b/ext/dba/tests/dba_flatfile_oob.phpt
@@ -0,0 +1,31 @@
+--TEST--
+DBA FlatFile handler bounds with a malformed (negative) length field
+--EXTENSIONS--
+dba
+--SKIPIF--
+<?php
+require_once __DIR__ . '/setup/setup_dba_tests.inc';
+check_skip('flatfile');
+?>
+--FILE--
+<?php
+$db_file = __DIR__ . '/dba_flatfile_oob.db';
+// A negative length narrows to a huge size_t and previously overran the read buffer.
+file_put_contents($db_file, "-1\n" . str_repeat('A', 200000));
+
+$db = dba_open($db_file, 'r', 'flatfile');
+var_dump(dba_firstkey($db));
+var_dump(dba_exists("AAAA", $db));
+var_dump(dba_fetch("AAAA", $db));
+dba_close($db);
+echo "done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/dba_flatfile_oob.db');
+?>
+--EXPECT--
+bool(false)
+bool(false)
+bool(false)
+done
diff --git a/ext/exif/exif.c b/ext/exif/exif.c
index 52bcaddbea0b..b30644f155cf 100644
--- a/ext/exif/exif.c
+++ b/ext/exif/exif.c
@@ -3652,8 +3652,14 @@ static bool exif_process_IFD_in_JPEG(image_info_type *ImageInfo, char *dir_start
 	 * There are 2 IDFs, the second one holds the keys (0x0201 and 0x0202) to the thumbnail
 	 */
 	if (!exif_offset_info_contains(info, dir_start+2+NumDirEntries*12, 4)) {
-		exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size");
-		return false;
+		/*
+		 * A TIFF/EXIF IFD ends with a 4-byte offset to the next IFD (IFD1 here,
+		 * which links the thumbnail), or zero when there is none. Some files end
+		 * the EXIF segment right after the entries and omit those 4 bytes. A
+		 * missing offset is valid and just means there is no next IFD, so stop
+		 * here instead of reporting the size as illegal.
+		 */
+		return true;
 	}
 
 	if (tag != TAG_EXIF_IFD_POINTER && tag != TAG_GPS_IFD_POINTER) {
diff --git a/ext/exif/tests/bug72094.phpt b/ext/exif/tests/bug72094.phpt
index c13a85f93f04..8fb3fa97c83d 100644
--- a/ext/exif/tests/bug72094.phpt
+++ b/ext/exif/tests/bug72094.phpt
@@ -47,8 +47,6 @@ Warning: exif_read_data(bug72094_3.jpg): Process tag(x3030=UndefinedTag): Illega
 
 Warning: exif_read_data(bug72094_3.jpg): Process tag(x3030=UndefinedTag): Illegal format code 0x3030, suppose BYTE in %s%ebug72094.php on line %d
 
-Warning: exif_read_data(bug72094_3.jpg): Illegal IFD size in %s%ebug72094.php on line %d
-
 Warning: exif_read_data(bug72094_3.jpg): File structure corrupted in %s%ebug72094.php on line %d
 
 Warning: exif_read_data(bug72094_3.jpg): Invalid JPEG file in %s%ebug72094.php on line %d
diff --git a/ext/exif/tests/gh11020.jpg b/ext/exif/tests/gh11020.jpg
new file mode 100644
index 000000000000..1f978a757613
Binary files /dev/null and b/ext/exif/tests/gh11020.jpg differ
diff --git a/ext/exif/tests/gh11020.phpt b/ext/exif/tests/gh11020.phpt
new file mode 100644
index 000000000000..0c88605749b8
--- /dev/null
+++ b/ext/exif/tests/gh11020.phpt
@@ -0,0 +1,12 @@
+--TEST--
+GH-11020 (exif_read_data() emits a spurious "Illegal IFD size" warning when an IFD is not followed by a next-IFD offset)
+--EXTENSIONS--
+exif
+--FILE--
+<?php
+$data = exif_read_data(__DIR__ . '/gh11020.jpg');
+var_dump(is_array($data), $data['Orientation']);
+?>
+--EXPECT--
+bool(true)
+int(1)
diff --git a/ext/session/php_session.h b/ext/session/php_session.h
index e48ed4615297..eb203012137e 100644
--- a/ext/session/php_session.h
+++ b/ext/session/php_session.h
@@ -202,6 +202,7 @@ typedef struct _php_ps_globals {
 	bool lazy_write; /* omit session write when it is possible */
 	bool in_save_handler; /* state if session is in save handler or not */
 	bool set_handler;     /* state if session module i setting handler or not */
+	bool random_seeded;
 	zend_string *session_vars; /* serialized original session data */
 } php_ps_globals;
 
diff --git a/ext/session/session.c b/ext/session/session.c
index 489f82d6f142..c4f9e8781159 100644
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -406,6 +406,18 @@ static zend_long php_session_gc(bool immediate)
 	if ((PS(mod_data) || PS(mod_user_implemented))) {
 		/* Use probability-based GC if not forced and probability is configured */
 		if (!collect && PS(gc_probability) > 0) {
+			/* Seed lazily on first GC draw per process. */
+			if (UNEXPECTED(!PS(random_seeded))) {
+				php_random_uint128_t seed;
+				if (php_random_bytes_silent(&seed, sizeof(seed)) == FAILURE) {
+					seed = php_random_uint128_constant(
+						php_random_generate_fallback_seed(),
+						php_random_generate_fallback_seed()
+					);
+				}
+				php_random_pcgoneseq128xslrr64_seed128(PS(random).state, seed);
+				PS(random_seeded) = true;
+			}
 			collect = php_random_range(PS(random), 0, PS(gc_divisor) - 1) < PS(gc_probability);
 		}
 
@@ -2946,14 +2958,7 @@ static PHP_GINIT_FUNCTION(ps)
 		.algo = &php_random_algo_pcgoneseq128xslrr64,
 		.state = &ps_globals->random_state,
 	};
-	php_random_uint128_t seed;
-	if (php_random_bytes_silent(&seed, sizeof(seed)) == FAILURE) {
-		seed = php_random_uint128_constant(
-			php_random_generate_fallback_seed(),
-			php_random_generate_fallback_seed()
-		);
-	}
-	php_random_pcgoneseq128xslrr64_seed128(ps_globals->random.state, seed);
+	ps_globals->random_seeded = false;
 }
 
 static PHP_MINIT_FUNCTION(session)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.