Skip to content

fix segfault in ZEND_BIND_STATIC #12758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
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
3 changes: 2 additions & 1 deletion Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -8788,6 +8788,8 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF)

variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W);

SAVE_OPLINE();

ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
if (!ht) {
ht = zend_array_dup(EX(func)->op_array.static_variables);
Expand All @@ -8797,7 +8799,6 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF)

value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));

SAVE_OPLINE();
if (opline->extended_value & ZEND_BIND_REF) {
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
if (UNEXPECTED(zval_update_constant_ex(value, EX(func)->op_array.scope) != SUCCESS)) {
Expand Down
3 changes: 2 additions & 1 deletion Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -48471,6 +48471,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN

variable_ptr = EX_VAR(opline->op1.var);

SAVE_OPLINE();

ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
if (!ht) {
ht = zend_array_dup(EX(func)->op_array.static_variables);
Expand All @@ -48480,7 +48482,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_UNUSED_HAN

value = (zval*)((char*)ht->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));

SAVE_OPLINE();
if (opline->extended_value & ZEND_BIND_REF) {
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
if (UNEXPECTED(zval_update_constant_ex(value, EX(func)->op_array.scope) != SUCCESS)) {
Expand Down
3 changes: 3 additions & 0 deletions ext/zend_test/php_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
HashTable global_weakmap;
int replace_zend_execute_ex;
int register_passes;
int observe_opline_in_zendmm;
zend_mm_heap* zend_orig_heap;
zend_mm_heap* zend_test_heap;
zend_test_fiber *active_fiber;
ZEND_END_MODULE_GLOBALS(zend_test)

Expand Down
77 changes: 77 additions & 0 deletions ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@
#include "zend_interfaces.h"
#include "zend_weakrefs.h"
#include "Zend/Optimizer/zend_optimizer.h"
#include "Zend/zend_alloc.h"
#include "test_arginfo.h"

// `php.h` sets `NDEBUG` when not `PHP_DEBUG` which will make `assert()` from
// assert.h a no-op. In order to have `assert()` working on NDEBUG builds, we
// undefine `NDEBUG` and re-include assert.h
#undef NDEBUG
#include "assert.h"

#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
# include <libxml/globals.h>
# include <libxml/parser.h>
Expand Down Expand Up @@ -364,6 +371,68 @@ static ZEND_FUNCTION(zend_test_crash)
php_printf("%s", invalid);
}

static bool has_opline(zend_execute_data *execute_data)
{
return execute_data
&& execute_data->func
&& ZEND_USER_CODE(execute_data->func->type)
&& execute_data->opline
;
}

void * zend_test_custom_malloc(size_t len)
{
if (has_opline(EG(current_execute_data))) {
assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1);
}
return _zend_mm_alloc(ZT_G(zend_orig_heap), len ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC);
}

void zend_test_custom_free(void *ptr)
{
if (has_opline(EG(current_execute_data))) {
assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1);
}
_zend_mm_free(ZT_G(zend_orig_heap), ptr ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC);
}

void * zend_test_custom_realloc(void * ptr, size_t len)
{
if (has_opline(EG(current_execute_data))) {
assert(EG(current_execute_data)->opline->lineno != (uint32_t)-1);
}
return _zend_mm_realloc(ZT_G(zend_orig_heap), ptr, len ZEND_FILE_LINE_EMPTY_CC ZEND_FILE_LINE_EMPTY_CC);
}

static PHP_INI_MH(OnUpdateZendTestObserveOplineInZendMM)
{
if (new_value == NULL) {
return FAILURE;
}

int int_value = zend_ini_parse_bool(new_value);

if (int_value == 1) {
// `zend_mm_heap` is a private struct, so we have not way to find the
// actual size, but 4096 bytes should be enough
ZT_G(zend_test_heap) = malloc(4096);
memset(ZT_G(zend_test_heap), 0, 4096);
zend_mm_set_custom_handlers(
ZT_G(zend_test_heap),
zend_test_custom_malloc,
zend_test_custom_free,
zend_test_custom_realloc
);
ZT_G(zend_orig_heap) = zend_mm_get_heap();
zend_mm_set_heap(ZT_G(zend_test_heap));
} else if (ZT_G(zend_test_heap)) {
free(ZT_G(zend_test_heap));
ZT_G(zend_test_heap) = NULL;
zend_mm_set_heap(ZT_G(zend_orig_heap));
}
return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
}

static ZEND_FUNCTION(zend_test_is_pcre_bundled)
{
ZEND_PARSE_PARAMETERS_NONE();
Expand Down Expand Up @@ -558,6 +627,7 @@ static ZEND_METHOD(ZendTestChildClassWithMethodWithParameterAttribute, override)
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.register_passes", "0", PHP_INI_SYSTEM, OnUpdateBool, register_passes, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.observe_opline_in_zendmm", "0", PHP_INI_ALL, OnUpdateZendTestObserveOplineInZendMM, observe_opline_in_zendmm, zend_zend_test_globals, zend_test_globals)
PHP_INI_END()

void (*old_zend_execute_ex)(zend_execute_data *execute_data);
Expand Down Expand Up @@ -700,6 +770,13 @@ PHP_RSHUTDOWN_FUNCTION(zend_test)
zend_weakrefs_hash_del(&ZT_G(global_weakmap), (zend_object *)(uintptr_t)objptr);
} ZEND_HASH_FOREACH_END();
zend_hash_destroy(&ZT_G(global_weakmap));

if (ZT_G(zend_test_heap)) {
free(ZT_G(zend_test_heap));
ZT_G(zend_test_heap) = NULL;
zend_mm_set_heap(ZT_G(zend_orig_heap));
}

return SUCCESS;
}

Expand Down
33 changes: 33 additions & 0 deletions ext/zend_test/tests/opline_dangling.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
possible segfault in `ZEND_BIND_STATIC`
--DESCRIPTION--
https://github.com/php/php-src/pull/12758
--EXTENSIONS--
zend_test
--INI--
zend_test.observe_opline_in_zendmm=1
--FILE--
<?php

function &ref() {
static $a = 5;
return $a;
}

class Foo {
public static int $i;
public static string $s = "x";
}

var_dump(Foo::$i = "1");
var_dump(Foo::$s, Foo::$i);
var_dump(ref());

echo 'Done.';
?>
--EXPECT--
int(1)
string(1) "x"
int(1)
int(5)
Done.