diff --git a/NEWS b/NEWS index 8bd02dc6d6ac..1f9ff0cd76bd 100644 --- a/NEWS +++ b/NEWS @@ -94,6 +94,8 @@ PHP NEWS exits with an uninitialized opline). (waroad) . Fixed use-after-free in JIT ASSIGN_DIM_OP when concatenating into an empty array element. (jorgsowa) + . Fixed memory leak in JIT ASSIGN_DIM when the assignment fails. + (jorgsowa) - PDO: . Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated. diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 9504b5bf79ec..b317a1eac9bc 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -922,10 +922,6 @@ static zval* ZEND_FASTCALL zend_jit_fetch_dim_w_helper(zend_array *ht, zval *dim ZVAL_NULL(EX_VAR(opline->result.var)); } } - if (opline->opcode == ZEND_ASSIGN_DIM - && ((opline+1)->op1_type & (IS_VAR | IS_TMP_VAR))) { - zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); - } return NULL; } ZEND_FALLTHROUGH; @@ -1003,13 +999,6 @@ static zval* ZEND_FASTCALL zend_jit_fetch_dim_w_helper(zend_array *ht, zval *dim default: zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_ARRAY), dim, BP_VAR_R); undef_result_after_exception(); - if (EG(opline_before_exception) - && (EG(opline_before_exception)+1)->opcode == ZEND_OP_DATA - && ((EG(opline_before_exception)+1)->op1_type & (IS_VAR|IS_TMP_VAR))) { - zend_execute_data *execute_data = EG(current_execute_data); - - zval_ptr_dtor_nogc(EX_VAR((EG(opline_before_exception)+1)->op1.var)); - } return NULL; } @@ -1472,6 +1461,31 @@ static void ZEND_FASTCALL zend_jit_fetch_dim_obj_rw_helper(zval *object_ptr, zva // zend_jit_fetch_dim_obj_helper(object_ptr, dim, result, BP_VAR_UNSET); //} +static zval* ZEND_FASTCALL zend_jit_assign_dim_fetch_w_helper(zend_array *ht, zval *dim) +{ + zend_execute_data *execute_data = EG(current_execute_data); + /* EX(opline) points to HANDLE_EXCEPTION once the fetch throws */ + const zend_op *opline = EX(opline); + zval *retval = zend_jit_fetch_dim_w_helper(ht, dim); + + if (UNEXPECTED(!retval)) { + zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); + } + return retval; +} + +static ZEND_COLD void ZEND_FASTCALL zend_jit_assign_dim_cannot_add_element(void) +{ + zend_execute_data *execute_data = EG(current_execute_data); + const zend_op *opline = EX(opline); + + if (opline->result_type & (IS_VAR | IS_TMP_VAR)) { + ZVAL_UNDEF(EX_VAR(opline->result.var)); + } + zend_cannot_add_element(); + zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); +} + static void ZEND_FASTCALL zend_jit_assign_dim_helper(zval *object_ptr, zval *dim, zval *value, zval *result) { if (EXPECTED(Z_TYPE_P(object_ptr) == IS_OBJECT)) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 536cef3dac70..26f22a7dbc93 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -243,6 +243,7 @@ static size_t tsrm_tls_offset = 0; _(undefined_offset, IR_FUNCTION | IR_FASTCALL_FUNC) \ _(undefined_key, IR_FUNCTION | IR_FASTCALL_FUNC) \ _(cannot_add_element, IR_FUNCTION | IR_FASTCALL_FUNC) \ + _(assign_dim_cannot_add_element, IR_FUNCTION | IR_FASTCALL_FUNC) \ _(assign_const, IR_FUNCTION | IR_FASTCALL_FUNC) \ _(assign_tmp, IR_FUNCTION | IR_FASTCALL_FUNC) \ _(assign_var, IR_FUNCTION | IR_FASTCALL_FUNC) \ @@ -2158,6 +2159,12 @@ static int zend_jit_invalid_this_stub(zend_jit_ctx *jit) return 1; } +static int zend_jit_assign_dim_cannot_add_element_stub(zend_jit_ctx *jit) +{ + ir_TAILCALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_dim_cannot_add_element)); + return 1; +} + static int zend_jit_undefined_function_stub(zend_jit_ctx *jit) { // JIT: load EX(opline) @@ -3124,6 +3131,7 @@ static void zend_jit_setup_disasm(void) REGISTER_HELPER(zend_jit_fetch_dim_isset_helper); REGISTER_HELPER(zend_jit_fetch_dim_rw_helper); REGISTER_HELPER(zend_jit_fetch_dim_w_helper); + REGISTER_HELPER(zend_jit_assign_dim_fetch_w_helper); REGISTER_HELPER(zend_jit_fetch_dim_str_offset_r_helper); REGISTER_HELPER(zend_jit_fetch_dim_str_r_helper); REGISTER_HELPER(zend_jit_fetch_dim_str_is_helper); @@ -12417,7 +12425,11 @@ static int zend_jit_fetch_dimension_address_inner(zend_jit_ctx *jit, ir_END_list(*end_inputs); break; case BP_VAR_W: - ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_dim_w_helper), ht_ref, ref); + if (opline->opcode == ZEND_ASSIGN_DIM && ((opline+1)->op1_type & (IS_VAR|IS_TMP_VAR))) { + ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_assign_dim_fetch_w_helper), ht_ref, ref); + } else { + ref = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(zend_jit_fetch_dim_w_helper), ht_ref, ref); + } if_found = ir_IF(ref); ir_IF_TRUE(if_found); ir_refs_add(found_inputs, ir_END()); @@ -13352,7 +13364,11 @@ static int zend_jit_assign_dim(zend_jit_ctx *jit, // JIT: zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied"); jit_SET_EX_OPLINE(jit, opline); - ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_cannot_add_element, IR_FASTCALL_FUNC)); + if ((opline+1)->op1_type & (IS_VAR|IS_TMP_VAR)) { + ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_assign_dim_cannot_add_element, IR_FASTCALL_FUNC)); + } else { + ir_CALL(IR_VOID, jit_STUB_FUNC_ADDR(jit, jit_stub_cannot_add_element, IR_FASTCALL_FUNC)); + } ir_END_list(end_inputs); diff --git a/ext/opcache/tests/jit/assign_dim_018.phpt b/ext/opcache/tests/jit/assign_dim_018.phpt new file mode 100644 index 000000000000..3fee8d5a70ac --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_018.phpt @@ -0,0 +1,31 @@ +--TEST-- +JIT ASSIGN_DIM: OP_DATA is freed when appending to a full array fails +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=function +--EXTENSIONS-- +opcache +--FILE-- + 1]; + $a[] = new D; +} + +try { + test(); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} +?> +--EXPECT-- +dtor +Error: Cannot add element to the array as the next element is already occupied diff --git a/ext/opcache/tests/jit/assign_dim_019.phpt b/ext/opcache/tests/jit/assign_dim_019.phpt new file mode 100644 index 000000000000..284757362a2d --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_019.phpt @@ -0,0 +1,34 @@ +--TEST-- +JIT ASSIGN_DIM: OP_DATA is freed when a float key conversion throws +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=function +--EXTENSIONS-- +opcache +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +dtor +Exception: Implicit conversion from float 1.5 to int loses precision diff --git a/ext/opcache/tests/jit/assign_dim_020.phpt b/ext/opcache/tests/jit/assign_dim_020.phpt new file mode 100644 index 000000000000..98f5f9842809 --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_020.phpt @@ -0,0 +1,34 @@ +--TEST-- +JIT ASSIGN_DIM: OP_DATA is freed when a resource key conversion throws +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=function +--EXTENSIONS-- +opcache +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +dtor +Exception: Resource ID#%d used as offset, casting to integer (%d) diff --git a/ext/opcache/tests/jit/assign_dim_021.phpt b/ext/opcache/tests/jit/assign_dim_021.phpt new file mode 100644 index 000000000000..6965ea6979bc --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_021.phpt @@ -0,0 +1,34 @@ +--TEST-- +JIT ASSIGN_DIM: OP_DATA is freed once when a float key conversion throws on a false container +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=function +--EXTENSIONS-- +opcache +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +Exception: Implicit conversion from float 1.5 to int loses precision diff --git a/ext/opcache/tests/jit/assign_dim_022.phpt b/ext/opcache/tests/jit/assign_dim_022.phpt new file mode 100644 index 000000000000..3557466ba879 --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_022.phpt @@ -0,0 +1,34 @@ +--TEST-- +JIT ASSIGN_DIM: OP_DATA is freed once when a resource key conversion throws on a false container +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=function +--EXTENSIONS-- +opcache +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +Exception: Resource ID#%d used as offset, casting to integer (%d)