Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .github/workflows/real-time-benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ on:
options:
- "0"
- "1"
binary_layout_strategy:
description: 'How the binary layout is generated'
required: false
default: ""
type: choice
options:
- ""
- "bolt_align"
- "bolt"
collect_extended_perf_stats:
description: 'Whether to collect extended perf stats as artifacts'
required: true
Expand Down Expand Up @@ -58,6 +67,7 @@ jobs:
BASELINE_COMMIT: "d5f6e56610c729710073350af318c4ea1b292cfe"
ID: "master"
JIT: "1"
BINARY_LAYOUT_STRATEGY: ""
COLLECT_EXTENDED_PERF_STATS: "0"
DEBUG_ENVIRONMENT: "0"
RUN_MICRO_BENCH: "0"
Expand Down Expand Up @@ -90,6 +100,7 @@ jobs:
echo "ID=benchmarked" >> $GITHUB_ENV

echo "JIT=${{ inputs.jit }}" >> $GITHUB_ENV
echo "BINARY_LAYOUT_STRATEGY=${{ inputs.binary_layout_strategy }}" >> $GITHUB_ENV
echo "COLLECT_EXTENDED_PERF_STATS=${{ inputs.collect_extended_perf_stats }}" >> $GITHUB_ENV
echo "DEBUG_ENVIRONMENT=${{ inputs.debug_environment }}" >> $GITHUB_ENV
echo "RUN_MICRO_BENCH=${{ inputs.run_micro_bench }}" >> $GITHUB_ENV
Expand Down Expand Up @@ -231,6 +242,7 @@ jobs:
cp ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini.dist ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini
sed -i "s|INFRA_DOCKER_REGISTRY=public.ecr.aws/abcdefgh|INFRA_DOCKER_REGISTRY=${{ secrets.PHP_VERSION_BENCHMARK_DOCKER_REGISTRY }}|g" ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini
sed -i "s|INFRA_WORKSPACE=|INFRA_WORKSPACE=$WORKSPACE|g" ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini
sed -i "s/INFRA_BINARY_LAYOUT_STRATEGY=/INFRA_BINARY_LAYOUT_STRATEGY=${{ env.BINARY_LAYOUT_STRATEGY }}/g" ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini
sed -i "s/INFRA_COLLECT_EXTENDED_PERF_STATS=0/INFRA_COLLECT_EXTENDED_PERF_STATS=${{ env.COLLECT_EXTENDED_PERF_STATS }}/g" ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini
sed -i "s/INFRA_DEBUG_ENVIRONMENT=0/INFRA_DEBUG_ENVIRONMENT=${{ env.DEBUG_ENVIRONMENT }}/g" ./php-version-benchmarks/config/infra/aws/x86_64-metal.ini

Expand Down
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ PHP NEWS
. Fixed bug GH-23418 (Use-after-free when looking up mounted directories).
(Weilin Du)

- Standard:
. Fixed an out-of-bounds read when following a redirect response with an
empty Location header. (iliaal)


27 Aug 2026, PHP 8.6.0beta2

Expand Down
6 changes: 6 additions & 0 deletions Zend/zend_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,12 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr,
#endif

p = (zend_mm_free_slot*)ptr;
#if ZEND_MM_HEAP_PROTECTION
/* Catch the most common double-free pattern for free. */
if (UNEXPECTED(p == heap->free_slot[bin_num])) {
zend_mm_panic("zend_mm_heap corrupted (double free)");
}
#endif
zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]);
heap->free_slot[bin_num] = p;
}
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/http_fopen_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -1054,15 +1054,15 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,

char *new_path = NULL;

if (strlen(header_info.location) < 8 ||
if (header_info.location_len < 8 ||
(strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
{
char *loc_path = NULL;
if (*header_info.location != '/') {
if (*(header_info.location+1) != '\0' && resource->path) {
if (header_info.location_len > 0 && resource->path) {
char *s = strrchr(ZSTR_VAL(resource->path), '/');
if (!s) {
s = ZSTR_VAL(resource->path);
Expand Down
36 changes: 36 additions & 0 deletions ext/standard/tests/http/http_empty_location_redirect.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Empty Location header must not over-read when building the redirect target
--FILE--
<?php
$serverCode = <<<'CODE'
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
phpt_notify_server_start($server);

for ($n = 0; $n < 4; $n++) {
$conn = stream_socket_accept($server, 10);
if (!$conn) {
break;
}
$req = fgets($conn);
while (trim(fgets($conn)) !== '') {}
$uri = explode(' ', $req)[1];
if ($n < 3) {
fwrite($conn, "HTTP/1.1 302 Found\r\nLocation:\r\nContent-Length: 0\r\n\r\n");
} else {
$body = "uri=$uri";
fwrite($conn, "HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\n\r\n$body");
}
fclose($conn);
}
CODE;

$clientCode = <<<'CODE'
$ctx = stream_context_create(['http' => ['follow_location' => 1]]);
echo @file_get_contents("http://{{ ADDR }}/a/b", false, $ctx), "\n";
CODE;

include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECT--
uri=/
40 changes: 38 additions & 2 deletions sapi/phpdbg/phpdbg_watch.c
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,36 @@ bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *elem
return true;
}

/* watch_recreation is keyed by element->str, so a parent and its child are stored
* under distinct keys and the deduplication in phpdbg_queue_element_for_recreation()
* cannot notice that they belong to the same chain. As phpdbg_free_watch_element_tree()
* frees the whole chain, any other entry referencing a member of it must be dropped
* first, or the chain gets freed twice. The buckets are only nulled out as the hash is
* being iterated over by the callers; they clean it right after. */
static void phpdbg_forget_queued_watch_element(phpdbg_watch_element *element) {
zval *zv = zend_hash_find(&PHPDBG_G(watch_recreation), element->str);
if (zv && Z_PTR_P(zv) == element) {
Z_PTR_P(zv) = NULL;
}
}

static void phpdbg_dequeue_watch_element_tree(phpdbg_watch_element *element) {
phpdbg_watch_element *cur;

for (cur = element->parent; cur; cur = cur->parent) {
phpdbg_forget_queued_watch_element(cur);
}
for (cur = element->child; cur; cur = cur->child) {
phpdbg_forget_queued_watch_element(cur);
}
phpdbg_forget_queued_watch_element(element);
}

void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
phpdbg_watch_element *child = element;

phpdbg_dequeue_watch_element_tree(element);

while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
child = child->child;
}
Expand All @@ -863,6 +891,10 @@ void phpdbg_dequeue_elements_for_recreation(void) {
phpdbg_watch_element *element;

ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
if (!element) {
/* freed along with an already dequeued element of the same chain */
continue;
}
ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong)(uintptr_t) element->parent_container)) {
zval _zv, *zv = &_zv;
Expand Down Expand Up @@ -1641,7 +1673,9 @@ void phpdbg_destroy_watchpoints(void) {

/* unconditionally free all remaining elements to avoid memory leaks */
ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
phpdbg_automatic_dequeue_free(element);
if (element) {
phpdbg_automatic_dequeue_free(element);
}
} ZEND_HASH_FOREACH_END();

/* upon fatal errors etc. (i.e. CG(unclean_shutdown) == 1), some watchpoints may still be active. Ensure memory is not watched anymore for next run. Do not care about memory freeing here, shutdown is unclean and near anyway. */
Expand Down Expand Up @@ -1669,7 +1703,9 @@ void phpdbg_release_watch_elements(void) {
uint32_t guard;

ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
phpdbg_automatic_dequeue_free(element);
if (element) {
phpdbg_automatic_dequeue_free(element);
}
} ZEND_HASH_FOREACH_END();
zend_hash_clean(&PHPDBG_G(watch_recreation));

Expand Down