diff --git a/NEWS b/NEWS index ec9efcbe02a3..aa6f7f81a89d 100644 --- a/NEWS +++ b/NEWS @@ -2,9 +2,34 @@ 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) + +- 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: 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/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/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); \ 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 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-- + +--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)