Skip to content

Commit 0f2cdbf

Browse files
committed
Introduce extra counter to avoid RTD key collisions
Also generate a fatal error if a collision occurs in zend_compile. This is not perfect, because collisions might still be introduced via opcache, if one file is included multiple times during a request, invalidate in the meantime and recompiled by different processes. This still needs to be addressed, but this patch fixes the much more common case of collisions occuring when opcache is not used. Fixes bug #78903.
1 parent 79376ab commit 0f2cdbf

File tree

5 files changed

+39
-33
lines changed

5 files changed

+39
-33
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ PHP NEWS
99

1010
- OPcache:
1111
. Fixed bug #78950 (Preloading trait method with static variables). (Nikita)
12+
. Fixed bug #78903 (Conflict in RTD key for closures results in crash).
13+
(Nikita)
1214

1315
19 Dec 2019, PHP 7.4.1
1416

Zend/zend.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ static void zend_set_default_compile_time_values(void) /* {{{ */
524524
/* default compile-time values */
525525
CG(short_tags) = short_tags_default;
526526
CG(compiler_options) = compiler_options_default;
527+
528+
CG(rtd_key_counter) = 0;
527529
}
528530
/* }}} */
529531

Zend/zend_compile.c

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,11 @@ static void zend_destroy_property_info_internal(zval *zv) /* {{{ */
127127
}
128128
/* }}} */
129129

130-
static zend_string *zend_build_runtime_definition_key(zend_string *name, unsigned char *lex_pos) /* {{{ */
130+
static zend_string *zend_build_runtime_definition_key(zend_string *name, uint32_t start_lineno) /* {{{ */
131131
{
132-
zend_string *result;
133-
char char_pos_buf[32];
134-
size_t char_pos_len = sprintf(char_pos_buf, "%p", lex_pos);
135132
zend_string *filename = CG(active_op_array)->filename;
136-
137-
/* NULL, name length, filename length, last accepting char position length */
138-
result = zend_string_alloc(1 + ZSTR_LEN(name) + ZSTR_LEN(filename) + char_pos_len, 0);
139-
sprintf(ZSTR_VAL(result), "%c%s%s%s", '\0', ZSTR_VAL(name), ZSTR_VAL(filename), char_pos_buf);
133+
zend_string *result = zend_strpprintf(0, "%c%s%s:%" PRIu32 "$%" PRIx32,
134+
'\0', ZSTR_VAL(name), ZSTR_VAL(filename), start_lineno, CG(rtd_key_counter)++);
140135
return zend_new_interned_string(result);
141136
}
142137
/* }}} */
@@ -5924,8 +5919,11 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as
59245919
return;
59255920
}
59265921

5927-
key = zend_build_runtime_definition_key(lcname, decl->lex_pos);
5928-
zend_hash_update_ptr(CG(function_table), key, op_array);
5922+
key = zend_build_runtime_definition_key(lcname, decl->start_lineno);
5923+
if (!zend_hash_add_ptr(CG(function_table), key, op_array)) {
5924+
zend_error_noreturn(E_ERROR,
5925+
"Runtime definition key collision for function %s. This is a bug", ZSTR_VAL(name));
5926+
}
59295927

59305928
if (op_array->fn_flags & ZEND_ACC_CLOSURE) {
59315929
opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL);
@@ -6347,16 +6345,11 @@ void zend_compile_implements(zend_ast *ast) /* {{{ */
63476345
}
63486346
/* }}} */
63496347

6350-
static zend_string *zend_generate_anon_class_name(unsigned char *lex_pos) /* {{{ */
6348+
static zend_string *zend_generate_anon_class_name(uint32_t start_lineno) /* {{{ */
63516349
{
6352-
zend_string *result;
6353-
char char_pos_buf[32];
6354-
size_t char_pos_len = sprintf(char_pos_buf, "%p", lex_pos);
63556350
zend_string *filename = CG(active_op_array)->filename;
6356-
6357-
/* NULL, name length, filename length, last accepting char position length */
6358-
result = zend_string_alloc(sizeof("class@anonymous") + ZSTR_LEN(filename) + char_pos_len, 0);
6359-
sprintf(ZSTR_VAL(result), "class@anonymous%c%s%s", '\0', ZSTR_VAL(filename), char_pos_buf);
6351+
zend_string *result = zend_strpprintf(0, "class@anonymous%c%s:%" PRIu32 "$%" PRIx32,
6352+
'\0', ZSTR_VAL(filename), start_lineno, CG(rtd_key_counter)++);
63606353
return zend_new_interned_string(result);
63616354
}
63626355
/* }}} */
@@ -6396,7 +6389,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
63966389

63976390
zend_register_seen_symbol(lcname, ZEND_SYMBOL_CLASS);
63986391
} else {
6399-
name = zend_generate_anon_class_name(decl->lex_pos);
6392+
name = zend_generate_anon_class_name(decl->start_lineno);
64006393
lcname = zend_string_tolower(name);
64016394
}
64026395
lcname = zend_new_interned_string(lcname);
@@ -6553,20 +6546,19 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
65536546
opline->extended_value = zend_alloc_cache_slot();
65546547
opline->result_type = IS_VAR;
65556548
opline->result.var = get_temporary_variable();
6556-
65576549
if (!zend_hash_add_ptr(CG(class_table), lcname, ce)) {
6558-
/* this anonymous class has been included */
6559-
zval zv;
6560-
ZVAL_PTR(&zv, ce);
6561-
destroy_zend_class(&zv);
6562-
return opline;
6550+
zend_error_noreturn(E_ERROR,
6551+
"Runtime definition key collision for %s. This is a bug", ZSTR_VAL(name));
65636552
}
65646553
} else {
6565-
zend_string *key = zend_build_runtime_definition_key(lcname, decl->lex_pos);
6554+
zend_string *key = zend_build_runtime_definition_key(lcname, decl->start_lineno);
65666555

65676556
/* RTD key is placed after lcname literal in op1 */
65686557
zend_add_literal_string(&key);
6569-
zend_hash_update_ptr(CG(class_table), key, ce);
6558+
if (!zend_hash_add_ptr(CG(class_table), key, ce)) {
6559+
zend_error_noreturn(E_ERROR,
6560+
"Runtime definition key collision for class %s. This is a bug", ZSTR_VAL(name));
6561+
}
65706562

65716563
opline->opcode = ZEND_DECLARE_CLASS;
65726564
if (extends_ast && toplevel

Zend/zend_globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ struct _zend_compiler_globals {
127127

128128
HashTable *delayed_variance_obligations;
129129
HashTable *delayed_autoloads;
130+
131+
uint32_t rtd_key_counter;
130132
};
131133

132134

ext/opcache/zend_accelerator_util_funcs.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,16 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source)
422422
t = zend_hash_find_ex(target, p->key, 1);
423423
if (UNEXPECTED(t != NULL)) {
424424
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
425-
/* Mangled key */
426-
t = zend_hash_update(target, p->key, &p->val);
425+
/* Runtime definition key. There are two circumstances under which the key can
426+
* already be defined:
427+
* 1. The file has been re-included without being changed in the meantime. In
428+
* this case we can keep the old value, because we know that the definition
429+
* hasn't changed.
430+
* 2. The file has been changed in the meantime, but the RTD key ends up colliding.
431+
* This would be a bug.
432+
* As we can't distinguish these cases, we assume that it is 1. and keep the old
433+
* value. */
434+
continue;
427435
} else {
428436
goto failure;
429437
}
@@ -466,8 +474,8 @@ static void zend_accel_function_hash_copy_from_shm(HashTable *target, HashTable
466474
t = zend_hash_find_ex(target, p->key, 1);
467475
if (UNEXPECTED(t != NULL)) {
468476
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
469-
/* Mangled key */
470-
zend_hash_update_ptr(target, p->key, Z_PTR(p->val));
477+
/* See comment in zend_accel_function_hash_copy(). */
478+
continue;
471479
} else {
472480
goto failure;
473481
}
@@ -509,7 +517,7 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source)
509517
t = zend_hash_find_ex(target, p->key, 1);
510518
if (UNEXPECTED(t != NULL)) {
511519
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
512-
/* Mangled key - ignore and wait for runtime */
520+
/* See comment in zend_accel_function_hash_copy(). */
513521
continue;
514522
} else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
515523
zend_class_entry *ce1 = Z_PTR(p->val);
@@ -546,7 +554,7 @@ static void zend_accel_class_hash_copy_from_shm(HashTable *target, HashTable *so
546554
t = zend_hash_find_ex(target, p->key, 1);
547555
if (UNEXPECTED(t != NULL)) {
548556
if (EXPECTED(ZSTR_LEN(p->key) > 0) && EXPECTED(ZSTR_VAL(p->key)[0] == 0)) {
549-
/* Mangled key - ignore and wait for runtime */
557+
/* See comment in zend_accel_function_hash_copy(). */
550558
continue;
551559
} else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) {
552560
zend_class_entry *ce1 = Z_PTR(p->val);

0 commit comments

Comments
 (0)