Skip to content

Commit 75d77cb

Browse files
committed
Zend: Skip frameless registration for temporary modules
dl() modules are MODULE_TEMPORARY. Their function records are freed at request shutdown, but zend_flf_functions keeps those pointers for the process lifetime. A later request can match the stale handler address and use the freed record. Temporary modules leave the frameless info unset, so the call uses the ordinary internal path. Closes GH-24008
1 parent e216cf6 commit 75d77cb

9 files changed

Lines changed: 135 additions & 2 deletions

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ PHP NEWS
2626
. Fixed GH-23980 (ZEND_ASSERT violation @ ZEND_INCLUDE_OR_EVAL). (ndossche)
2727
. Fixed bug GH-23896 (Assertion failure in zend_call_function() after a
2828
throwing deprecation). (lazerg)
29+
. Fixed use-after-free when a dl()-loaded extension declares a frameless
30+
function and a later request calls it. (Ilia Alshanetsky)
2931

3032
- DOM:
3133
. Fixed use-after-free when re-constructing a DOMXPath whose php:function

‎Zend/zend_API.c‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2976,7 +2976,11 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
29762976
internal_function->prototype = NULL;
29772977
internal_function->prop_info = NULL;
29782978
internal_function->attributes = NULL;
2979-
internal_function->frameless_function_infos = ptr->frameless_function_infos;
2979+
if (type == MODULE_TEMPORARY) {
2980+
internal_function->frameless_function_infos = NULL;
2981+
} else {
2982+
internal_function->frameless_function_infos = ptr->frameless_function_infos;
2983+
}
29802984
if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
29812985
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
29822986
} else {

‎ext/dl_test/dl_test.c‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ PHP_FUNCTION(dl_test_test2)
5252
}
5353
/* }}}*/
5454

55+
PHP_FUNCTION(dl_test_frameless)
56+
{
57+
zend_long value;
58+
59+
ZEND_PARSE_PARAMETERS_START(1, 1)
60+
Z_PARAM_LONG(value)
61+
ZEND_PARSE_PARAMETERS_END();
62+
63+
RETURN_LONG(value);
64+
}
65+
66+
ZEND_FRAMELESS_FUNCTION(dl_test_frameless, 1)
67+
{
68+
zend_long value;
69+
70+
Z_FLF_PARAM_LONG(1, value);
71+
72+
RETVAL_LONG(value);
73+
74+
flf_clean:;
75+
}
76+
5577
/* {{{ PHP_DL_TEST_USE_REGISTER_FUNCTIONS_DIRECTLY */
5678
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_dl_test_use_register_functions_directly, 0, 0, IS_STRING, 0)
5779
ZEND_END_ARG_INFO()

‎ext/dl_test/dl_test.stub.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ function dl_test_test1(): void {}
99

1010
function dl_test_test2(string $str = ""): string {}
1111

12+
/**
13+
* @frameless-function {"arity": 1}
14+
*/
15+
function dl_test_frameless(int $value): int {}
16+
1217
class DlTest {
1318
public function test(string $str = ""): string {}
1419
}

‎ext/dl_test/dl_test_arginfo.h‎

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
var_dump(dl_test_frameless(7));
3+
try {
4+
dl_test_frameless("nope");
5+
echo "no throw\n";
6+
} catch (TypeError $e) {
7+
echo $e->getMessage(), "\n";
8+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
dl() of a frameless function does not keep the freed function record
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY === 'Windows') {
6+
die('skip setarch -R is required so dl() remaps the extension at the stale handler address');
7+
}
8+
if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
9+
die('skip php-cgi not available');
10+
}
11+
$setarch = trim((string) shell_exec('command -v setarch'));
12+
if ($setarch === '') {
13+
die('skip setarch -R is required so dl() remaps the extension at the stale handler address');
14+
}
15+
$arch = php_uname('m');
16+
exec($setarch . ' ' . escapeshellarg($arch) . ' -R true', $setarch_out, $setarch_code);
17+
if ($setarch_code !== 0) {
18+
die('skip setarch -R cannot run on ' . $arch);
19+
}
20+
$so = ini_get('extension_dir') . DIRECTORY_SEPARATOR . 'dl_test.so';
21+
if (!file_exists($so)) {
22+
die('skip dl_test extension is not built (tried ' . $so . ')');
23+
}
24+
?>
25+
--FILE--
26+
<?php
27+
$cmd = 'env -u SCRIPT_FILENAME -u PATH_TRANSLATED -u REDIRECT_STATUS -u REQUEST_METHOD -u QUERY_STRING'
28+
. ' USE_ZEND_ALLOC=0 ASAN_OPTIONS=' . escapeshellarg('detect_leaks=0:halt_on_error=1:abort_on_error=1')
29+
. ' setarch ' . escapeshellarg(php_uname('m')) . ' -R '
30+
. escapeshellarg(getenv('TEST_PHP_CGI_EXECUTABLE'))
31+
. ' -n -q -T 2 -d enable_dl=1 -d extension_dir=' . escapeshellarg(ini_get('extension_dir'))
32+
. ' ' . escapeshellarg(__DIR__ . '/frameless_temporary_cgi.inc');
33+
$proc = proc_open($cmd, [
34+
0 => ['pipe', 'r'],
35+
1 => ['pipe', 'w'],
36+
2 => ['pipe', 'w'],
37+
], $pipes);
38+
fclose($pipes[0]);
39+
$out = stream_get_contents($pipes[1]);
40+
stream_get_contents($pipes[2]);
41+
fclose($pipes[1]);
42+
fclose($pipes[2]);
43+
echo $out;
44+
$code = proc_close($proc);
45+
if ($code !== 0) {
46+
echo "exit:$code\n";
47+
}
48+
?>
49+
--EXPECT--
50+
int(7)
51+
dl_test_frameless(): Argument #1 ($value) must be of type int, string given
52+
int(7)
53+
dl_test_frameless(): Argument #1 ($value) must be of type int, string given
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
$ext = PHP_OS_FAMILY === 'Windows' ? 'php_dl_test.dll' : 'dl_test.so';
3+
if (!dl($ext)) {
4+
echo "dl failed\n";
5+
return;
6+
}
7+
include __DIR__ . '/frameless_temporary.inc';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
dl() does not compile calls to a temporary module's function as frameless
3+
--SKIPIF--
4+
<?php include __DIR__ . "/skip.inc"; ?>
5+
--EXTENSIONS--
6+
opcache
7+
--INI--
8+
enable_dl=1
9+
opcache.enable_cli=1
10+
opcache.opt_debug_level=0x10000
11+
--FILE--
12+
<?php
13+
dl(PHP_OS_FAMILY === 'Windows' ? 'php_dl_test.dll' : 'dl_test.so');
14+
include __DIR__ . '/frameless_temporary.inc';
15+
?>
16+
--EXPECTF--
17+
%A0001 INIT_FCALL 1 %d string("dl_test_frameless")
18+
%Aint(7)
19+
dl_test_frameless(): Argument #1 ($value) must be of type int, string given

0 commit comments

Comments
 (0)