From fe53521e92eb141a3365df8e98eb41003cdf0887 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Wed, 27 May 2026 20:08:36 +0200 Subject: [PATCH 1/6] ext/session: fix GH-21314 (session GC behaviour is different since 8.4) Closes GH-22164 --- ext/session/php_session.h | 1 + ext/session/session.c | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ext/session/php_session.h b/ext/session/php_session.h index dfa7632e5a44..b52cf43dfba3 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 70e1673d87f5..ba71d709a536 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -394,6 +394,18 @@ static zend_long php_session_gc(bool immediate) /* {{{ */ /* GC must be done before reading session data. */ if ((PS(mod_data) || PS(mod_user_implemented))) { 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); } @@ -2984,14 +2996,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; } /* }}} */ From 6317a472f4435af7e9869c9e594fa8680412430a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Fri, 26 Jun 2026 19:09:26 +0300 Subject: [PATCH 2/6] ext/exif: Fix GH-11020: spurious "Illegal IFD size" warning in exif_read_data() When an IFD is not followed by a 4-byte next-IFD offset, the EXIF block has no further IFD. Treat that as the end of the chain and return the parsed tags, instead of warning and discarding them. The bounds check from bug #72094 still applies, so the absent offset bytes are never read. Closes GH-22486 --- NEWS | 4 ++++ ext/exif/exif.c | 10 ++++++++-- ext/exif/tests/bug72094.phpt | 2 -- ext/exif/tests/gh11020.jpg | Bin 0 -> 663 bytes ext/exif/tests/gh11020.phpt | 12 ++++++++++++ 5 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 ext/exif/tests/gh11020.jpg create mode 100644 ext/exif/tests/gh11020.phpt diff --git a/NEWS b/NEWS index 012a28ffe6ce..766daa2ee517 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.24 +- 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) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 58e6d2801055..1b57377ffb89 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -3642,8 +3642,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 0000000000000000000000000000000000000000..1f978a7576136efe54818072f84dcf2bb52ab4e4 GIT binary patch literal 663 zcmex=BhhaM)G;tL zF)@)>x3sk|ve7d(F#;LF$jQmc!_6bX%PV1|D5GdZGWdUhL68IFC}u__1|~s9WI1EBlCfRTxrg_Vt+gOiIJs9>uA0}~@NGZPClD=P~NP<1U(o`FS>RY=j$ zkxe)-kzJ`!#HexNLJno8jR!@8E`CrkPAY2R|V^&07y2J$~}^+4C1KUw!=a`ODXD z-+%o41@ado12e>1KoYCJ1cClyVqsxsVF&q(k*OSrnFU!`6%E;h90S=C3x$=88aYIq zCNA7~kW<+>=!0ld(M2vX6_bamA3 +--EXPECT-- +bool(true) +int(1) From a9c7dfefbdcab6b6d2a5c5692f5dcfb5676814ae Mon Sep 17 00:00:00 2001 From: alhudz Date: Wed, 10 Jun 2026 11:15:01 +0530 Subject: [PATCH 3/6] ext/dba: fix oob read on malformed length field in dba flatfile handler Closes GH-22266 --- ext/dba/libflatfile/flatfile.c | 60 ++++++++++++++--------------- ext/dba/tests/dba_flatfile_oob.phpt | 31 +++++++++++++++ 2 files changed, 59 insertions(+), 32 deletions(-) create mode 100644 ext/dba/tests/dba_flatfile_oob.phpt 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-- + +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(false) +bool(false) +bool(false) +done From a80216b291c77f4b3cb653034d199f0e793e39ea Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 2 Jul 2026 11:54:12 +0100 Subject: [PATCH 4/6] Update NEWS for bugfixes --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 766daa2ee517..6ac3e8db3939 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.24 +- 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) @@ -42,6 +45,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) From ce1d7873a58b0672971e8a50462daae7859cabda Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 2 Jul 2026 12:03:35 +0100 Subject: [PATCH 5/6] ext/dba: prevent reallocation for info function (#22546) There is no need to duplicate the strings as they are all `const char*` pointers. Thus, just change the handle to return `const char*` --- ext/dba/dba.c | 4 +--- ext/dba/dba_cdb.c | 6 +++--- ext/dba/dba_db1.c | 2 +- ext/dba/dba_db2.c | 2 +- ext/dba/dba_db3.c | 2 +- ext/dba/dba_db4.c | 2 +- ext/dba/dba_dbm.c | 2 +- ext/dba/dba_flatfile.c | 2 +- ext/dba/dba_gdbm.c | 2 +- ext/dba/dba_inifile.c | 2 +- ext/dba/dba_lmdb.c | 2 +- ext/dba/dba_ndbm.c | 2 +- ext/dba/dba_qdbm.c | 2 +- ext/dba/dba_tcadb.c | 2 +- ext/dba/php_dba.h | 4 ++-- 15 files changed, 18 insertions(+), 20 deletions(-) diff --git a/ext/dba/dba.c b/ext/dba/dba.c index c0688714fe7c..a9114753a8a6 100644 --- a/ext/dba/dba.c +++ b/ext/dba/dba.c @@ -1282,10 +1282,8 @@ PHP_FUNCTION(dba_handlers) for (const dba_handler *hptr = handler; hptr->name; hptr++) { if (full_info) { - // TODO: avoid reallocation ??? - char *str = hptr->info(hptr, NULL); + const char *str = hptr->info(hptr, NULL); add_assoc_string(return_value, hptr->name, str); - efree(str); } else { add_next_index_string(return_value, hptr->name); } diff --git a/ext/dba/dba_cdb.c b/ext/dba/dba_cdb.c index 79b60c765c37..3578866b546c 100644 --- a/ext/dba/dba_cdb.c +++ b/ext/dba/dba_cdb.c @@ -319,12 +319,12 @@ DBA_INFO_FUNC(cdb) { #ifdef DBA_CDB_BUILTIN if (!strcmp(hnd->name, "cdb")) { - return estrdup(cdb_version()); + return cdb_version(); } else { - return estrdup(cdb_make_version()); + return cdb_make_version(); } #else - return estrdup("External"); + return "External"; #endif } diff --git a/ext/dba/dba_db1.c b/ext/dba/dba_db1.c index dfcd637c8f87..59846c90a4c8 100644 --- a/ext/dba/dba_db1.c +++ b/ext/dba/dba_db1.c @@ -177,7 +177,7 @@ DBA_SYNC_FUNC(db1) DBA_INFO_FUNC(db1) { - return estrdup(DB1_VERSION); + return DB1_VERSION; } #endif diff --git a/ext/dba/dba_db2.c b/ext/dba/dba_db2.c index dd723ea40af8..50893d8458c8 100644 --- a/ext/dba/dba_db2.c +++ b/ext/dba/dba_db2.c @@ -187,7 +187,7 @@ DBA_SYNC_FUNC(db2) DBA_INFO_FUNC(db2) { - return estrdup(DB_VERSION_STRING); + return DB_VERSION_STRING; } #endif diff --git a/ext/dba/dba_db3.c b/ext/dba/dba_db3.c index aacc65dda5a5..d4111e8c41fc 100644 --- a/ext/dba/dba_db3.c +++ b/ext/dba/dba_db3.c @@ -225,7 +225,7 @@ DBA_SYNC_FUNC(db3) DBA_INFO_FUNC(db3) { - return estrdup(DB_VERSION_STRING); + return DB_VERSION_STRING; } #endif diff --git a/ext/dba/dba_db4.c b/ext/dba/dba_db4.c index 16ac9be37a13..fbd5f8191b44 100644 --- a/ext/dba/dba_db4.c +++ b/ext/dba/dba_db4.c @@ -282,7 +282,7 @@ DBA_SYNC_FUNC(db4) DBA_INFO_FUNC(db4) { - return estrdup(DB_VERSION_STRING); + return DB_VERSION_STRING; } #endif diff --git a/ext/dba/dba_dbm.c b/ext/dba/dba_dbm.c index 9579444454ce..73f137e9891f 100644 --- a/ext/dba/dba_dbm.c +++ b/ext/dba/dba_dbm.c @@ -192,7 +192,7 @@ DBA_INFO_FUNC(dbm) return dba_info_gdbm(hnd, info); } #endif - return estrdup(DBM_VERSION); + return DBM_VERSION; } #endif diff --git a/ext/dba/dba_flatfile.c b/ext/dba/dba_flatfile.c index 4208277a920d..5e8bae223f9e 100644 --- a/ext/dba/dba_flatfile.c +++ b/ext/dba/dba_flatfile.c @@ -167,7 +167,7 @@ DBA_SYNC_FUNC(flatfile) DBA_INFO_FUNC(flatfile) { - return estrdup(flatfile_version()); + return flatfile_version(); } #endif diff --git a/ext/dba/dba_gdbm.c b/ext/dba/dba_gdbm.c index 961f539a451f..64877eb289c4 100644 --- a/ext/dba/dba_gdbm.c +++ b/ext/dba/dba_gdbm.c @@ -189,7 +189,7 @@ DBA_SYNC_FUNC(gdbm) DBA_INFO_FUNC(gdbm) { - return estrdup(gdbm_version); + return gdbm_version; } #endif diff --git a/ext/dba/dba_inifile.c b/ext/dba/dba_inifile.c index 1539bb0496dd..bf5ef322759d 100644 --- a/ext/dba/dba_inifile.c +++ b/ext/dba/dba_inifile.c @@ -186,7 +186,7 @@ DBA_SYNC_FUNC(inifile) DBA_INFO_FUNC(inifile) { - return estrdup(inifile_version()); + return inifile_version(); } #endif diff --git a/ext/dba/dba_lmdb.c b/ext/dba/dba_lmdb.c index 57e74f320239..6e6a78ad5e24 100644 --- a/ext/dba/dba_lmdb.c +++ b/ext/dba/dba_lmdb.c @@ -360,7 +360,7 @@ DBA_SYNC_FUNC(lmdb) DBA_INFO_FUNC(lmdb) { - return estrdup(MDB_VERSION_STRING); + return MDB_VERSION_STRING; } #endif diff --git a/ext/dba/dba_ndbm.c b/ext/dba/dba_ndbm.c index 2b4002591e91..c60ab7e5f428 100644 --- a/ext/dba/dba_ndbm.c +++ b/ext/dba/dba_ndbm.c @@ -147,7 +147,7 @@ DBA_SYNC_FUNC(ndbm) DBA_INFO_FUNC(ndbm) { - return estrdup("NDBM"); + return "NDBM"; } #endif diff --git a/ext/dba/dba_qdbm.c b/ext/dba/dba_qdbm.c index 8e692b7e55bc..cc97cd9757f4 100644 --- a/ext/dba/dba_qdbm.c +++ b/ext/dba/dba_qdbm.c @@ -173,7 +173,7 @@ DBA_SYNC_FUNC(qdbm) DBA_INFO_FUNC(qdbm) { - return estrdup(dpversion); + return dpversion; } #endif diff --git a/ext/dba/dba_tcadb.c b/ext/dba/dba_tcadb.c index 0539a7036602..5f4ed5eeabdc 100644 --- a/ext/dba/dba_tcadb.c +++ b/ext/dba/dba_tcadb.c @@ -193,7 +193,7 @@ DBA_SYNC_FUNC(tcadb) DBA_INFO_FUNC(tcadb) { - return estrdup(tcversion); + return tcversion; } #endif diff --git a/ext/dba/php_dba.h b/ext/dba/php_dba.h index 86c8a4f0ed65..38ad3ea2820b 100644 --- a/ext/dba/php_dba.h +++ b/ext/dba/php_dba.h @@ -89,7 +89,7 @@ typedef struct dba_handler { zend_string* (*nextkey)(dba_info *); zend_result (*optimize)(dba_info *); zend_result (*sync)(dba_info *); - char* (*info)(const struct dba_handler *hnd, dba_info *); + const char* (*info)(const struct dba_handler *hnd, dba_info *); /* dba_info==NULL: Handler info, dba_info!=NULL: Database info */ } dba_handler; @@ -116,7 +116,7 @@ typedef struct dba_handler { #define DBA_SYNC_FUNC(x) \ zend_result dba_sync_##x(dba_info *info) #define DBA_INFO_FUNC(x) \ - char *dba_info_##x(const dba_handler *hnd, dba_info *info) + const char *dba_info_##x(const dba_handler *hnd, dba_info *info) #define DBA_FUNCS(x) \ DBA_OPEN_FUNC(x); \ From 04b63f9f67d05f79c4c0d8f180efb86b11a98713 Mon Sep 17 00:00:00 2001 From: Matteo Beccati Date: Thu, 2 Jul 2026 13:16:30 +0200 Subject: [PATCH 6/6] Added missing NEWS entries for alpha2 --- NEWS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NEWS b/NEWS index 5f3ea1ff3f61..aa6f7f81a89d 100644 --- a/NEWS +++ b/NEWS @@ -9,13 +9,27 @@ PHP NEWS . 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) +- Opcache: + . Fixed bug GH-21770 (Infinite recursion in property hook getter in opcache + preloaded trait). (iliaal) + - OpenSSL: + . Fixed timeout for supplemental read at end of a blocking stream in SSL + stream wrapper. (ilutov) . Fixed stream_socket_get_crypto_status() after supplemental read. (ilutov) +- PDO_ODBC: + . Fixed bug GH-20726 (Crash with ODBC connection pooling when the DSN + carries no credentials). (iliaal) + - Session: . Fixed bug GH-21314 (Different session garbage collector behavior between PHP 8.3 and PHP 8.5). (jorgsowa) +- Streams: + . Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL + and a proxy set). (CVE-2026-12184) (ndossche) + 02 Jul 2026, PHP 8.6.0alpha1 - Core: