Fixed bug #73989 (PHP 7.1 Segfaults within Symfony test suite)

This commit is contained in:
Xinchen Hui 2017-02-13 19:16:17 +08:00
parent eb1373e509
commit 3917350531
4 changed files with 114 additions and 52 deletions

2
NEWS
View file

@ -3,6 +3,8 @@ PHP NEWS
?? ??? 2017 PHP 7.0.17 ?? ??? 2017 PHP 7.0.17
- Core: - Core:
. Fixed bug #73989 (PHP 7.1 Segfaults within Symfony test suite).
(Dmitry, Laruence)
. Fixed bug #74084 (Out of bound read - zend_mm_alloc_small). (Laruence) . Fixed bug #74084 (Out of bound read - zend_mm_alloc_small). (Laruence)
. Fixed bug #73807 (Performance problem with processing large post request). . Fixed bug #73807 (Performance problem with processing large post request).
(Nikita) (Nikita)

28
Zend/tests/bug73989.phpt Normal file
View file

@ -0,0 +1,28 @@
--TEST--
Bug #73989 (PHP 7.1 Segfaults within Symfony test suite)
--FILE--
<?php
class Cycle
{
private $thing;
public function __construct()
{
$obj = $this;
$this->thing = function() use($obj) {};
}
public function __destruct()
{
($this->thing)();
}
}
for ($i = 0; $i < 10000; ++$i) {
$obj = new Cycle();
}
echo "OK\n";
?>
--EXPECT--
OK

View file

@ -25,26 +25,12 @@
/* one (0) is reserved */ /* one (0) is reserved */
#define GC_ROOT_BUFFER_MAX_ENTRIES 10001 #define GC_ROOT_BUFFER_MAX_ENTRIES 10001
#define GC_FAKE_BUFFER_FLAG 0x80
#define GC_TYPE_MASK 0x7f
#define GC_HAS_DESTRUCTORS (1<<0) #define GC_HAS_DESTRUCTORS (1<<0)
#ifndef ZEND_GC_DEBUG #ifndef ZEND_GC_DEBUG
# define ZEND_GC_DEBUG 0 # define ZEND_GC_DEBUG 0
#endif #endif
#define GC_NUM_ADDITIONAL_ENTRIES \
((4096 - ZEND_MM_OVERHEAD - sizeof(void*) * 2) / sizeof(gc_root_buffer))
typedef struct _gc_additional_bufer gc_additional_buffer;
struct _gc_additional_bufer {
uint32_t used;
gc_additional_buffer *next;
gc_root_buffer buf[GC_NUM_ADDITIONAL_ENTRIES];
};
#ifdef ZTS #ifdef ZTS
ZEND_API int gc_globals_id; ZEND_API int gc_globals_id;
#else #else
@ -53,9 +39,6 @@ ZEND_API zend_gc_globals gc_globals;
ZEND_API int (*gc_collect_cycles)(void); ZEND_API int (*gc_collect_cycles)(void);
#define GC_REMOVE_FROM_ROOTS(current) \
gc_remove_from_roots((current))
#if ZEND_GC_DEBUG > 1 #if ZEND_GC_DEBUG > 1
# define GC_TRACE(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__); # define GC_TRACE(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__);
# define GC_TRACE_REF(ref, format, ...) \ # define GC_TRACE_REF(ref, format, ...) \
@ -123,6 +106,12 @@ static zend_always_inline void gc_remove_from_roots(gc_root_buffer *root)
GC_BENCH_DEC(root_buf_length); GC_BENCH_DEC(root_buf_length);
} }
static zend_always_inline void gc_remove_from_additional_roots(gc_root_buffer *root)
{
root->next->prev = root->prev;
root->prev->next = root->next;
}
static void root_buffer_dtor(zend_gc_globals *gc_globals) static void root_buffer_dtor(zend_gc_globals *gc_globals)
{ {
if (gc_globals->buf) { if (gc_globals->buf) {
@ -149,6 +138,8 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
gc_globals->gc_runs = 0; gc_globals->gc_runs = 0;
gc_globals->collected = 0; gc_globals->collected = 0;
gc_globals->additional_buffer = NULL;
#if GC_BENCH #if GC_BENCH
gc_globals->root_buf_length = 0; gc_globals->root_buf_length = 0;
gc_globals->root_buf_peak = 0; gc_globals->root_buf_peak = 0;
@ -204,6 +195,8 @@ ZEND_API void gc_reset(void)
GC_G(first_unused) = NULL; GC_G(first_unused) = NULL;
GC_G(last_unused) = NULL; GC_G(last_unused) = NULL;
} }
GC_G(additional_buffer) = NULL;
} }
ZEND_API void gc_init(void) ZEND_API void gc_init(void)
@ -276,21 +269,42 @@ ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
GC_BENCH_PEAK(root_buf_peak, root_buf_length); GC_BENCH_PEAK(root_buf_peak, root_buf_length);
} }
static zend_always_inline gc_root_buffer* gc_find_additional_buffer(zend_refcounted *ref)
{
gc_additional_buffer *additional_buffer = GC_G(additional_buffer);
/* We have to check each additional_buffer to find which one holds the ref */
while (additional_buffer) {
gc_root_buffer *root = additional_buffer->buf + (GC_ADDRESS(GC_INFO(ref)) - GC_ROOT_BUFFER_MAX_ENTRIES);
if (root->ref == ref) {
return root;
}
additional_buffer = additional_buffer->next;
}
ZEND_ASSERT(0);
return NULL;
}
ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref) ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref)
{ {
gc_root_buffer *root; gc_root_buffer *root;
ZEND_ASSERT(GC_ADDRESS(GC_INFO(ref))); ZEND_ASSERT(GC_ADDRESS(GC_INFO(ref)));
ZEND_ASSERT(GC_ADDRESS(GC_INFO(ref)) < GC_ROOT_BUFFER_MAX_ENTRIES);
GC_BENCH_INC(zval_remove_from_buffer); GC_BENCH_INC(zval_remove_from_buffer);
if (EXPECTED(GC_ADDRESS(GC_INFO(ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
root = GC_G(buf) + GC_ADDRESS(GC_INFO(ref)); root = GC_G(buf) + GC_ADDRESS(GC_INFO(ref));
gc_remove_from_roots(root);
} else {
root = gc_find_additional_buffer(ref);
gc_remove_from_additional_roots(root);
}
if (GC_REF_GET_COLOR(ref) != GC_BLACK) { if (GC_REF_GET_COLOR(ref) != GC_BLACK) {
GC_TRACE_SET_COLOR(ref, GC_PURPLE); GC_TRACE_SET_COLOR(ref, GC_PURPLE);
} }
GC_INFO(ref) = 0; GC_INFO(ref) = 0;
GC_REMOVE_FROM_ROOTS(root);
/* updete next root that is going to be freed */ /* updete next root that is going to be freed */
if (GC_G(next_to_free) == root) { if (GC_G(next_to_free) == root) {
@ -641,7 +655,7 @@ static void gc_scan_roots(void)
} }
} }
static void gc_add_garbage(zend_refcounted *ref, gc_additional_buffer **additional_buffer) static void gc_add_garbage(zend_refcounted *ref)
{ {
gc_root_buffer *buf = GC_G(unused); gc_root_buffer *buf = GC_G(unused);
@ -664,25 +678,23 @@ static void gc_add_garbage(zend_refcounted *ref, gc_additional_buffer **addition
#endif #endif
} else { } else {
/* If we don't have free slots in the buffer, allocate a new one and /* If we don't have free slots in the buffer, allocate a new one and
* set it's address to GC_ROOT_BUFFER_MAX_ENTRIES that have special * set it's address above GC_ROOT_BUFFER_MAX_ENTRIES that have special
* meaning. * meaning.
*/ */
if (!*additional_buffer || (*additional_buffer)->used == GC_NUM_ADDITIONAL_ENTRIES) { if (!GC_G(additional_buffer) || GC_G(additional_buffer)->used == GC_NUM_ADDITIONAL_ENTRIES) {
gc_additional_buffer *new_buffer = emalloc(sizeof(gc_additional_buffer)); gc_additional_buffer *new_buffer = emalloc(sizeof(gc_additional_buffer));
new_buffer->used = 0; new_buffer->used = 0;
new_buffer->next = *additional_buffer; new_buffer->next = GC_G(additional_buffer);
*additional_buffer = new_buffer; GC_G(additional_buffer) = new_buffer;
} }
buf = (*additional_buffer)->buf + (*additional_buffer)->used; buf = GC_G(additional_buffer)->buf + GC_G(additional_buffer)->used;
(*additional_buffer)->used++;
#if 1 #if 1
/* optimization: color is already GC_BLACK (0) */ /* optimization: color is already GC_BLACK (0) */
GC_INFO(ref) = GC_ROOT_BUFFER_MAX_ENTRIES; GC_INFO(ref) = GC_ROOT_BUFFER_MAX_ENTRIES + GC_G(additional_buffer)->used;
#else #else
GC_REF_SET_ADDRESS(ref, GC_ROOT_BUFFER_MAX_ENTRIES); GC_REF_SET_ADDRESS(ref, GC_ROOT_BUFFER_MAX_ENTRIES) + GC_G(additional_buffer)->used;
#endif #endif
/* modify type to prevent indirect destruction */ GC_G(additional_buffer)->used++;
GC_TYPE(ref) |= GC_FAKE_BUFFER_FLAG;
} }
if (buf) { if (buf) {
GC_REFCOUNT(ref)++; GC_REFCOUNT(ref)++;
@ -694,7 +706,7 @@ static void gc_add_garbage(zend_refcounted *ref, gc_additional_buffer **addition
} }
} }
static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_additional_buffer **additional_buffer) static int gc_collect_white(zend_refcounted *ref, uint32_t *flags)
{ {
int count = 0; int count = 0;
HashTable *ht; HashTable *ht;
@ -727,7 +739,7 @@ tail_call:
#else #else
if (!GC_ADDRESS(GC_INFO(ref))) { if (!GC_ADDRESS(GC_INFO(ref))) {
#endif #endif
gc_add_garbage(ref, additional_buffer); gc_add_garbage(ref);
} }
if (obj->handlers->dtor_obj && if (obj->handlers->dtor_obj &&
((obj->handlers->dtor_obj != zend_objects_destroy_object) || ((obj->handlers->dtor_obj != zend_objects_destroy_object) ||
@ -751,7 +763,7 @@ tail_call:
if (Z_REFCOUNTED_P(zv)) { if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv); ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++; GC_REFCOUNT(ref)++;
count += gc_collect_white(ref, flags, additional_buffer); count += gc_collect_white(ref, flags);
/* count non-refcounted for compatibility ??? */ /* count non-refcounted for compatibility ??? */
} else if (Z_TYPE_P(zv) != IS_UNDEF) { } else if (Z_TYPE_P(zv) != IS_UNDEF) {
count++; count++;
@ -773,7 +785,7 @@ tail_call:
#else #else
if (!GC_ADDRESS(GC_INFO(ref))) { if (!GC_ADDRESS(GC_INFO(ref))) {
#endif #endif
gc_add_garbage(ref, additional_buffer); gc_add_garbage(ref);
} }
ht = (zend_array*)ref; ht = (zend_array*)ref;
} else if (GC_TYPE(ref) == IS_REFERENCE) { } else if (GC_TYPE(ref) == IS_REFERENCE) {
@ -813,7 +825,7 @@ tail_call:
if (Z_REFCOUNTED_P(zv)) { if (Z_REFCOUNTED_P(zv)) {
ref = Z_COUNTED_P(zv); ref = Z_COUNTED_P(zv);
GC_REFCOUNT(ref)++; GC_REFCOUNT(ref)++;
count += gc_collect_white(ref, flags, additional_buffer); count += gc_collect_white(ref, flags);
/* count non-refcounted for compatibility ??? */ /* count non-refcounted for compatibility ??? */
} else if (Z_TYPE_P(zv) != IS_UNDEF) { } else if (Z_TYPE_P(zv) != IS_UNDEF) {
count++; count++;
@ -831,7 +843,7 @@ tail_call:
return count; return count;
} }
static int gc_collect_roots(uint32_t *flags, gc_additional_buffer **additional_buffer) static int gc_collect_roots(uint32_t *flags)
{ {
int count = 0; int count = 0;
gc_root_buffer *current = GC_G(roots).next; gc_root_buffer *current = GC_G(roots).next;
@ -840,8 +852,12 @@ static int gc_collect_roots(uint32_t *flags, gc_additional_buffer **additional_b
while (current != &GC_G(roots)) { while (current != &GC_G(roots)) {
gc_root_buffer *next = current->next; gc_root_buffer *next = current->next;
if (GC_REF_GET_COLOR(current->ref) == GC_BLACK) { if (GC_REF_GET_COLOR(current->ref) == GC_BLACK) {
if (EXPECTED(GC_ADDRESS(GC_INFO(current->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
gc_remove_from_roots(current);
} else {
gc_remove_from_additional_roots(current);
}
GC_INFO(current->ref) = 0; /* reset GC_ADDRESS() and keep GC_BLACK */ GC_INFO(current->ref) = 0; /* reset GC_ADDRESS() and keep GC_BLACK */
GC_REMOVE_FROM_ROOTS(current);
} }
current = next; current = next;
} }
@ -850,7 +866,7 @@ static int gc_collect_roots(uint32_t *flags, gc_additional_buffer **additional_b
while (current != &GC_G(roots)) { while (current != &GC_G(roots)) {
GC_REFCOUNT(current->ref)++; GC_REFCOUNT(current->ref)++;
if (GC_REF_GET_COLOR(current->ref) == GC_WHITE) { if (GC_REF_GET_COLOR(current->ref) == GC_WHITE) {
count += gc_collect_white(current->ref, flags, additional_buffer); count += gc_collect_white(current->ref, flags);
} }
current = current->next; current = current->next;
} }
@ -886,13 +902,16 @@ static void gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buff
tail_call: tail_call:
if (root || if (root ||
(GC_ADDRESS(GC_INFO(ref)) != 0 && (GC_ADDRESS(GC_INFO(ref)) != 0 &&
GC_REF_GET_COLOR(ref) == GC_BLACK && GC_REF_GET_COLOR(ref) == GC_BLACK)) {
GC_ADDRESS(GC_INFO(ref)) != GC_ROOT_BUFFER_MAX_ENTRIES)) {
GC_TRACE_REF(ref, "removing from buffer"); GC_TRACE_REF(ref, "removing from buffer");
GC_REFCOUNT(ref)--; GC_REFCOUNT(ref)--;
if (root) { if (root) {
if (EXPECTED(GC_ADDRESS(GC_INFO(root->ref)) < GC_ROOT_BUFFER_MAX_ENTRIES)) {
gc_remove_from_roots(root);
} else {
gc_remove_from_additional_roots(root);
}
GC_INFO(ref) = 0; GC_INFO(ref) = 0;
GC_REMOVE_FROM_ROOTS(root);
root = NULL; root = NULL;
} else { } else {
GC_REMOVE_FROM_BUFFER(ref); GC_REMOVE_FROM_BUFFER(ref);
@ -986,7 +1005,7 @@ ZEND_API int zend_gc_collect_cycles(void)
zend_refcounted *p; zend_refcounted *p;
gc_root_buffer to_free; gc_root_buffer to_free;
uint32_t gc_flags = 0; uint32_t gc_flags = 0;
gc_additional_buffer *additional_buffer; gc_additional_buffer *additional_buffer_snapshot;
#if ZEND_GC_DEBUG #if ZEND_GC_DEBUG
zend_bool orig_gc_full; zend_bool orig_gc_full;
#endif #endif
@ -1010,8 +1029,8 @@ ZEND_API int zend_gc_collect_cycles(void)
#endif #endif
GC_TRACE("Collecting roots"); GC_TRACE("Collecting roots");
additional_buffer = NULL; additional_buffer_snapshot = GC_G(additional_buffer);
count = gc_collect_roots(&gc_flags, &additional_buffer); count = gc_collect_roots(&gc_flags);
#if ZEND_GC_DEBUG #if ZEND_GC_DEBUG
GC_G(gc_full) = orig_gc_full; GC_G(gc_full) = orig_gc_full;
#endif #endif
@ -1055,7 +1074,7 @@ ZEND_API int zend_gc_collect_cycles(void)
while (current != &to_free) { while (current != &to_free) {
p = current->ref; p = current->ref;
GC_G(next_to_free) = current->next; GC_G(next_to_free) = current->next;
if ((GC_TYPE(p) & GC_TYPE_MASK) == IS_OBJECT) { if (GC_TYPE(p) == IS_OBJECT) {
zend_object *obj = (zend_object*)p; zend_object *obj = (zend_object*)p;
if (IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) && if (IS_OBJ_VALID(EG(objects_store).object_buckets[obj->handle]) &&
@ -1092,7 +1111,7 @@ ZEND_API int zend_gc_collect_cycles(void)
p = current->ref; p = current->ref;
GC_G(next_to_free) = current->next; GC_G(next_to_free) = current->next;
GC_TRACE_REF(p, "destroying"); GC_TRACE_REF(p, "destroying");
if ((GC_TYPE(p) & GC_TYPE_MASK) == IS_OBJECT) { if (GC_TYPE(p) == IS_OBJECT) {
zend_object *obj = (zend_object*)p; zend_object *obj = (zend_object*)p;
if (EG(objects_store).object_buckets && if (EG(objects_store).object_buckets &&
@ -1111,7 +1130,7 @@ ZEND_API int zend_gc_collect_cycles(void)
EG(objects_store).free_list_head = obj->handle; EG(objects_store).free_list_head = obj->handle;
p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset); p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset);
} }
} else if ((GC_TYPE(p) & GC_TYPE_MASK) == IS_ARRAY) { } else if (GC_TYPE(p) == IS_ARRAY) {
zend_array *arr = (zend_array*)p; zend_array *arr = (zend_array*)p;
GC_TYPE(arr) = IS_NULL; GC_TYPE(arr) = IS_NULL;
@ -1133,10 +1152,10 @@ ZEND_API int zend_gc_collect_cycles(void)
current = next; current = next;
} }
while (additional_buffer != NULL) { while (GC_G(additional_buffer) != additional_buffer_snapshot) {
gc_additional_buffer *next = additional_buffer->next; gc_additional_buffer *next = GC_G(additional_buffer)->next;
efree(additional_buffer); efree(GC_G(additional_buffer));
additional_buffer = next; GC_G(additional_buffer) = next;
} }
GC_TRACE("Collection finished"); GC_TRACE("Collection finished");

View file

@ -67,6 +67,17 @@ typedef struct _gc_root_buffer {
uint32_t refcount; uint32_t refcount;
} gc_root_buffer; } gc_root_buffer;
#define GC_NUM_ADDITIONAL_ENTRIES \
((4096 - ZEND_MM_OVERHEAD - sizeof(void*) * 2) / sizeof(gc_root_buffer))
typedef struct _gc_additional_bufer gc_additional_buffer;
struct _gc_additional_bufer {
uint32_t used;
gc_additional_buffer *next;
gc_root_buffer buf[GC_NUM_ADDITIONAL_ENTRIES];
};
typedef struct _zend_gc_globals { typedef struct _zend_gc_globals {
zend_bool gc_enabled; zend_bool gc_enabled;
zend_bool gc_active; zend_bool gc_active;
@ -93,6 +104,8 @@ typedef struct _zend_gc_globals {
uint32_t zval_marked_grey; uint32_t zval_marked_grey;
#endif #endif
gc_additional_buffer *additional_buffer;
} zend_gc_globals; } zend_gc_globals;
#ifdef ZTS #ifdef ZTS