Skip to content

Commit 53fa98e

Browse files
authored
Fix GH-17715: Handle preloaded internal function runtime cache (#17835)
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.
1 parent fc73da5 commit 53fa98e

8 files changed

+135
-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).
@@ -78,7 +80,7 @@ PHP NEWS
7880
. Fix memory leak on overflow in _php_stream_scandir(). (nielsdos)
7981

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

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

Zend/zend_map_ptr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ typedef struct _zend_string zend_string;
7070
} while (0)
7171
# define ZEND_MAP_PTR_BIASED_BASE(real_base) \
7272
((void*)(((uintptr_t)(real_base)) + zend_map_ptr_static_size * sizeof(void *) - 1))
73+
/* Note: chunked like: [8192..12287][4096..8191][0..4095] */
74+
#define ZEND_MAP_PTR_STATIC_NUM_TO_PTR(num) \
75+
((void **)CG(map_ptr_real_base) + zend_map_ptr_static_size - ZEND_MM_ALIGNED_SIZE_EX((num) + 1, 4096) + ((num) & 4095))
7376
#else
7477
# error "Unknown ZEND_MAP_PTR_KIND"
7578
#endif

ext/opcache/ZendAccelerator.c

Lines changed: 58 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,42 @@ 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_MM_ALIGNED_SIZE_EX(zend_map_ptr_static_last, 4096);
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), (zend_map_ptr_static_size + 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) * runtime_cache_size;
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+
*ZEND_MAP_PTR_STATIC_NUM_TO_PTR(cur_static_map_ptr) = cache;
4398+
cache += runtime_cache_size;
4399+
}
43644400
}
43654401
}
43664402

@@ -4369,7 +4405,7 @@ static zend_result accel_preload(const char *config, bool in_child)
43694405
zend_file_handle file_handle;
43704406
zend_result ret;
43714407
char *orig_open_basedir;
4372-
size_t orig_map_ptr_last;
4408+
size_t orig_map_ptr_last, orig_map_ptr_static_last;
43734409
uint32_t orig_compiler_options;
43744410

43754411
ZCG(enabled) = false;
@@ -4380,6 +4416,7 @@ static zend_result accel_preload(const char *config, bool in_child)
43804416
accelerator_orig_compile_file = preload_compile_file;
43814417

43824418
orig_map_ptr_last = CG(map_ptr_last);
4419+
orig_map_ptr_static_last = zend_map_ptr_static_last;
43834420

43844421
/* Compile and execute preloading script */
43854422
zend_stream_init_filename(&file_handle, (char *) config);
@@ -4559,7 +4596,7 @@ static zend_result accel_preload(const char *config, bool in_child)
45594596
SHM_PROTECT();
45604597
HANDLE_UNBLOCK_INTERRUPTIONS();
45614598

4562-
preload_load();
4599+
preload_load(orig_map_ptr_static_last);
45634600

45644601
/* Store individual scripts with unlinked classes */
45654602
HANDLE_BLOCK_INTERRUPTIONS();
@@ -4811,7 +4848,7 @@ static zend_result accel_finish_startup(void)
48114848

48124849
if (ZCSG(preload_script)) {
48134850
/* Preloading was done in another process */
4814-
preload_load();
4851+
preload_load(zend_map_ptr_static_last);
48154852
zend_shared_alloc_unlock();
48164853
return SUCCESS;
48174854
}
@@ -4839,7 +4876,7 @@ static zend_result accel_finish_startup(void)
48394876
}
48404877

48414878
if (ZCSG(preload_script)) {
4842-
preload_load();
4879+
preload_load(zend_map_ptr_static_last);
48434880
}
48444881

48454882
zend_shared_alloc_unlock();
@@ -4853,6 +4890,12 @@ static zend_result accel_finish_startup(void)
48534890
#endif /* ZEND_WIN32 */
48544891
}
48554892

4893+
static void accel_activate(void) {
4894+
if (ZCG(preloaded_internal_run_time_cache)) {
4895+
memset(ZCG(preloaded_internal_run_time_cache), 0, ZCG(preloaded_internal_run_time_cache_size));
4896+
}
4897+
}
4898+
48564899
ZEND_EXT_API zend_extension zend_extension_entry = {
48574900
ACCELERATOR_PRODUCT_NAME, /* name */
48584901
PHP_VERSION, /* version */
@@ -4861,7 +4904,7 @@ ZEND_EXT_API zend_extension zend_extension_entry = {
48614904
"Copyright (c)", /* copyright */
48624905
accel_startup, /* startup */
48634906
NULL, /* shutdown */
4864-
NULL, /* per-script activation */
4907+
accel_activate, /* per-script activation */
48654908
#ifdef HAVE_JIT
48664909
accel_deactivate, /* per-script deactivation */
48674910
#else

ext/opcache/ZendAccelerator.h

Lines changed: 5 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;
@@ -251,6 +254,7 @@ typedef struct _zend_accel_shared_globals {
251254
zend_accel_hash hash; /* hash table for cached scripts */
252255

253256
size_t map_ptr_last;
257+
size_t map_ptr_static_last;
254258

255259
/* Directives & Maintenance */
256260
time_t start_time;
@@ -310,7 +314,7 @@ extern const char *zps_api_failure_reason;
310314
BEGIN_EXTERN_C()
311315

312316
void accel_shutdown(void);
313-
zend_result accel_activate(INIT_FUNC_ARGS);
317+
ZEND_RINIT_FUNCTION(zend_accelerator);
314318
zend_result accel_post_deactivate(void);
315319
void zend_accel_schedule_restart(zend_accel_restart_reason reason);
316320
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)