Skip to content
Open
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
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ PHP NEWS
(ndossche)
. Fixed JIT ignoring exceptions from typed property set hooks. (David Carlier)

- PCNTL:
. Added the $rekey_free_lists parameter to pcntl_fork(), pcntl_rfork() and
pcntl_forkx() to optionally avoid re-keying inherited free lists
(GH-25403). (Alessio Attilio)

- PDO:
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
column index. (Ilia Alshanetsky)
Expand Down
8 changes: 8 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ PHP 8.7 UPGRADE NOTES
64 pipe or file handles no longer crashes; it fails with a warning, as that
path remains bounded by WaitForMultipleObjects().

- PCNTL:
. pcntl_fork(), pcntl_rfork() and pcntl_forkx() now accept an optional
$rekey_free_lists argument, which defaults to true. Set it to false to avoid
rewriting the child's inherited small free lists. These free blocks will
not be reused by the child, but per-child allocator key isolation is
preserved. Cached chunks are still re-keyed, and the usual child
initialization is performed.

========================================
6. New Functions
========================================
Expand Down
45 changes: 30 additions & 15 deletions Zend/zend_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2264,27 +2264,32 @@ static void zend_mm_init_key(zend_mm_heap *heap)
zend_mm_refresh_key(heap);
}

ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap)
static void zend_mm_refresh_key_child_ex(zend_mm_heap *heap, bool rekey_free_lists)
{
uintptr_t old_key = heap->shadow_key;

zend_mm_init_key(heap);

/* Update shadow pointers with new key */
for (uint32_t i = 0; i < ZEND_MM_BINS; i++) {
zend_mm_free_slot *slot = heap->free_slot[i];
if (!slot) {
continue;
}
zend_mm_free_slot *next;
while ((next = slot->next_free_slot)) {
zend_mm_free_slot **shadow_addr = ZEND_MM_FREE_SLOT_PTR_SHADOW_ADDR(slot, i);
if (UNEXPECTED(next != zend_mm_decode_free_slot_key(old_key, shadow_addr, *shadow_addr))) {
zend_mm_panic("zend_mm_heap corrupted");
if (rekey_free_lists) {
/* Update shadow pointers with new key */
for (uint32_t i = 0; i < ZEND_MM_BINS; i++) {
zend_mm_free_slot *slot = heap->free_slot[i];
if (!slot) {
continue;
}
zend_mm_free_slot *next;
while ((next = slot->next_free_slot)) {
zend_mm_free_slot **shadow_addr = ZEND_MM_FREE_SLOT_PTR_SHADOW_ADDR(slot, i);
if (UNEXPECTED(next != zend_mm_decode_free_slot_key(old_key, shadow_addr, *shadow_addr))) {
zend_mm_panic("zend_mm_heap corrupted");
}
zend_mm_set_next_free_slot(heap, i, slot, next);
slot = next;
}
zend_mm_set_next_free_slot(heap, i, slot, next);
slot = next;
}
} else {
/* Avoid touching inherited free-list pages in the child. */
memset(heap->free_slot, 0, sizeof(heap->free_slot));
}

zend_mm_rekey_cached_chunks(heap, old_key);
Expand All @@ -2294,6 +2299,11 @@ ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap)
#endif
}

ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap)
{
zend_mm_refresh_key_child_ex(heap, true);
}

static zend_mm_heap *zend_mm_init(void)
{
zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE);
Expand Down Expand Up @@ -3403,9 +3413,14 @@ ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown)
zend_mm_shutdown(AG(mm_heap), full_shutdown, silent);
}

ZEND_API void refresh_memory_manager_ex(bool rekey_free_lists)
{
zend_mm_refresh_key_child_ex(AG(mm_heap), rekey_free_lists);
}

ZEND_API void refresh_memory_manager(void)
{
zend_mm_refresh_key_child(AG(mm_heap));
refresh_memory_manager_ex(true);
}

ZEND_NORETURN static ZEND_COLD void zend_out_of_memory(void)
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ ZEND_API bool zend_alloc_in_memory_limit_error_reporting(void);
ZEND_API void start_memory_manager(void);
ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown);
ZEND_API void refresh_memory_manager(void);
ZEND_API void refresh_memory_manager_ex(bool rekey_free_lists);
ZEND_API bool is_zend_mm(void);
ZEND_API bool is_zend_ptr(const void *ptr);

Expand Down
21 changes: 15 additions & 6 deletions ext/pcntl/pcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,12 @@ PHP_MINFO_FUNCTION(pcntl)
PHP_FUNCTION(pcntl_fork)
{
pid_t id;
bool rekey_free_lists = true;

ZEND_PARSE_PARAMETERS_NONE();
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(rekey_free_lists)
ZEND_PARSE_PARAMETERS_END();

id = fork();
if (id == -1) {
Expand All @@ -295,7 +299,7 @@ PHP_FUNCTION(pcntl_fork)

}
} else if (id == 0) {
php_child_init();
php_child_init_ex(rekey_free_lists);
}

RETURN_LONG((zend_long) id);
Expand Down Expand Up @@ -1552,11 +1556,13 @@ PHP_FUNCTION(pcntl_rfork)
zend_long flags;
zend_long csignal = 0;
pid_t pid;
bool rekey_free_lists = true;

ZEND_PARSE_PARAMETERS_START(1, 2)
ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_LONG(flags)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(csignal)
Z_PARAM_BOOL(rekey_free_lists)
ZEND_PARSE_PARAMETERS_END();

/* This is a flag to use with great caution in general, preferably not within PHP */
Expand Down Expand Up @@ -1599,7 +1605,7 @@ PHP_FUNCTION(pcntl_rfork)
php_error_docref(NULL, E_WARNING, "Error %d", errno);
}
} else if (pid == 0) {
php_child_init();
php_child_init_ex(rekey_free_lists);
}

RETURN_LONG((zend_long) pid);
Expand All @@ -1616,9 +1622,12 @@ PHP_FUNCTION(pcntl_forkx)
{
zend_long flags;
pid_t pid;
bool rekey_free_lists = true;

ZEND_PARSE_PARAMETERS_START(1, 1)
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_LONG(flags)
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(rekey_free_lists)
ZEND_PARSE_PARAMETERS_END();

if (flags < FORK_NOSIGCHLD || flags > FORK_WAITPID) {
Expand All @@ -1644,7 +1653,7 @@ PHP_FUNCTION(pcntl_forkx)
php_error_docref(NULL, E_WARNING, "Error %d", errno);
}
} else if (pid == 0) {
php_child_init();
php_child_init_ex(rekey_free_lists);
}

RETURN_LONG((zend_long) pid);
Expand Down
6 changes: 3 additions & 3 deletions ext/pcntl/pcntl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,7 @@
const PCNTL_ECAPMODE = UNKNOWN;
#endif

function pcntl_fork(): int {}
function pcntl_fork(bool $rekey_free_lists = true): int {}

/**
* @param int $status
Expand Down Expand Up @@ -1085,11 +1085,11 @@ function pcntl_unshare(int $flags): bool {}
#endif

#ifdef HAVE_RFORK
function pcntl_rfork(int $flags, int $signal = 0): int{}
function pcntl_rfork(int $flags, int $signal = 0, bool $rekey_free_lists = true): int{}
#endif

#ifdef HAVE_FORKX
function pcntl_forkx(int $flags): int{}
function pcntl_forkx(int $flags, bool $rekey_free_lists = true): int{}
#endif

#ifdef HAVE_PIDFD_OPEN
Expand Down
10 changes: 7 additions & 3 deletions ext/pcntl/pcntl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions ext/pcntl/pcntl_decl.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions ext/pcntl/tests/gh25403_cow.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
GH-25403: opting out of child memory re-keying avoids inherited free-list writes
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (PHP_OS !== 'Linux') die('skip Linux minor-fault accounting required');
if (getenv('USE_ZEND_ALLOC') === '0') die('skip Zend allocator required');
?>
--INI--
memory_limit=128M
--FILE--
<?php
$strings = [];
for ($i = 0; $i < 131072; $i++) {
$strings[] = str_repeat('x', 256);
}
foreach ($strings as $i => $value) {
if ($i % 2) unset($strings[$i]);
}
$pid = pcntl_fork(rekey_free_lists: false);
if ($pid === -1) throw new Exception('Fork failed');
if ($pid === 0) {
var_dump(getrusage()['ru_minflt'] < 2048);
exit(0);
}
if (pcntl_waitpid($pid, $status) !== $pid) throw new Exception('Wait failed');
var_dump(pcntl_wifexited($status) && pcntl_wexitstatus($status) === 0);
?>
--EXPECT--
bool(true)
bool(true)
48 changes: 48 additions & 0 deletions ext/pcntl/tests/gh25403_fork.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--TEST--
pcntl_fork() preserves child initialization with optional memory re-keying
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists('pcntl_fork')) die('skip pcntl_fork unavailable');
?>
--FILE--
<?php
function checkChild(bool $rekey): void {
$objects = [];
for ($i = 0; $i < 8192; $i++) {
$objects[] = str_repeat('x', 128 + $i % 64);
}
foreach ($objects as $i => $value) {
if ($i % 2) unset($objects[$i]);
}
realpath(__FILE__);
$pid = pcntl_fork(rekey_free_lists: $rekey);
if ($pid === -1) throw new Exception('Fork failed');
if ($pid === 0) {
if (array_key_exists(__FILE__, realpath_cache_get())) exit(1);
for ($i = 0; $i < 8192; $i++) {
$objects[] = str_repeat('y', 128 + $i % 64);
}
gc_mem_caches();
$pid = pcntl_fork(rekey_free_lists: !$rekey);
if ($pid === -1) exit(1);
if ($pid === 0) {
unset($objects);
gc_mem_caches();
exit(0);
}
if (pcntl_waitpid($pid, $status) !== $pid) exit(1);
exit(pcntl_wifexited($status) ? pcntl_wexitstatus($status) : 1);
}
var_dump(pcntl_waitpid($pid, $status) === $pid);
var_dump(pcntl_wifexited($status) && pcntl_wexitstatus($status) === 0);
}
checkChild(false);
checkChild(true);
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
48 changes: 48 additions & 0 deletions ext/pcntl/tests/gh25403_forkx.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--TEST--
pcntl_forkx() preserves child initialization with optional memory re-keying
--EXTENSIONS--
pcntl
--SKIPIF--
<?php
if (!function_exists('pcntl_forkx')) die('skip pcntl_forkx unavailable');
?>
--FILE--
<?php
function checkChild(bool $rekey): void {
$objects = [];
for ($i = 0; $i < 8192; $i++) {
$objects[] = str_repeat('x', 128 + $i % 64);
}
foreach ($objects as $i => $value) {
if ($i % 2) unset($objects[$i]);
}
realpath(__FILE__);
$pid = pcntl_forkx(FORK_WAITPID, rekey_free_lists: $rekey);
if ($pid === -1) throw new Exception('Fork failed');
if ($pid === 0) {
if (array_key_exists(__FILE__, realpath_cache_get())) exit(1);
for ($i = 0; $i < 8192; $i++) {
$objects[] = str_repeat('y', 128 + $i % 64);
}
gc_mem_caches();
$pid = pcntl_fork(rekey_free_lists: !$rekey);
if ($pid === -1) exit(1);
if ($pid === 0) {
unset($objects);
gc_mem_caches();
exit(0);
}
if (pcntl_waitpid($pid, $status) !== $pid) exit(1);
exit(pcntl_wifexited($status) ? pcntl_wexitstatus($status) : 1);
}
var_dump(pcntl_waitpid($pid, $status) === $pid);
var_dump(pcntl_wifexited($status) && pcntl_wexitstatus($status) === 0);
}
checkChild(false);
checkChild(true);
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
Loading
Loading