From b66039db333f730be60c6f6e1925eeb01220e4eb Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 29 Aug 2016 12:02:50 +0300 Subject: [PATCH] Fixed bug #72944 (Null pointer deref in zval_delref_p). --- NEWS | 1 + Zend/tests/bug72944.phpt | 12 +++++++++++ Zend/zend_compile.c | 34 +++++++++++++++++++++++++++++- Zend/zend_compile.h | 1 + ext/opcache/Optimizer/block_pass.c | 33 ++++++++++++++++++++++++++--- 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/bug72944.phpt diff --git a/NEWS b/NEWS index 12cc50d0648..522076e9c74 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ PHP NEWS ?? ??? 2016 PHP 7.0.11 - Core: + . Fixed bug #72944 (Null pointer deref in zval_delref_p). (Dmitry) . Fixed bug #72943 (assign_dim on string doesn't reset hval). (Laruence) . Fixed bug #72911 (Memleak in zend_binary_assign_op_obj_helper). (Laruence) . Fixed bug #72813 (Segfault with __get returned by ref). (Laruence) diff --git a/Zend/tests/bug72944.phpt b/Zend/tests/bug72944.phpt new file mode 100644 index 00000000000..541730a22a7 --- /dev/null +++ b/Zend/tests/bug72944.phpt @@ -0,0 +1,12 @@ +--TEST-- +Bug #72944 (Null pointer deref in zval_delref_p). +--FILE-- + +--EXPECTF-- +Notice: Use of undefined constant e - assumed 'e' in %sbug72944.php on line 2 + +Notice: Undefined variable: A in %sbug72944.php on line 2 +OK diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 73ccd4a174c..7c4d9d5e1dc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1944,10 +1944,42 @@ static inline uint32_t zend_emit_jump(uint32_t opnum_target) /* {{{ */ } /* }}} */ +ZEND_API int zend_is_smart_branch(zend_op *opline) /* {{{ */ +{ + switch (opline->opcode) { + case ZEND_IS_IDENTICAL: + case ZEND_IS_NOT_IDENTICAL: + case ZEND_IS_EQUAL: + case ZEND_IS_NOT_EQUAL: + case ZEND_IS_SMALLER: + case ZEND_IS_SMALLER_OR_EQUAL: + case ZEND_CASE: + case ZEND_ISSET_ISEMPTY_VAR: + case ZEND_ISSET_ISEMPTY_DIM_OBJ: + case ZEND_ISSET_ISEMPTY_PROP_OBJ: + case ZEND_INSTANCEOF: + case ZEND_TYPE_CHECK: + case ZEND_DEFINED: + return 1; + default: + return 0; + } +} +/* }}} */ + static inline uint32_t zend_emit_cond_jump(zend_uchar opcode, znode *cond, uint32_t opnum_target) /* {{{ */ { uint32_t opnum = get_next_op_number(CG(active_op_array)); - zend_op *opline = zend_emit_op(NULL, opcode, cond, NULL); + zend_op *opline; + + if ((cond->op_type & (IS_CV|IS_CONST)) + && opnum > 0 + && zend_is_smart_branch(CG(active_op_array)->opcodes + opnum - 1)) { + /* emit extra NOP to avoid incorrect SMART_BRANCH in very rare cases */ + zend_emit_op(NULL, ZEND_NOP, NULL, NULL); + opnum = get_next_op_number(CG(active_op_array)); + } + opline = zend_emit_op(NULL, opcode, cond, NULL); opline->op2.opline_num = opnum_target; return opnum; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index bbd2df7f2e8..f946448ffab 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -761,6 +761,7 @@ ZEND_API char *zend_make_compiled_string_description(const char *name); ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify_handlers); uint32_t zend_get_class_fetch_type(zend_string *name); ZEND_API zend_uchar zend_get_call_op(zend_uchar init_op, zend_function *fbc); +ZEND_API int zend_is_smart_branch(zend_op *opline); typedef zend_bool (*zend_auto_global_callback)(zend_string *name); typedef struct _zend_auto_global { diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index 5e9b20d191e..4b532c4b206 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -561,7 +561,7 @@ static void zend_rebuild_access_path(zend_cfg *cfg, zend_op_array *op_array, int convert_to_string((v)); \ } -static void strip_nop(zend_code_block *block, zend_optimizer_ctx *ctx) +static void strip_nop(zend_code_block *block, zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *opline = block->start_opline; zend_op *end, *new_end; @@ -575,6 +575,14 @@ static void strip_nop(zend_code_block *block, zend_optimizer_ctx *ctx) } return; } + if (block->len == 2 + && ((block->start_opline + 1)->opcode == ZEND_JMPZ + || (block->start_opline + 1)->opcode == ZEND_JMPNZ) + && (block->start_opline + 1)->op1_type & (IS_CV|IS_CONST) + && block->start_opline > op_array->opcodes + && zend_is_smart_branch(block->start_opline - 1)) { + break; + } block->start_opline++; block->start_opline_no++; block->len--; @@ -588,10 +596,21 @@ static void strip_nop(zend_code_block *block, zend_optimizer_ctx *ctx) zend_op *src; int len = 0; + src = opline; while (opline < end && opline->opcode == ZEND_NOP) { + if (opline + 1 < end + && ((opline + 1)->opcode == ZEND_JMPZ + || (opline + 1)->opcode == ZEND_JMPNZ) + && (opline + 1)->op1_type & (IS_CV|IS_CONST) + && opline > op_array->opcodes + && zend_is_smart_branch(opline - 1)) { + /* don't remove NOP, that splits incorrect smart branch */ + opline++; + break; + } + src++; opline++; } - src = opline; while (opline < end && opline->opcode != ZEND_NOP) { opline++; @@ -621,6 +640,14 @@ static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, if (block->follow_to) { delete_code_block(block, ctx); } + if (block->len == 2 + && ((block->start_opline + 1)->opcode == ZEND_JMPZ + || (block->start_opline + 1)->opcode == ZEND_JMPNZ) + && (block->start_opline + 1)->op1_type & (IS_CV|IS_CONST) + && block->start_opline > op_array->opcodes + && zend_is_smart_branch(block->start_opline - 1)) { + break; + } return; } block->start_opline++; @@ -1137,7 +1164,7 @@ static void zend_optimize_block(zend_code_block *block, zend_op_array *op_array, opline++; } - strip_nop(block, ctx); + strip_nop(block, op_array, ctx); } /* Rebuild plain (optimized) op_array from CFG */