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
2 changes: 0 additions & 2 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
#include "zend_enum.h"
#include "zend_object_handlers.h"
#include "zend_observer.h"
#include "zend_system_id.h"

#include <stdarg.h>

Expand Down Expand Up @@ -3124,7 +3123,6 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
}
zend_flf_handlers[zend_flf_count] = flf_info->handler;
zend_flf_functions[zend_flf_count] = (zend_function *)reg_function;
zend_add_system_entropy("frameless", ZSTR_VAL(reg_function->function_name), &flf_info->num_args, sizeof(flf_info->num_args));
zend_flf_count++;
flf_info++;
}
Expand Down
15 changes: 15 additions & 0 deletions Zend/zend_system_id.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,21 @@ void zend_finalize_system_id(void)
}
}

/* Cached scripts can refer to offsets of internal functions.
* Effects of disable_functions and frameless functions must be taken into account.
* Therefore, embed the function names in-order.
* Updating third party extensions may change function signatures, arginfo, offsets, ...
* Therefore, also embed the loaded modules and their versions. */
ZEND_HASH_MAP_FOREACH_STR_KEY(CG(function_table), zend_string *name) {
PHP_MD5Update(&context, ZSTR_VAL(name), ZSTR_LEN(name) + 1);
} ZEND_HASH_FOREACH_END();
ZEND_HASH_MAP_FOREACH_PTR(&module_registry, zend_module_entry *mod) {
PHP_MD5Update(&context, mod->name, strlen(mod->name) + 1);
if (EXPECTED(mod->version != NO_VERSION_YET)) {
PHP_MD5Update(&context, mod->version, strlen(mod->version) + 1);
}
} ZEND_HASH_FOREACH_END();

PHP_MD5Final(digest, &context);
php_hash_bin2hex(zend_system_id, digest, sizeof digest);
finalized = 1;
Expand Down
110 changes: 110 additions & 0 deletions ext/opcache/tests/gh17733.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
--TEST--
GH-17733 (System entropy does not take into account function order, resulting in wrong functions possibly called in different configurations)
--EXTENSIONS--
opcache
--FILE--
<?php
$cache = __DIR__ . '/' . 'gh17733';
$script = $cache . '/' . 'call.php';
mkdir($cache);

function write_script(string $script, int $version): void {
file_put_contents($script, <<<PHP
<?php
echo "v$version\n";
try {
var_dump(str_repeat("ab", 3));
} catch (\Throwable \$e) {
echo \$e::class, ": ", \$e->getMessage(), "\n";
}
PHP);
}

function run(string $cache, string $script, string $disable_functions): void {
$ext = ini_get('extension_dir');
$opcache = PHP_OS_FAMILY === 'Windows' ? 'php_opcache.dll' : 'opcache.so';
$cmd = [PHP_BINARY, '-n', '-d', 'extension_dir="' . $ext . '"'];
if (file_exists($ext . '/' . $opcache)) {
array_push($cmd, '-d', 'zend_extension=opcache');
}
array_push($cmd,
'-d', 'opcache.enable_cli=1',
'-d', 'opcache.file_cache="' . $cache . '"',
'-d', 'opcache.file_cache_only=1',
'-d', 'opcache.file_update_protection=0',
'-d', 'opcache.validate_timestamps=0', // Important to tell apart versions
'-d', 'disable_functions=' . $disable_functions,
$script,
);
$proc = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
echo stream_get_contents($pipes[1]);
proc_close($proc);
}

function system_ids(string $cache): int {
$pattern = PHP_OS_FAMILY === 'Windows' ? '/*/*' : '/*';
return count(glob($cache . $pattern, GLOB_ONLYDIR));
}

write_script($script, 1);
run($cache, $script, '');
var_dump(system_ids($cache));

write_script($script, 2);
echo "--- Same environment reuses the cache ---\n";
run($cache, $script, '');
var_dump(system_ids($cache));

echo "--- Shifted function table ---\n";
run($cache, $script, 'zend_version');
var_dump(system_ids($cache));

echo "--- Disabled function ---\n";
run($cache, $script, 'str_repeat');
var_dump(system_ids($cache));

write_script($script, 3);
echo "--- Each environment reuses its own cache ---\n";
run($cache, $script, 'zend_version');
run($cache, $script, 'str_repeat');
run($cache, $script, '');
var_dump(system_ids($cache));
?>
--CLEAN--
<?php
$cache = __DIR__ . '/' . 'gh17733';
if (is_dir($cache)) {
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($cache, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($it as $file) {
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
}
rmdir($cache);
}
?>
--EXPECT--
v1
string(6) "ababab"
int(1)
--- Same environment reuses the cache ---
v1
string(6) "ababab"
int(1)
--- Shifted function table ---
v2
string(6) "ababab"
int(2)
--- Disabled function ---
v2
Error: Call to undefined function str_repeat()
int(3)
--- Each environment reuses its own cache ---
v2
string(6) "ababab"
v2
Error: Call to undefined function str_repeat()
v1
string(6) "ababab"
int(3)
21 changes: 1 addition & 20 deletions ext/opcache/zend_file_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -532,32 +532,13 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
}
if (opline->op2_type == IS_CONST) {
SERIALIZE_PTR(opline->op2.zv);

/* See GH-17733. Reset Z_EXTRA_P(op2) of ZEND_INIT_FCALL, which
* is an offset into the global function table, to avoid calling
* incorrect functions when environment changes. This, and the
* equivalent code below, can be removed once proper system ID
* validation is implemented. */
if (opline->opcode == ZEND_INIT_FCALL) {
zval *op2 = opline->op2.zv;
UNSERIALIZE_PTR(op2);
Z_EXTRA_P(op2) = 0;
ZEND_VM_SET_OPCODE_HANDLER(opline);
}
}
#else
if (opline->op1_type == IS_CONST) {
opline->op1.constant = RT_CONSTANT(opline, opline->op1) - literals;
}
if (opline->op2_type == IS_CONST) {
zval *op2 = RT_CONSTANT(opline, opline->op2);
opline->op2.constant = op2 - literals;

/* See GH-17733 and comment above. */
if (opline->opcode == ZEND_INIT_FCALL) {
Z_EXTRA_P(op2) = 0;
ZEND_VM_SET_OPCODE_HANDLER(opline);
}
opline->op2.constant = RT_CONSTANT(opline, opline->op2) - literals;
}
#endif
#if ZEND_USE_ABS_JMP_ADDR
Expand Down
Loading