Skip to content

Commit 0d5fe44

Browse files
committed
Fix GH-17715: Handle preloaded internal function runtime cache
This solely affects the builtin enum functions currently. Given that these are stored in SHM, we cannot simply hardwire a pointer into the internal function runtime cache on NTS too, but have to use a MAP_PTR (like on ZTS). Now, by design, the runtime cache of internal functions no longer is reset between requests, hence we need to store them explicitly as static runtime cache. On NTS builds we cannot trivially move the pointers into CG(internal_run_time_cache) as they're directly stored on the individual functions (on ZTS we could simply iterate the static map_ptrs). Hence, we have the choice between having opcache managing the internal run_time_cache for its preloaded functions itself or realloc CG(internal_run_time_cache) and iterate through all functions to assign the new address. We choose the latter for simplicity and initial speed. Note: map_ptr_static_last has been added as last element to zend_accel_shared_globals, so that accesses to it are compatible. We do not have to care about the ABI of creating new zend_accel_shared_globals structs. That's opcaches prerogative.
1 parent e8dda54 commit 0d5fe44

File tree

7 files changed

+134
-20
lines changed

7 files changed

+134
-20
lines changed

NEWS

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ PHP NEWS
5555
. Fixed bug GH-17577 (JIT packed type guard crash). (nielsdos, Dmitry)
5656
. Fixed bug GH-17747 (Exception on reading property in register-based
5757
FETCH_OBJ_R breaks JIT). (Dmitry, nielsdos)
58+
. Fixed bug GH-17715 (Null pointer deref in observer API when calling
59+
cases() method on preloaded enum). (Bob)
5860

5961
- PDO_SQLite:
6062
. Fixed GH-17837 ()::getColumnMeta() on unexecuted statement segfaults).
@@ -76,7 +78,7 @@ PHP NEWS
7678
. Fix memory leak on overflow in _php_stream_scandir(). (nielsdos)
7779

7880
- Windows:
79-
. Fixed phpize for Windows 11 (24H2). (bwoebi)
81+
. Fixed phpize for Windows 11 (24H2). (Bob)
8082
. Fixed GH-17855 (CURL_STATICLIB flag set even if linked with shared lib).
8183
(cmb)
8284

Zend/zend_enum.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,10 @@ static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id n
418418
zif->module = EG(current_module);
419419
zif->scope = ce;
420420
zif->T = ZEND_OBSERVER_ENABLED;
421-
if (EG(active)) { // at run-time
421+
if (EG(active)) { // at run-time
422+
if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
423+
zif->fn_flags |= ZEND_ACC_PRELOADED;
424+
}
422425
ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
423426
} else {
424427
#ifdef ZTS

ext/opcache/ZendAccelerator.c

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,6 +2614,7 @@ static void zend_reset_cache_vars(void)
26142614
ZCSG(restart_pending) = false;
26152615
ZCSG(force_restart_time) = 0;
26162616
ZCSG(map_ptr_last) = CG(map_ptr_last);
2617+
ZCSG(map_ptr_static_last) = zend_map_ptr_static_last;
26172618
}
26182619

26192620
static void accel_reset_pcre_cache(void)
@@ -2629,7 +2630,7 @@ static void accel_reset_pcre_cache(void)
26292630
} ZEND_HASH_FOREACH_END();
26302631
}
26312632

2632-
zend_result accel_activate(INIT_FUNC_ARGS)
2633+
ZEND_RINIT_FUNCTION(zend_accelerator)
26332634
{
26342635
if (!ZCG(enabled) || !accel_startup_ok) {
26352636
ZCG(accelerator_enabled) = false;
@@ -2961,12 +2962,15 @@ static void accel_globals_ctor(zend_accel_globals *accel_globals)
29612962
GC_MAKE_PERSISTENT_LOCAL(accel_globals->key);
29622963
}
29632964

2964-
#ifdef ZTS
29652965
static void accel_globals_dtor(zend_accel_globals *accel_globals)
29662966
{
2967+
#ifdef ZTS
29672968
zend_string_free(accel_globals->key);
2968-
}
29692969
#endif
2970+
if (accel_globals->preloaded_internal_run_time_cache) {
2971+
pefree(accel_globals->preloaded_internal_run_time_cache, 1);
2972+
}
2973+
}
29702974

29712975
#ifdef HAVE_HUGE_CODE_PAGES
29722976
# ifndef _WIN32
@@ -3407,6 +3411,8 @@ void accel_shutdown(void)
34073411
if (!ZCG(enabled) || !accel_startup_ok) {
34083412
#ifdef ZTS
34093413
ts_free_id(accel_globals_id);
3414+
#else
3415+
accel_globals_dtor(&accel_globals);
34103416
#endif
34113417
return;
34123418
}
@@ -3421,6 +3427,8 @@ void accel_shutdown(void)
34213427

34223428
#ifdef ZTS
34233429
ts_free_id(accel_globals_id);
3430+
#else
3431+
accel_globals_dtor(&accel_globals);
34243432
#endif
34253433

34263434
if (!_file_cache_only) {
@@ -4318,7 +4326,7 @@ static zend_persistent_script* preload_script_in_shared_memory(zend_persistent_s
43184326
return new_persistent_script;
43194327
}
43204328

4321-
static void preload_load(void)
4329+
static void preload_load(size_t orig_map_ptr_static_last)
43224330
{
43234331
/* Load into process tables */
43244332
zend_script *script = &ZCSG(preload_script)->script;
@@ -4353,14 +4361,43 @@ static void preload_load(void)
43534361
if (EG(class_table)) {
43544362
EG(persistent_classes_count) = EG(class_table)->nNumUsed;
43554363
}
4356-
if (CG(map_ptr_last) != ZCSG(map_ptr_last)) {
4357-
size_t old_map_ptr_last = CG(map_ptr_last);
4364+
4365+
size_t old_map_ptr_last = CG(map_ptr_last);
4366+
if (zend_map_ptr_static_last != ZCSG(map_ptr_static_last) || old_map_ptr_last != ZCSG(map_ptr_last)) {
43584367
CG(map_ptr_last) = ZCSG(map_ptr_last);
4359-
CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(CG(map_ptr_last) + 1, 4096);
4360-
CG(map_ptr_real_base) = perealloc(CG(map_ptr_real_base), CG(map_ptr_size) * sizeof(void*), 1);
4368+
CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(ZCSG(map_ptr_last) + 1, 4096);
4369+
zend_map_ptr_static_last = ZCSG(map_ptr_static_last);
4370+
4371+
/* Grow map_ptr table as needed, but allocate once for static + regular map_ptrs */
4372+
size_t new_static_size = ((zend_map_ptr_static_last - 1) & 4095) + 1;
4373+
if (zend_map_ptr_static_size != new_static_size) {
4374+
void *new_base = pemalloc((new_static_size + CG(map_ptr_size)) * sizeof(void *), 1);
4375+
if (CG(map_ptr_real_base)) {
4376+
memcpy((void **) new_base + new_static_size - zend_map_ptr_static_size, CG(map_ptr_real_base), (old_map_ptr_last + zend_map_ptr_static_size) * sizeof(void *));
4377+
pefree(CG(map_ptr_real_base), 1);
4378+
}
4379+
CG(map_ptr_real_base) = new_base;
4380+
zend_map_ptr_static_size = new_static_size;
4381+
} else {
4382+
CG(map_ptr_real_base) = perealloc(CG(map_ptr_real_base), CG(map_ptr_size) * sizeof(void *), 1);
4383+
}
4384+
4385+
memset((void **) CG(map_ptr_real_base) + zend_map_ptr_static_size + old_map_ptr_last, 0, (CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *));
43614386
CG(map_ptr_base) = ZEND_MAP_PTR_BIASED_BASE(CG(map_ptr_real_base));
4362-
memset((void **) CG(map_ptr_real_base) + old_map_ptr_last, 0,
4363-
(CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *));
4387+
}
4388+
4389+
if (orig_map_ptr_static_last != zend_map_ptr_static_last) {
4390+
/* preloaded static entries currently are all runtime cache pointers, just assign them as such */
4391+
size_t runtime_cache_size = zend_internal_run_time_cache_reserved_size();
4392+
ZCG(preloaded_internal_run_time_cache_size) = (zend_map_ptr_static_last - orig_map_ptr_static_last) * sizeof(void *);
4393+
char *cache = pemalloc(ZCG(preloaded_internal_run_time_cache_size), 1);
4394+
ZCG(preloaded_internal_run_time_cache) = cache;
4395+
4396+
for (size_t cur_static_map_ptr = orig_map_ptr_static_last; cur_static_map_ptr < zend_map_ptr_static_last; ++cur_static_map_ptr) {
4397+
void **ptr = (void **) CG(map_ptr_real_base) + zend_map_ptr_static_size - ((cur_static_map_ptr & ~4095) + 4096) + (cur_static_map_ptr & 4095);
4398+
*ptr = cache;
4399+
cache += runtime_cache_size;
4400+
}
43644401
}
43654402
}
43664403

@@ -4369,7 +4406,7 @@ static zend_result accel_preload(const char *config, bool in_child)
43694406
zend_file_handle file_handle;
43704407
zend_result ret;
43714408
char *orig_open_basedir;
4372-
size_t orig_map_ptr_last;
4409+
size_t orig_map_ptr_last, orig_map_ptr_static_last;
43734410
uint32_t orig_compiler_options;
43744411

43754412
ZCG(enabled) = false;
@@ -4380,6 +4417,7 @@ static zend_result accel_preload(const char *config, bool in_child)
43804417
accelerator_orig_compile_file = preload_compile_file;
43814418

43824419
orig_map_ptr_last = CG(map_ptr_last);
4420+
orig_map_ptr_static_last = zend_map_ptr_static_last;
43834421

43844422
/* Compile and execute preloading script */
43854423
zend_stream_init_filename(&file_handle, (char *) config);
@@ -4559,7 +4597,7 @@ static zend_result accel_preload(const char *config, bool in_child)
45594597
SHM_PROTECT();
45604598
HANDLE_UNBLOCK_INTERRUPTIONS();
45614599

4562-
preload_load();
4600+
preload_load(orig_map_ptr_static_last);
45634601

45644602
/* Store individual scripts with unlinked classes */
45654603
HANDLE_BLOCK_INTERRUPTIONS();
@@ -4811,7 +4849,7 @@ static zend_result accel_finish_startup(void)
48114849

48124850
if (ZCSG(preload_script)) {
48134851
/* Preloading was done in another process */
4814-
preload_load();
4852+
preload_load(zend_map_ptr_static_last);
48154853
zend_shared_alloc_unlock();
48164854
return SUCCESS;
48174855
}
@@ -4839,7 +4877,7 @@ static zend_result accel_finish_startup(void)
48394877
}
48404878

48414879
if (ZCSG(preload_script)) {
4842-
preload_load();
4880+
preload_load(zend_map_ptr_static_last);
48434881
}
48444882

48454883
zend_shared_alloc_unlock();
@@ -4853,6 +4891,12 @@ static zend_result accel_finish_startup(void)
48534891
#endif /* ZEND_WIN32 */
48544892
}
48554893

4894+
static void accel_activate(void) {
4895+
if (ZCG(preloaded_internal_run_time_cache)) {
4896+
memset(ZCG(preloaded_internal_run_time_cache), 0, ZCG(preloaded_internal_run_time_cache_size));
4897+
}
4898+
}
4899+
48564900
ZEND_EXT_API zend_extension zend_extension_entry = {
48574901
ACCELERATOR_PRODUCT_NAME, /* name */
48584902
PHP_VERSION, /* version */
@@ -4861,7 +4905,7 @@ ZEND_EXT_API zend_extension zend_extension_entry = {
48614905
"Copyright (c)", /* copyright */
48624906
accel_startup, /* startup */
48634907
NULL, /* shutdown */
4864-
NULL, /* per-script activation */
4908+
accel_activate, /* per-script activation */
48654909
#ifdef HAVE_JIT
48664910
accel_deactivate, /* per-script deactivation */
48674911
#else

ext/opcache/ZendAccelerator.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
#include "zend_extensions.h"
5252
#include "zend_compile.h"
53+
#include "zend_API.h"
5354

5455
#include "Optimizer/zend_optimizer.h"
5556
#include "zend_accelerator_hash.h"
@@ -216,6 +217,8 @@ typedef struct _zend_accel_globals {
216217
#ifndef ZEND_WIN32
217218
zend_ulong root_hash;
218219
#endif
220+
void *preloaded_internal_run_time_cache;
221+
size_t preloaded_internal_run_time_cache_size;
219222
/* preallocated shared-memory block to save current script */
220223
void *mem;
221224
zend_persistent_script *current_persistent_script;
@@ -280,6 +283,8 @@ typedef struct _zend_accel_shared_globals {
280283

281284
/* Interned Strings Support (must be the last element) */
282285
ZEND_SET_ALIGNED(ZEND_STRING_TABLE_POS_ALIGNMENT, zend_string_table interned_strings);
286+
287+
size_t map_ptr_static_last;
283288
} zend_accel_shared_globals;
284289

285290
#ifdef ZEND_WIN32
@@ -310,7 +315,7 @@ extern const char *zps_api_failure_reason;
310315
BEGIN_EXTERN_C()
311316

312317
void accel_shutdown(void);
313-
zend_result accel_activate(INIT_FUNC_ARGS);
318+
ZEND_RINIT_FUNCTION(zend_accelerator);
314319
zend_result accel_post_deactivate(void);
315320
void zend_accel_schedule_restart(zend_accel_restart_reason reason);
316321
void zend_accel_schedule_restart_if_necessary(zend_accel_restart_reason reason);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
--TEST--
2+
Enum preloading with observers
3+
--EXTENSIONS--
4+
opcache
5+
zend_test
6+
--INI--
7+
opcache.enable=1
8+
opcache.enable_cli=1
9+
opcache.optimization_level=-1
10+
opcache.preload={PWD}/preload_enum.inc
11+
zend_test.observer.enabled=1
12+
zend_test.observer.show_output=1
13+
zend_test.observer.observe_all=1
14+
--SKIPIF--
15+
<?php
16+
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
17+
?>
18+
--FILE--
19+
<?php
20+
21+
spl_autoload_register(static function ($class) {
22+
if ($class === 'MyEnum') {
23+
require_once(__DIR__ . '/preload_enum.inc');
24+
}
25+
});
26+
27+
var_dump(MyEnum::cases());
28+
29+
?>
30+
--EXPECTF--
31+
<!-- init '%spreload_enum.inc' -->
32+
<file '%spreload_enum.inc'>
33+
<!-- init var_dump() -->
34+
<var_dump>
35+
enum(MyEnum::Bar)
36+
</var_dump>
37+
</file '%spreload_enum.inc'>
38+
<!-- init '%spreload_enum_observed.php' -->
39+
<file '%spreload_enum_observed.php'>
40+
<!-- init spl_autoload_register() -->
41+
<spl_autoload_register>
42+
</spl_autoload_register>
43+
<!-- init MyEnum::cases() -->
44+
<MyEnum::cases>
45+
</MyEnum::cases>
46+
<!-- init var_dump() -->
47+
<var_dump>
48+
array(2) {
49+
[0]=>
50+
enum(MyEnum::Foo)
51+
[1]=>
52+
enum(MyEnum::Bar)
53+
}
54+
</var_dump>
55+
</file '%spreload_enum_observed.php'>

ext/opcache/zend_accelerator_module.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ static zend_module_entry accel_module_entry = {
572572
ext_functions,
573573
ZEND_MINIT(zend_accelerator),
574574
ZEND_MSHUTDOWN(zend_accelerator),
575-
accel_activate,
575+
ZEND_RINIT(zend_accelerator),
576576
NULL,
577577
zend_accel_info,
578578
PHP_VERSION,

ext/opcache/zend_persist.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,11 @@ static zend_op_array *zend_persist_class_method(zend_op_array *op_array, zend_cl
735735
// Real dynamically created internal functions like enum methods must have their own run_time_cache pointer. They're always on the same scope as their defining class.
736736
// However, copies - as caused by inheritance of internal methods - must retain the original run_time_cache pointer, shared with the source function.
737737
if (!op_array->scope || (op_array->scope == ce && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE))) {
738-
ZEND_MAP_PTR_NEW(op_array->run_time_cache);
738+
if (op_array->fn_flags & ZEND_ACC_PRELOADED) {
739+
ZEND_MAP_PTR_NEW_STATIC(op_array->run_time_cache);
740+
} else {
741+
ZEND_MAP_PTR_NEW(op_array->run_time_cache);
742+
}
739743
}
740744
}
741745
}
@@ -1413,6 +1417,7 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script
14131417

14141418
if (for_shm) {
14151419
ZCSG(map_ptr_last) = CG(map_ptr_last);
1420+
ZCSG(map_ptr_static_last) = zend_map_ptr_static_last;
14161421
}
14171422

14181423
#ifdef HAVE_JIT

0 commit comments

Comments
 (0)