Skip to content

Commit 8a8c69c

Browse files
iluuu1994nielsdos
authored andcommitted
Implement opcache checksum skip list
1 parent a9e4f51 commit 8a8c69c

13 files changed

+201
-20
lines changed

ext/opcache/ZendAccelerator.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2120,7 +2120,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type)
21202120
unsigned int checksum = zend_accel_script_checksum(persistent_script);
21212121
if (checksum != persistent_script->dynamic_members.checksum ) {
21222122
/* The checksum is wrong */
2123-
zend_accel_error(ACCEL_LOG_INFO, "Checksum failed for '%s': expected=0x%08x, found=0x%08x",
2123+
zend_accel_error(ACCEL_LOG_WARNING, "Checksum failed for '%s': expected=0x%08x, found=0x%08x",
21242124
ZSTR_VAL(persistent_script->script.filename), persistent_script->dynamic_members.checksum, checksum);
21252125
zend_shared_alloc_lock();
21262126
if (!persistent_script->corrupted) {
@@ -4763,6 +4763,10 @@ static int accel_finish_startup(void)
47634763
ZCG(cwd_key_len) = 0;
47644764
ZCG(cwd_check) = 1;
47654765

4766+
ZCG(checksum_skip_list) = NULL;
4767+
ZCG(checksum_skip_list_count) = 0;
4768+
ZCG(checksum_skip_list_capacity) = 0;
4769+
47664770
if (accel_preload(ZCG(accel_directives).preload, in_child) != SUCCESS) {
47674771
ret = FAILURE;
47684772
}

ext/opcache/ZendAccelerator.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ typedef struct _zend_persistent_script {
122122

123123
void *mem; /* shared memory area used by script structures */
124124
size_t size; /* size of used shared memory */
125+
/* list of offsets relative to (mem + ZEND_ALIGNED_SIZE(sizeof(zend_persistent_script))) to be skipped in the checksum calculation */
126+
uint32_t *checksum_skip_list;
125127

126128
/* All entries that shouldn't be counted in the ADLER32
127129
* checksum must be declared in this struct
@@ -225,6 +227,10 @@ typedef struct _zend_accel_globals {
225227
/* preallocated buffer for keys */
226228
zend_string key;
227229
char _key[MAXPATHLEN * 8];
230+
231+
uint32_t *checksum_skip_list;
232+
uint32_t checksum_skip_list_count;
233+
uint32_t checksum_skip_list_capacity;
228234
} zend_accel_globals;
229235

230236
typedef struct _zend_string_table {

ext/opcache/jit/zend_jit.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
#include "Optimizer/zend_func_info.h"
3737
#include "Optimizer/zend_ssa.h"
3838
#include "Optimizer/zend_inference.h"
39-
#include "Optimizer/zend_call_graph.h"
4039
#include "Optimizer/zend_dump.h"
4140

4241
#if ZEND_JIT_TARGET_X86
@@ -1267,7 +1266,7 @@ static int zend_may_overflow(const zend_op *opline, const zend_ssa_op *ssa_op, c
12671266
}
12681267
}
12691268

1270-
static int zend_jit_build_cfg(const zend_op_array *op_array, zend_cfg *cfg)
1269+
ZEND_EXT_API int zend_jit_build_cfg(const zend_op_array *op_array, zend_cfg *cfg)
12711270
{
12721271
uint32_t flags;
12731272

ext/opcache/jit/zend_jit.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#ifndef HAVE_JIT_H
2020
#define HAVE_JIT_H
2121

22+
#include "Optimizer/zend_call_graph.h"
23+
2224
#if defined(__x86_64__) || defined(i386) || defined(ZEND_WIN32)
2325
# define ZEND_JIT_TARGET_X86 1
2426
# define ZEND_JIT_TARGET_ARM64 0
@@ -154,6 +156,7 @@ ZEND_EXT_API void zend_jit_activate(void);
154156
ZEND_EXT_API void zend_jit_deactivate(void);
155157
ZEND_EXT_API void zend_jit_status(zval *ret);
156158
ZEND_EXT_API void zend_jit_restart(void);
159+
ZEND_EXT_API int zend_jit_build_cfg(const zend_op_array *op_array, zend_cfg *cfg);
157160

158161
typedef struct _zend_lifetime_interval zend_lifetime_interval;
159162
typedef struct _zend_life_range zend_life_range;

ext/opcache/jit/zend_jit_trace.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
+----------------------------------------------------------------------+
1717
*/
1818

19+
#include "zend_persist.h"
20+
1921
static zend_op_array dummy_op_array;
2022
static zend_jit_trace_info *zend_jit_traces = NULL;
2123
static const void **zend_jit_exit_groups = NULL;
@@ -8262,6 +8264,7 @@ static int zend_jit_setup_hot_trace_counters(zend_op_array *op_array)
82628264
if (cfg.blocks[i].flags & ZEND_BB_LOOP_HEADER) {
82638265
/* loop header */
82648266
opline = op_array->opcodes + cfg.blocks[i].start;
8267+
checksum_skip_list_add(&opline->handler);
82658268
if (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) {
82668269
opline->handler = (const void*)zend_jit_loop_trace_counter_handler;
82678270
if (!ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter) {
@@ -8286,6 +8289,7 @@ static int zend_jit_setup_hot_trace_counters(zend_op_array *op_array)
82868289
}
82878290
}
82888291

8292+
checksum_skip_list_add(&opline->handler);
82898293
if (!ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags) {
82908294
/* function entry */
82918295
opline->handler = (const void*)zend_jit_func_trace_counter_handler;

ext/opcache/tests/gh8065.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
class Bar extends Foo {}

ext/opcache/tests/gh8065.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Bug GH-8065: Opcache zend_class_entry.inheritance_cache breaks persistent script checksum
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.consistency_checks=1
7+
opcache.log_verbosity_level=2
8+
opcache.protect_memory=1
9+
--EXTENSIONS--
10+
opcache
11+
--FILE--
12+
<?php
13+
14+
class Foo {}
15+
16+
require_once __DIR__ . '/gh8065.inc';
17+
18+
echo "Done\n";
19+
20+
?>
21+
--EXPECT--
22+
Done

ext/opcache/zend_accelerator_util_funcs.c

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -299,18 +299,33 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script,
299299
#define ADLER32_DO4(buf, i) ADLER32_DO2(buf, i); ADLER32_DO2(buf, i + 2);
300300
#define ADLER32_DO8(buf, i) ADLER32_DO4(buf, i); ADLER32_DO4(buf, i + 4);
301301
#define ADLER32_DO16(buf) ADLER32_DO8(buf, 0); ADLER32_DO8(buf, 8);
302-
303-
unsigned int zend_adler32(unsigned int checksum, unsigned char *buf, uint32_t len)
302+
#define ADLER32_CHECK_SKIPPED(by) \
303+
do { \
304+
offset = buf - start; \
305+
while (*skip_list != 0 && *skip_list < offset) { \
306+
skip_list++; \
307+
} \
308+
skipped = *skip_list != 0 && *skip_list < (offset + (by)); \
309+
} while (0)
310+
311+
unsigned int zend_adler32(unsigned int checksum, unsigned char *buf, uint32_t len, uint32_t *skip_list)
304312
{
313+
unsigned char *start = buf;
305314
unsigned int s1 = checksum & 0xffff;
306315
unsigned int s2 = (checksum >> 16) & 0xffff;
307316
unsigned char *end;
317+
bool skipped;
318+
uint32_t offset;
308319

309320
while (len >= ADLER32_NMAX) {
310321
len -= ADLER32_NMAX;
311322
end = buf + ADLER32_NMAX;
323+
skipped = false;
312324
do {
313-
ADLER32_DO16(buf);
325+
ADLER32_CHECK_SKIPPED(16);
326+
if (!skipped) {
327+
ADLER32_DO16(buf);
328+
}
314329
buf += 16;
315330
} while (buf != end);
316331
s1 %= ADLER32_BASE;
@@ -321,16 +336,27 @@ unsigned int zend_adler32(unsigned int checksum, unsigned char *buf, uint32_t le
321336
if (len >= 16) {
322337
end = buf + (len & 0xfff0);
323338
len &= 0xf;
339+
skipped = false;
324340
do {
325-
ADLER32_DO16(buf);
341+
ADLER32_CHECK_SKIPPED(16);
342+
if (!skipped) {
343+
ADLER32_DO16(buf);
344+
}
326345
buf += 16;
327346
} while (buf != end);
328347
}
329348
if (len) {
330349
end = buf + len;
350+
skipped = false;
331351
do {
332-
ADLER32_DO1(buf);
333-
buf++;
352+
ADLER32_CHECK_SKIPPED(1);
353+
if (!skipped) {
354+
ADLER32_DO1(buf);
355+
buf++;
356+
} else {
357+
// skip_list contains a list of pointers so we skip the entire pointer size
358+
buf += sizeof(void*);
359+
}
334360
} while (buf != end);
335361
}
336362
s1 %= ADLER32_BASE;
@@ -346,19 +372,20 @@ unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_scrip
346372
size_t size = persistent_script->size;
347373
size_t persistent_script_check_block_size = ((char *)&(persistent_script->dynamic_members)) - (char *)persistent_script;
348374
unsigned int checksum = ADLER32_INIT;
375+
uint32_t zero = 0;
349376

350377
if (mem < (unsigned char*)persistent_script) {
351-
checksum = zend_adler32(checksum, mem, (unsigned char*)persistent_script - mem);
378+
checksum = zend_adler32(checksum, mem, (unsigned char*)persistent_script - mem, &zero);
352379
size -= (unsigned char*)persistent_script - mem;
353380
mem += (unsigned char*)persistent_script - mem;
354381
}
355382

356-
zend_adler32(checksum, mem, persistent_script_check_block_size);
357-
mem += sizeof(*persistent_script);
358-
size -= sizeof(*persistent_script);
383+
checksum = zend_adler32(checksum, mem, persistent_script_check_block_size, &zero);
384+
mem += ZEND_ALIGNED_SIZE(sizeof(*persistent_script));
385+
size -= ZEND_ALIGNED_SIZE(sizeof(*persistent_script));
359386

360387
if (size > 0) {
361-
checksum = zend_adler32(checksum, mem, size);
388+
checksum = zend_adler32(checksum, mem, size, persistent_script->checksum_skip_list);
362389
}
363390
return checksum;
364391
}

ext/opcache/zend_accelerator_util_funcs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script,
3535

3636
#define ADLER32_INIT 1 /* initial Adler-32 value */
3737

38-
unsigned int zend_adler32(unsigned int checksum, unsigned char *buf, uint32_t len);
38+
unsigned int zend_adler32(unsigned int checksum, unsigned char *buf, uint32_t len, uint32_t *skip_list);
3939

4040
unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_script);
4141

ext/opcache/zend_file_cache.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,8 +1030,11 @@ int zend_file_cache_script_store(zend_persistent_script *script, int in_shm)
10301030
}
10311031
zend_shared_alloc_destroy_xlat_table();
10321032

1033-
info.checksum = zend_adler32(ADLER32_INIT, buf, script->size);
1034-
info.checksum = zend_adler32(info.checksum, (unsigned char*)ZSTR_VAL((zend_string*)ZCG(mem)), info.str_size);
1033+
zend_string *const s = (zend_string*)ZCG(mem);
1034+
1035+
uint32_t zero = 0;
1036+
info.checksum = zend_adler32(ADLER32_INIT, buf, script->size, &zero);
1037+
info.checksum = zend_adler32(info.checksum, (unsigned char*)ZSTR_VAL(s), info.str_size, &zero);
10351038

10361039
#if __has_feature(memory_sanitizer)
10371040
/* The buffer may contain uninitialized regions. However, the uninitialized parts will not be
@@ -1775,8 +1778,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl
17751778
close(fd);
17761779

17771780
/* verify checksum */
1781+
uint32_t zero = 0;
17781782
if (ZCG(accel_directives).file_cache_consistency_checks &&
1779-
(actual_checksum = zend_adler32(ADLER32_INIT, mem, info.mem_size + info.str_size)) != info.checksum) {
1783+
(actual_checksum = zend_adler32(ADLER32_INIT, mem, info.mem_size + info.str_size, &zero)) != info.checksum) {
17801784
zend_accel_error(ACCEL_LOG_WARNING, "corrupted file '%s' excepted checksum: 0x%08x actual checksum: 0x%08x\n", filename, info.checksum, actual_checksum);
17811785
zend_file_cache_unlink(filename);
17821786
zend_arena_release(&CG(arena), checkpoint);

ext/opcache/zend_persist.c

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,60 @@ static void zend_persist_op_array(zval *zv);
8787
static const uint32_t uninitialized_bucket[-HT_MIN_MASK] =
8888
{HT_INVALID_IDX, HT_INVALID_IDX};
8989

90+
static void checksum_skip_list_init(void)
91+
{
92+
ZEND_ASSERT(ZCG(checksum_skip_list) == NULL);
93+
ZCG(checksum_skip_list_count) = 0;
94+
ZCG(checksum_skip_list_capacity) = 8;
95+
ZCG(checksum_skip_list) = safe_emalloc(ZCG(checksum_skip_list_capacity), sizeof(uint32_t), 0);
96+
}
97+
98+
static void checksum_skip_list_extend(uint32_t size)
99+
{
100+
ZEND_ASSERT(ZCG(checksum_skip_list) != NULL);
101+
ZCG(checksum_skip_list) = safe_erealloc(ZCG(checksum_skip_list), ZCG(checksum_skip_list_capacity), sizeof(uint32_t), 0);
102+
}
103+
104+
void checksum_skip_list_add(void *p)
105+
{
106+
ZEND_ASSERT(ZCG(checksum_skip_list) != NULL);
107+
void *base_btr = ((char*)ZCG(current_persistent_script)->mem) + ZEND_ALIGNED_SIZE(sizeof(zend_persistent_script));
108+
ZEND_ASSERT(p >= base_btr);
109+
if ((ZCG(checksum_skip_list_count) + 1) >= ZCG(checksum_skip_list_capacity)) {
110+
ZCG(checksum_skip_list_capacity) *= 2;
111+
checksum_skip_list_extend(ZCG(checksum_skip_list_capacity));
112+
}
113+
ZCG(checksum_skip_list)[ZCG(checksum_skip_list_count)++] = p - base_btr;
114+
}
115+
116+
static int checksum_skip_list_compare(const uint32_t *l, const uint32_t *r)
117+
{
118+
if (*l < *r) {
119+
return -1;
120+
} else if (*l == *r) {
121+
return 0;
122+
} else {
123+
return 1;
124+
}
125+
}
126+
127+
static void checksum_skip_list_swap(uint32_t *l, uint32_t *r)
128+
{
129+
uint32_t tmp = *r;
130+
*r = *l;
131+
*l = tmp;
132+
}
133+
134+
static uint32_t *checksum_skip_list_persist(void)
135+
{
136+
zend_sort((void *) ZCG(checksum_skip_list), ZCG(checksum_skip_list_count), sizeof(*ZCG(checksum_skip_list)), (compare_func_t) checksum_skip_list_compare, (swap_func_t) checksum_skip_list_swap);
137+
uint32_t *result = zend_shared_memdup(ZCG(checksum_skip_list), (ZCG(checksum_skip_list_count) + 1) * sizeof(uint32_t));
138+
result[ZCG(checksum_skip_list_count)] = 0;
139+
efree(ZCG(checksum_skip_list));
140+
ZCG(checksum_skip_list) = NULL;
141+
return result;
142+
}
143+
90144
static void zend_hash_persist(HashTable *ht)
91145
{
92146
uint32_t idx, nIndex;
@@ -866,6 +920,9 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce)
866920
ce->ce_flags |= ZEND_ACC_FILE_CACHED;
867921
}
868922
ce->inheritance_cache = NULL;
923+
if (ZCG(checksum_skip_list) != NULL) {
924+
checksum_skip_list_add(&ce->inheritance_cache);
925+
}
869926

870927
if (!(ce->ce_flags & ZEND_ACC_CACHED)) {
871928
if (ZSTR_HAS_CE_CACHE(ce->name)) {
@@ -1290,6 +1347,8 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script
12901347
script->corrupted = 0;
12911348
ZCG(current_persistent_script) = script;
12921349

1350+
checksum_skip_list_init();
1351+
12931352
if (!for_shm) {
12941353
/* script is not going to be saved in SHM */
12951354
script->corrupted = 1;
@@ -1346,7 +1405,9 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script
13461405
}
13471406
#endif
13481407

1349-
script->corrupted = 0;
1408+
script->checksum_skip_list = checksum_skip_list_persist();
1409+
1410+
script->corrupted = false;
13501411
ZCG(current_persistent_script) = NULL;
13511412

13521413
return script;

ext/opcache/zend_persist.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,6 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *ce);
3030
void zend_update_parent_ce(zend_class_entry *ce);
3131
void zend_persist_warnings_calc(uint32_t num_warnings, zend_error_info **warnings);
3232
zend_error_info **zend_persist_warnings(uint32_t num_warnings, zend_error_info **warnings);
33+
void checksum_skip_list_add(void *p);
3334

3435
#endif /* ZEND_PERSIST_H */

0 commit comments

Comments
 (0)