Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3abebf3
Fix JIT TLS on MacOS
arnaud-lb Oct 13, 2025
54d793d
Merge branch 'PHP-8.3' into PHP-8.4
arnaud-lb Oct 13, 2025
0f63407
Merge branch 'PHP-8.4' into PHP-8.5
arnaud-lb Oct 13, 2025
dc8b9f0
Merge branch 'PHP-8.5'
arnaud-lb Oct 13, 2025
8e0504c
Partially fix GH-16317: DOM classes do not allow __debugInfo() overri…
ndossche Oct 11, 2025
39ef16a
Merge branch 'PHP-8.3' into PHP-8.4
ndossche Oct 13, 2025
390e243
Add forgotten NEWS item
ndossche Oct 13, 2025
599078f
Merge branch 'PHP-8.4' into PHP-8.5
ndossche Oct 13, 2025
7fbf9b8
Merge branch 'PHP-8.5'
ndossche Oct 13, 2025
3b54fa4
Make entry argument nullable for phar_split_fname() to avoid extra al…
ndossche Oct 13, 2025
b529c77
phar: Fix more alias memory leaks
ndossche Oct 12, 2025
5e58833
Merge branch 'PHP-8.3' into PHP-8.4
ndossche Oct 13, 2025
5ce0019
Merge branch 'PHP-8.4' into PHP-8.5
ndossche Oct 13, 2025
b9aa42e
Merge branch 'PHP-8.5'
ndossche Oct 13, 2025
5a7c84f
phar: Fix memleak+UAF when opening temp stream in buildFromDirectory(…
ndossche Oct 12, 2025
ffc548d
Merge branch 'PHP-8.3' into PHP-8.4
ndossche Oct 13, 2025
722a8b0
Merge branch 'PHP-8.4' into PHP-8.5
ndossche Oct 13, 2025
67719e0
Merge branch 'PHP-8.5'
ndossche Oct 13, 2025
aab5045
Add extra checks to Phar::mungServer() (#20141)
ndossche Oct 13, 2025
88f8c5c
Fix shm corruption with coercion in options of unserialize()
ndossche Oct 10, 2025
8389197
Merge branch 'PHP-8.3' into PHP-8.4
ndossche Oct 13, 2025
ff86c59
Merge branch 'PHP-8.4' into PHP-8.5
ndossche Oct 13, 2025
f30f358
Merge branch 'PHP-8.5'
ndossche Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ PHP NEWS
. Fixed bug GH-20051 (apache2 shutdowns when restart is requested during
preloading). (Arnaud, welcomycozyhom)

- Phar:
. Support reference values in Phar::mungServer(). (nielsdos)
. Invalid values now throw in Phar::mungServer() instead of being silently
ignored. (nielsdos)

- Standard:
. Fixed bug GH-19926 (reset internal pointer earlier while splicing array
while COW violation flag is still set). (alexandre-daubois)
Expand Down
7 changes: 7 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ PHP 8.6 UPGRADE NOTES
1. Backward Incompatible Changes
========================================

- Phar:
. Invalid values now throw in Phar::mungServer() instead of being silently
ignored.

========================================
2. New Features
========================================
Expand All @@ -44,6 +48,9 @@ PHP 8.6 UPGRADE NOTES
5. Changed Functions
========================================

- Phar:
. Phar::mungServer() now supports reference values.

========================================
6. New Functions
========================================
Expand Down
6 changes: 6 additions & 0 deletions ext/dom/php_dom.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ static void dom_unset_property(zend_object *object, zend_string *member, void **
zend_std_unset_property(object, member, cache_slot);
}

/* This custom handler is necessary to avoid a recursive construction of the entire subtree. */
static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) /* {{{ */
{
dom_object *obj = php_dom_obj_from_obj(object);
Expand All @@ -498,6 +499,11 @@ static HashTable* dom_get_debug_info_helper(zend_object *object, int *is_temp) /
dom_prop_handler *entry;
zend_string *object_str;

/* As we have a custom implementation, we must manually check for overrides. */
if (object->ce->__debugInfo) {
return zend_std_get_debug_info(object, is_temp);
}

*is_temp = 1;

std_props = zend_std_get_properties(object);
Expand Down
20 changes: 20 additions & 0 deletions ext/dom/tests/gh16317.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
GH-16317 (DOM classes do not allow __debugInfo() overrides to work)
--FILE--
<?php

class Demo extends DOMNode {
public function __construct() {}
public function __debugInfo(): array {
return ['x' => 'y'];
}
}

var_dump(new Demo());

?>
--EXPECT--
object(Demo)#1 (1) {
["x"]=>
string(1) "y"
}
48 changes: 44 additions & 4 deletions ext/opcache/jit/tls/zend_jit_tls_darwin.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,36 @@

#include <stdint.h>
#include <unistd.h>
#include <mach-o/dyld.h>

TSRMLS_CACHE_EXTERN();

/* Thunk format used since dydl 1284 (approx. MacOS 15)
* https://github.com/apple-oss-distributions/dyld/blob/9307719dd8dc9b385daa412b03cfceb897b2b398/libdyld/ThreadLocalVariables.h#L146 */
#if defined(__x86_64__) || defined(__aarch64__)
struct TLV_Thunkv2
{
void* func;
uint32_t key;
uint32_t offset;
};
#else
struct TLV_Thunkv2
{
void* func;
uint16_t key;
uint16_t offset;
};
#endif

/* Thunk format used in earlier versions */
struct TLV_Thunkv1
{
void* func;
size_t key;
size_t offset;
};

zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
size_t *tcb_offset,
size_t *module_index,
Expand All @@ -37,12 +64,25 @@ zend_result zend_jit_resolve_tsrm_ls_cache_offsets(
}

#if defined(__x86_64__)
size_t *ti;
struct TLV_Thunkv2 *thunk;
__asm__ __volatile__(
"leaq __tsrm_ls_cache(%%rip),%0"
: "=r" (ti));
*module_offset = ti[2];
*module_index = ti[1] * 8;
: "=r" (thunk));

/* Detect dyld 1284: With dyld 1284, thunk->func will be _tlv_get_addr.
* Unfortunately this symbol is private, but we can find it
* as _tlv_bootstrap+8: https://github.com/apple-oss-distributions/dyld/blob/9307719dd8dc9b385daa412b03cfceb897b2b398/libdyld/threadLocalHelpers.s#L54
* In earlier versions, thunk->func will be tlv_get_addr, which is not
* _tlv_bootstrap+8.
*/
if (thunk->func == (void*)((char*)_tlv_bootstrap + 8)) {
*module_offset = thunk->offset;
*module_index = (size_t)thunk->key * 8;
} else {
struct TLV_Thunkv1 *thunkv1 = (struct TLV_Thunkv1*) thunk;
*module_offset = thunkv1->offset;
*module_index = thunkv1->key * 8;
}

return SUCCESS;
#endif
Expand Down
14 changes: 6 additions & 8 deletions ext/phar/dirstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,12 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
{
phar_entry_info entry, *e;
phar_archive_data *phar = NULL;
char *error, *arch, *entry2;
size_t arch_len, entry_len;
char *error, *arch;
size_t arch_len;
php_url *resource = NULL;

/* pre-readonly check, we need to know if this is a data phar */
if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, &entry2, &entry_len, 2, 2)) {
if (FAILURE == phar_split_fname(url_from, strlen(url_from), &arch, &arch_len, NULL, NULL, 2, 2)) {
php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", no phar archive specified", url_from);
return 0;
}
Expand All @@ -364,7 +364,6 @@ int phar_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url_from, int mo
}

efree(arch);
efree(entry2);

if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
php_stream_wrapper_log_error(wrapper, options, "phar error: cannot create directory \"%s\", write operations disabled", url_from);
Expand Down Expand Up @@ -477,12 +476,12 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
{
phar_entry_info *entry;
phar_archive_data *phar = NULL;
char *error, *arch, *entry2;
size_t arch_len, entry_len;
char *error, *arch;
size_t arch_len;
php_url *resource = NULL;

/* pre-readonly check, we need to know if this is a data phar */
if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, &entry2, &entry_len, 2, 2)) {
if (FAILURE == phar_split_fname(url, strlen(url), &arch, &arch_len, NULL, NULL, 2, 2)) {
php_stream_wrapper_log_error(wrapper, options, "phar error: cannot remove directory \"%s\", no phar archive specified, or phar archive does not exist", url);
return 0;
}
Expand All @@ -492,7 +491,6 @@ int phar_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options
}

efree(arch);
efree(entry2);

if (PHAR_G(readonly) && (!phar || !phar->is_data)) {
php_stream_wrapper_log_error(wrapper, options, "phar error: cannot rmdir directory \"%s\", write operations disabled", url);
Expand Down
26 changes: 9 additions & 17 deletions ext/phar/func_interceptors.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@ PHP_FUNCTION(phar_opendir) /* {{{ */
goto skip_phar;
}

if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, &entry, &entry_len, 2, 0)) {
if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
php_stream_context *context = NULL;
php_stream *stream;
char *name;

efree(entry);
entry = estrndup(filename, filename_len);
/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
entry_len = filename_len;
Expand Down Expand Up @@ -89,8 +88,8 @@ PHP_FUNCTION(phar_opendir) /* {{{ */

static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool using_include_path)
{
char *arch, *entry;
size_t arch_len, entry_len;
char *arch;
size_t arch_len;
zend_string *fname = zend_get_executed_filename_ex();

/* we are checking for existence of a file within the relative path. Chances are good that this is
Expand All @@ -99,13 +98,10 @@ static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool
return NULL;
}

if (FAILURE == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, &entry, &entry_len, 2, 0)) {
if (FAILURE == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
return NULL;
}

efree(entry);
entry = NULL;
entry_len = 0;
/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
/* retrieving a file defaults to within the current directory, so use this if possible */
phar_archive_data *phar;
Expand All @@ -122,8 +118,8 @@ static zend_string* phar_get_name_for_relative_paths(zend_string *filename, bool
return NULL;
}
} else {
entry_len = ZSTR_LEN(filename);
entry = phar_fix_filepath(estrndup(ZSTR_VAL(filename), ZSTR_LEN(filename)), &entry_len, 1);
size_t entry_len = ZSTR_LEN(filename);
char *entry = phar_fix_filepath(estrndup(ZSTR_VAL(filename), ZSTR_LEN(filename)), &entry_len, 1);
if (entry[0] == '/') {
if (!zend_hash_str_exists(&(phar->manifest), entry + 1, entry_len - 1)) {
/* this file is not in the phar, use the original path */
Expand Down Expand Up @@ -509,9 +505,7 @@ static void phar_file_stat(const char *filename, size_t filename_length, int typ
phar = PHAR_G(last_phar);
goto splitted;
}
if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, &entry, &entry_len, 2, 0)) {

efree(entry);
if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
entry = estrndup(filename, filename_length);
/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
entry_len = filename_length;
Expand Down Expand Up @@ -751,10 +745,9 @@ PHP_FUNCTION(phar_is_file) /* {{{ */
goto skip_phar;
}

if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, &entry, &entry_len, 2, 0)) {
if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
phar_archive_data *phar;

efree(entry);
entry = filename;
/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
entry_len = filename_len;
Expand Down Expand Up @@ -817,10 +810,9 @@ PHP_FUNCTION(phar_is_link) /* {{{ */
goto skip_phar;
}

if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, &entry, &entry_len, 2, 0)) {
if (SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)) {
phar_archive_data *phar;

efree(entry);
entry = filename;
/* fopen within phar, if :// is not in the url, then prepend phar://<archive>/ */
entry_len = filename_len;
Expand Down
22 changes: 12 additions & 10 deletions ext/phar/phar.c
Original file line number Diff line number Diff line change
Expand Up @@ -2298,16 +2298,18 @@ zend_result phar_split_fname(const char *filename, size_t filename_len, char **a
*arch_len = ext_str - filename + ext_len;
*arch = estrndup(filename, *arch_len);

if (ext_str[ext_len]) {
*entry_len = filename_len - *arch_len;
*entry = estrndup(ext_str+ext_len, *entry_len);
#ifdef PHP_WIN32
phar_unixify_path_separators(*entry, *entry_len);
#endif
*entry = phar_fix_filepath(*entry, entry_len, 0);
} else {
*entry_len = 1;
*entry = estrndup("/", 1);
if (entry) {
if (ext_str[ext_len]) {
*entry_len = filename_len - *arch_len;
*entry = estrndup(ext_str+ext_len, *entry_len);
#ifdef PHP_WIN32
phar_unixify_path_separators(*entry, *entry_len);
#endif
*entry = phar_fix_filepath(*entry, entry_len, 0);
} else {
*entry_len = 1;
*entry = estrndup("/", 1);
}
}

#ifdef PHP_WIN32
Expand Down
28 changes: 15 additions & 13 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,8 @@ static void phar_postprocess_ru_web(char *fname, size_t fname_len, char **entry,
PHP_METHOD(Phar, running)
{
zend_string *fname;
char *arch, *entry;
size_t arch_len, entry_len;
char *arch;
size_t arch_len;
bool retphar = true;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &retphar) == FAILURE) {
Expand All @@ -427,9 +427,8 @@ PHP_METHOD(Phar, running)

if (
zend_string_starts_with_literal_ci(fname, "phar://")
&& SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, &entry, &entry_len, 2, 0)
&& SUCCESS == phar_split_fname(ZSTR_VAL(fname), ZSTR_LEN(fname), &arch, &arch_len, NULL, NULL, 2, 0)
) {
efree(entry);
if (retphar) {
RETVAL_STRINGL(ZSTR_VAL(fname), arch_len + 7);
efree(arch);
Expand Down Expand Up @@ -485,8 +484,7 @@ PHP_METHOD(Phar, mount)
}
#endif

if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) {
efree(entry);
if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, NULL, NULL, 2, 0)) {
entry = NULL;

if (path_len > 7 && !memcmp(path, "phar://", 7)) {
Expand Down Expand Up @@ -905,7 +903,7 @@ PHP_METHOD(Phar, mungServer)
phar_request_initialize();

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(mungvalues), data) {

ZVAL_DEREF(data);
if (Z_TYPE_P(data) != IS_STRING) {
zend_throw_exception_ex(phar_ce_PharException, 0, "Non-string value passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME");
RETURN_THROWS();
Expand All @@ -919,8 +917,10 @@ PHP_METHOD(Phar, mungServer)
PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_SCRIPT_NAME;
} else if (zend_string_equals_literal(Z_STR_P(data), "SCRIPT_FILENAME")) {
PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_SCRIPT_FILENAME;
} else {
zend_throw_exception_ex(phar_ce_PharException, 0, "Invalid value passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME");
RETURN_THROWS();
}
// TODO Warning for invalid value?
} ZEND_HASH_FOREACH_END();
}
/* }}} */
Expand Down Expand Up @@ -1300,9 +1300,9 @@ PHP_METHOD(Phar, getSupportedCompression)
/* {{{ Completely remove a phar archive from memory and disk */
PHP_METHOD(Phar, unlinkArchive)
{
char *fname, *error, *arch, *entry;
char *fname, *error, *arch;
size_t fname_len;
size_t arch_len, entry_len;
size_t arch_len;
phar_archive_data *phar;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) {
Expand All @@ -1329,16 +1329,14 @@ PHP_METHOD(Phar, unlinkArchive)
if (
zend_file_name
&& zend_string_starts_with_literal_ci(zend_file_name, "phar://")
&& SUCCESS == phar_split_fname(ZSTR_VAL(zend_file_name), ZSTR_LEN(zend_file_name), &arch, &arch_len, &entry, &entry_len, 2, 0)
&& SUCCESS == phar_split_fname(ZSTR_VAL(zend_file_name), ZSTR_LEN(zend_file_name), &arch, &arch_len, NULL, NULL, 2, 0)
) {
if (arch_len == fname_len && !memcmp(arch, fname, arch_len)) {
zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" cannot be unlinked from within itself", fname);
efree(arch);
efree(entry);
RETURN_THROWS();
}
efree(arch);
efree(entry);
}

if (phar->is_persistent) {
Expand Down Expand Up @@ -1785,6 +1783,10 @@ PHP_METHOD(Phar, buildFromDirectory)
pass.ret = return_value;
pass.fp = php_stream_fopen_tmpfile();
if (pass.fp == NULL) {
zval_ptr_dtor(&iteriter);
if (apply_reg) {
zval_ptr_dtor(&regexiter);
}
zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" unable to create temporary file", phar_obj->archive->fname);
RETURN_THROWS();
}
Expand Down
Loading