Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 78 additions & 10 deletions Zend/zend_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,11 @@ struct _zend_mm_chunk {
zend_mm_heap *heap;
zend_mm_chunk *next;
zend_mm_chunk *prev;
zend_mm_chunk *next_shadow; /* shadow of "next" while the chunk is cached */
uint32_t free_pages; /* number of free pages */
uint32_t free_tail; /* number of free pages at the end of chunk */
uint32_t num;
char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)];
char reserve[64 - (sizeof(void*) * 4 + sizeof(uint32_t) * 3)];
zend_mm_heap heap_slot; /* used only in main chunk */
zend_mm_page_map free_map; /* 512 bits or 64 bytes */
zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */
Expand Down Expand Up @@ -883,6 +884,64 @@ static zend_always_inline void zend_mm_chunk_init(zend_mm_heap *heap, zend_mm_ch
chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE);
}

/* Cached chunks are linked through their headers, which live in memory a heap
* overflow can reach, so the link is mirrored in an encoded shadow. The shadow
* is byte-swapped, so that small overwrites hit the most significant bytes of
* the address, XOR'ed with the heap key, and XOR'ed with its own address so
* that a valid (link, shadow) pair cannot be replayed into another chunk. */
static zend_always_inline zend_mm_chunk *zend_mm_encode_cached_chunk(const zend_mm_heap *heap, const void *holder, const zend_mm_chunk *next)
{
#ifdef WORDS_BIGENDIAN
return (zend_mm_chunk*)((uintptr_t)next ^ heap->shadow_key ^ (uintptr_t)holder);
#else
return (zend_mm_chunk*)(BSWAPPTR((uintptr_t)next) ^ heap->shadow_key ^ (uintptr_t)holder);
#endif
}

static zend_always_inline zend_mm_chunk *zend_mm_decode_cached_chunk_key(uintptr_t key, const void *holder, const zend_mm_chunk *encoded)
{
#ifdef WORDS_BIGENDIAN
zend_mm_chunk *next = (zend_mm_chunk*)((uintptr_t)encoded ^ key ^ (uintptr_t)holder);
#else
zend_mm_chunk *next = (zend_mm_chunk*)(BSWAPPTR((uintptr_t)encoded ^ key ^ (uintptr_t)holder));
#endif

ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(next, ZEND_MM_CHUNK_SIZE) == 0, "zend_mm_heap corrupted");
return next;
}

static zend_always_inline void zend_mm_set_next_cached_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk, zend_mm_chunk *next)
{
chunk->next = next;
chunk->next_shadow = zend_mm_encode_cached_chunk(heap, &chunk->next_shadow, next);
}

static zend_always_inline zend_mm_chunk *zend_mm_get_next_cached_chunk_key(uintptr_t key, const zend_mm_chunk *chunk)
{
zend_mm_chunk *next = zend_mm_decode_cached_chunk_key(key, &chunk->next_shadow, chunk->next_shadow);

ZEND_MM_CHECK(chunk->next == next, "zend_mm_heap corrupted");
return next;
}

static zend_always_inline zend_mm_chunk *zend_mm_get_next_cached_chunk(const zend_mm_heap *heap, const zend_mm_chunk *chunk)
{
return zend_mm_get_next_cached_chunk_key(heap->shadow_key, chunk);
}

/* Re-encode the cached links after the heap key changed. */
static zend_always_inline void zend_mm_rekey_cached_chunks(zend_mm_heap *heap, uintptr_t old_key)
{
zend_mm_chunk *chunk = heap->cached_chunks;

while (chunk != NULL) {
zend_mm_chunk *next = zend_mm_get_next_cached_chunk_key(old_key, chunk);

zend_mm_set_next_cached_chunk(heap, chunk, next);
chunk = next;
}
}

/***********************/
/* Huge Runs (forward) */
/***********************/
Expand Down Expand Up @@ -1031,7 +1090,9 @@ static void *zend_mm_alloc_pages(zend_mm_heap *heap, uint32_t pages_count ZEND_F
if (heap->cached_chunks) {
heap->cached_chunks_count--;
chunk = heap->cached_chunks;
heap->cached_chunks = chunk->next;
/* The list head lives in the heap, which is as reachable as the chunk headers. */
ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(chunk, ZEND_MM_CHUNK_SIZE) == 0, "zend_mm_heap corrupted");
heap->cached_chunks = zend_mm_get_next_cached_chunk(heap, chunk);
} else {
#if ZEND_MM_LIMIT
if (UNEXPECTED(ZEND_MM_CHUNK_SIZE > heap->limit - heap->real_size)) {
Expand Down Expand Up @@ -1150,7 +1211,7 @@ static zend_always_inline void zend_mm_delete_chunk(zend_mm_heap *heap, zend_mm_
&& heap->last_chunks_delete_count >= 4)) {
/* delay deletion */
heap->cached_chunks_count++;
chunk->next = heap->cached_chunks;
zend_mm_set_next_cached_chunk(heap, chunk, heap->cached_chunks);
heap->cached_chunks = chunk;
} else {
#if ZEND_MM_STAT || ZEND_MM_LIMIT
Expand All @@ -1168,7 +1229,7 @@ static zend_always_inline void zend_mm_delete_chunk(zend_mm_heap *heap, zend_mm_
zend_mm_chunk_free(heap, chunk, ZEND_MM_CHUNK_SIZE);
} else {
//TODO: select the best chunk to delete???
chunk->next = heap->cached_chunks->next;
zend_mm_set_next_cached_chunk(heap, chunk, zend_mm_get_next_cached_chunk(heap, heap->cached_chunks));
zend_mm_chunk_free(heap, heap->cached_chunks, ZEND_MM_CHUNK_SIZE);
heap->cached_chunks = chunk;
}
Expand Down Expand Up @@ -2037,6 +2098,8 @@ ZEND_API void zend_mm_refresh_key_child(zend_mm_heap *heap)
}
}

zend_mm_rekey_cached_chunks(heap, old_key);

#if ZEND_DEBUG
heap->pid = getpid();
#endif
Expand Down Expand Up @@ -2489,7 +2552,7 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
p = heap->main_chunk->next;
while (p != heap->main_chunk) {
zend_mm_chunk *q = p->next;
p->next = heap->cached_chunks;
zend_mm_set_next_cached_chunk(heap, p, heap->cached_chunks);
heap->cached_chunks = p;
p = q;
heap->chunks_count--;
Expand All @@ -2500,7 +2563,7 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
/* free all cached chunks */
while (heap->cached_chunks) {
p = heap->cached_chunks;
heap->cached_chunks = p->next;
heap->cached_chunks = zend_mm_get_next_cached_chunk(heap, p);
zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE);
}
/* free the first chunk */
Expand All @@ -2511,16 +2574,16 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
while ((double)heap->cached_chunks_count + 0.9 > heap->avg_chunks_count &&
heap->cached_chunks) {
p = heap->cached_chunks;
heap->cached_chunks = p->next;
heap->cached_chunks = zend_mm_get_next_cached_chunk(heap, p);
zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE);
heap->cached_chunks_count--;
}
/* clear cached chunks */
p = heap->cached_chunks;
while (p != NULL) {
zend_mm_chunk *q = p->next;
zend_mm_chunk *q = zend_mm_get_next_cached_chunk(heap, p);
memset(p, 0, sizeof(zend_mm_chunk));
p->next = q;
zend_mm_set_next_cached_chunk(heap, p, q);
p = q;
}

Expand Down Expand Up @@ -2557,7 +2620,12 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
&& "heap was re-used without calling zend_mm_refresh_key_child() after a fork");
#endif

uintptr_t old_key = heap->shadow_key;

zend_mm_refresh_key(heap);

/* Cached chunks outlive the request, so re-encode their links */
zend_mm_rekey_cached_chunks(heap, old_key);
}
}

Expand Down Expand Up @@ -2904,7 +2972,7 @@ ZEND_API zend_result zend_set_memory_limit(size_t memory_limit)
/* free some cached chunks to fit into new memory limit */
do {
zend_mm_chunk *p = heap->cached_chunks;
heap->cached_chunks = p->next;
heap->cached_chunks = zend_mm_get_next_cached_chunk(heap, p);
zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE);
heap->cached_chunks_count--;
heap->real_size -= ZEND_MM_CHUNK_SIZE;
Expand Down
Loading