[php-src] master: Merge branch '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:59:10+01:00

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

Merge branch 'PHP-8.5'

* PHP-8.5:
  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 ec9efcbe02a3..5f3ea1ff3f61 100644
--- a/NEWS
+++ b/NEWS
@@ -2,9 +2,20 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.6.0alpha2
 
+- 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)
+
 - OpenSSL:
   . Fixed stream_socket_get_crypto_status() after supplemental read. (ilutov)
 
+- Session:
+  . Fixed bug GH-21314 (Different session garbage collector behavior between
+    PHP 8.3 and PHP 8.5). (jorgsowa)
+
 02 Jul 2026, PHP 8.6.0alpha1
 
 - Core:
diff --git a/ext/dba/libflatfile/flatfile.c b/ext/dba/libflatfile/flatfile.c
index 561766777f6f..a5a9bf70c23b 100644
--- a/ext/dba/libflatfile/flatfile.c
+++ b/ext/dba/libflatfile/flatfile.c
@@ -35,6 +35,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
@@ -110,10 +122,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);
 
@@ -133,10 +143,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);
@@ -160,10 +168,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);
 
@@ -176,10 +182,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);
 	}
@@ -200,10 +204,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);
 
@@ -216,10 +218,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);
 	}
@@ -242,20 +242,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 a331cbd40cea..31030e12fbf1 100644
--- a/ext/exif/exif.c
+++ b/ext/exif/exif.c
@@ -3650,8 +3650,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 08c08b9a024a..2a1d782fd727 100644
--- a/ext/session/php_session.h
+++ b/ext/session/php_session.h
@@ -183,6 +183,7 @@ typedef struct _php_ps_globals {
 	bool mod_user_is_open;
 	bool mod_user_uses_object_methods_as_handlers;
 	bool use_trans_sid; /* contains the INI value of whether to use trans-sid */
+	bool random_seeded;
 } php_ps_globals;
 
 typedef php_ps_globals zend_ps_globals;
diff --git a/ext/session/session.c b/ext/session/session.c
index 1fdfc5d1073f..1723acc4448c 100644
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -394,6 +394,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);
 		}
 
@@ -2891,14 +2903,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.