Skip to content

Commit 1c79a4b

Browse files
committed
Detect heap freelist corruption
1 parent 6d285e3 commit 1c79a4b

File tree

3 files changed

+112
-14
lines changed

3 files changed

+112
-14
lines changed

Zend/zend_alloc.c

Lines changed: 110 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
#include "zend_multiply.h"
5959
#include "zend_bitset.h"
6060
#include "zend_mmap.h"
61+
#include "zend_portability.h"
62+
#include "ext/random/php_random_csprng.h"
63+
#include "ext/random/php_random.h"
6164
#include <signal.h>
6265

6366
#ifdef HAVE_UNISTD_H
@@ -200,6 +203,8 @@ typedef struct _zend_mm_free_slot zend_mm_free_slot;
200203
typedef struct _zend_mm_chunk zend_mm_chunk;
201204
typedef struct _zend_mm_huge_list zend_mm_huge_list;
202205

206+
typedef uintptr_t zend_mm_free_slot_shadow;
207+
203208
static bool zend_mm_use_huge_pages = false;
204209

205210
/*
@@ -245,6 +250,7 @@ struct _zend_mm_heap {
245250
size_t size; /* current memory usage */
246251
size_t peak; /* peak memory usage */
247252
#endif
253+
uintptr_t key;
248254
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
249255
#if ZEND_MM_STAT || ZEND_MM_LIMIT
250256
size_t real_size; /* current size of allocated pages */
@@ -275,6 +281,8 @@ struct _zend_mm_heap {
275281
} custom_heap;
276282
HashTable *tracked_allocs;
277283
#endif
284+
pid_t pid;
285+
php_random_status_state_xoshiro256starstar random_state;
278286
};
279287

280288
struct _zend_mm_chunk {
@@ -318,7 +326,7 @@ struct _zend_mm_huge_list {
318326
#define ZEND_MM_PAGE_ADDR(chunk, page_num) \
319327
((void*)(((zend_mm_page*)(chunk)) + (page_num)))
320328

321-
#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,
329+
#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) MAX(size, sizeof(void*)*2),
322330
static const uint32_t bin_data_size[] = {
323331
ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)
324332
};
@@ -333,6 +341,12 @@ static const uint32_t bin_pages[] = {
333341
ZEND_MM_BINS_INFO(_BIN_DATA_PAGES, x, y)
334342
};
335343

344+
#define _BIN_SHADOW_OFFSET(num, size, elements, pages, x, y) \
345+
((MAX(MIN(size, 64), sizeof(void*)*2) - sizeof(void*)) / sizeof(void*)),
346+
static const uint32_t bin_shadow_offset[] = {
347+
ZEND_MM_BINS_INFO(_BIN_SHADOW_OFFSET, x, y)
348+
};
349+
336350
#if ZEND_DEBUG
337351
ZEND_COLD void zend_debug_alloc_output(char *format, ...)
338352
{
@@ -1248,6 +1262,35 @@ static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
12481262

12491263
#define ZEND_MM_SMALL_SIZE_TO_BIN(size) zend_mm_small_size_to_bin(size)
12501264

1265+
static zend_always_inline void zend_mm_set_next_free_slot(zend_mm_heap *heap, uint32_t bin_num, zend_mm_free_slot *slot, zend_mm_free_slot *next)
1266+
{
1267+
slot->next_free_slot = next;
1268+
*((zend_mm_free_slot_shadow*)slot + bin_shadow_offset[bin_num]) = (zend_mm_free_slot_shadow)next ^ heap->key;
1269+
}
1270+
1271+
static zend_always_inline void zend_mm_copy_next_free_slot(zend_mm_free_slot* dest, uint32_t bin_num, zend_mm_free_slot* from)
1272+
{
1273+
dest->next_free_slot = from->next_free_slot;
1274+
*((zend_mm_free_slot_shadow*)dest + bin_shadow_offset[bin_num]) = *((zend_mm_free_slot_shadow*)from + bin_shadow_offset[bin_num]);
1275+
}
1276+
1277+
static ZEND_COLD ZEND_NORETURN void zend_mm_free_slot_corrupted(void)
1278+
{
1279+
zend_mm_panic("zend_mm_heap corrupted");
1280+
}
1281+
1282+
static zend_always_inline zend_mm_free_slot *zend_mm_check_next_free_slot(zend_mm_heap *heap, uint32_t bin_num, zend_mm_free_slot* slot)
1283+
{
1284+
zend_mm_free_slot *next = slot->next_free_slot;
1285+
if (EXPECTED(next != NULL)) {
1286+
zend_mm_free_slot_shadow shadow = *((zend_mm_free_slot_shadow*)slot + bin_shadow_offset[bin_num]);
1287+
if (UNEXPECTED(next != (zend_mm_free_slot*)(shadow ^ heap->key))) {
1288+
zend_mm_free_slot_corrupted();
1289+
}
1290+
}
1291+
return next;
1292+
}
1293+
12511294
static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint32_t bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
12521295
{
12531296
zend_mm_chunk *chunk;
@@ -1281,7 +1324,7 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint
12811324
end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1)));
12821325
heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]);
12831326
do {
1284-
p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);
1327+
zend_mm_set_next_free_slot(heap, bin_num, p, (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]));
12851328
#if ZEND_DEBUG
12861329
do {
12871330
zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)));
@@ -1317,7 +1360,7 @@ static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_
13171360

13181361
if (EXPECTED(heap->free_slot[bin_num] != NULL)) {
13191362
zend_mm_free_slot *p = heap->free_slot[bin_num];
1320-
heap->free_slot[bin_num] = p->next_free_slot;
1363+
heap->free_slot[bin_num] = zend_mm_check_next_free_slot(heap, bin_num, p);
13211364
return p;
13221365
} else {
13231366
return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
@@ -1340,7 +1383,7 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr,
13401383
#endif
13411384

13421385
p = (zend_mm_free_slot*)ptr;
1343-
p->next_free_slot = heap->free_slot[bin_num];
1386+
zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]);
13441387
heap->free_slot[bin_num] = p;
13451388
}
13461389

@@ -1904,6 +1947,27 @@ static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZE
19041947
/* Initialization */
19051948
/******************/
19061949

1950+
static zend_result zend_mm_refresh_key(zend_mm_heap *heap)
1951+
{
1952+
php_random_result result = php_random_algo_xoshiro256starstar.generate(&heap->random_state);
1953+
ZEND_ASSERT(result.size == sizeof(uint64_t));
1954+
heap->key = (uintptr_t) result.result;
1955+
return SUCCESS;
1956+
}
1957+
1958+
static zend_result zend_mm_init_key(zend_mm_heap *heap)
1959+
{
1960+
uint64_t seed[4];
1961+
if (php_random_bytes(&seed, sizeof(seed), false) != SUCCESS) {
1962+
return FAILURE;
1963+
}
1964+
1965+
php_random_xoshiro256starstar_seed256(&heap->random_state,
1966+
seed[0], seed[1], seed[2], seed[3]);
1967+
1968+
return zend_mm_refresh_key(heap);
1969+
}
1970+
19071971
static zend_mm_heap *zend_mm_init(void)
19081972
{
19091973
zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE);
@@ -1940,6 +2004,12 @@ static zend_mm_heap *zend_mm_init(void)
19402004
heap->size = 0;
19412005
heap->peak = 0;
19422006
#endif
2007+
if (zend_mm_init_key(heap) != SUCCESS) {
2008+
#if ZEND_MM_ERROR
2009+
fprintf(stderr, "Can't initialize heap\n");
2010+
#endif
2011+
return NULL;
2012+
}
19432013
#if ZEND_MM_LIMIT
19442014
heap->limit = (size_t)Z_L(-1) >> 1;
19452015
heap->overflow = 0;
@@ -1951,12 +2021,13 @@ static zend_mm_heap *zend_mm_init(void)
19512021
heap->storage = NULL;
19522022
#endif
19532023
heap->huge_list = NULL;
2024+
heap->pid = getpid();
19542025
return heap;
19552026
}
19562027

19572028
ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
19582029
{
1959-
zend_mm_free_slot *p, **q;
2030+
zend_mm_free_slot *p, *q;
19602031
zend_mm_chunk *chunk;
19612032
size_t page_offset;
19622033
int page_num;
@@ -1994,15 +2065,15 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
19942065
has_free_pages = true;
19952066
}
19962067
chunk->map[page_num] = ZEND_MM_SRUN_EX(i, free_counter);
1997-
p = p->next_free_slot;
2068+
p = zend_mm_check_next_free_slot(heap, i, p);
19982069
}
19992070

20002071
if (!has_free_pages) {
20012072
continue;
20022073
}
20032074

2004-
q = &heap->free_slot[i];
2005-
p = *q;
2075+
q = (zend_mm_free_slot*)&heap->free_slot[i];
2076+
p = q->next_free_slot;
20062077
while (p != NULL) {
20072078
chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE);
20082079
ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted");
@@ -2020,11 +2091,19 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
20202091
ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i);
20212092
if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[i]) {
20222093
/* remove from cache */
2023-
p = p->next_free_slot;
2024-
*q = p;
2094+
if (q == (zend_mm_free_slot*)&heap->free_slot[i]) {
2095+
q->next_free_slot = zend_mm_check_next_free_slot(heap, i, p);
2096+
} else {
2097+
zend_mm_copy_next_free_slot((zend_mm_free_slot*)q, i, p);
2098+
}
2099+
p = zend_mm_check_next_free_slot(heap, i, p);
20252100
} else {
2026-
q = &p->next_free_slot;
2027-
p = *q;
2101+
q = p;
2102+
if (q == (zend_mm_free_slot*)&heap->free_slot[i]) {
2103+
p = q->next_free_slot;
2104+
} else {
2105+
p = zend_mm_check_next_free_slot(heap, i, q);
2106+
}
20282107
}
20292108
}
20302109
}
@@ -2376,6 +2455,18 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
23762455
memset(p->free_map, 0, sizeof(p->free_map) + sizeof(p->map));
23772456
p->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1;
23782457
p->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE);
2458+
2459+
pid_t pid = getpid();
2460+
if (heap->pid != pid) {
2461+
if (zend_mm_init_key(heap) != SUCCESS) {
2462+
zend_mm_panic("Can't initialize heap");
2463+
}
2464+
heap->pid = pid;
2465+
} else {
2466+
if (zend_mm_refresh_key(heap) != SUCCESS) {
2467+
zend_mm_panic("Can't initialize heap");
2468+
}
2469+
}
23792470
}
23802471
}
23812472

@@ -3052,6 +3143,12 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
30523143
heap->size = 0;
30533144
heap->peak = 0;
30543145
#endif
3146+
if (zend_mm_init_key(heap) != SUCCESS) {
3147+
#if ZEND_MM_ERROR
3148+
fprintf(stderr, "Can't initialize heap\n");
3149+
#endif
3150+
return NULL;
3151+
}
30553152
#if ZEND_MM_LIMIT
30563153
heap->limit = (size_t)Z_L(-1) >> 1;
30573154
heap->overflow = 0;
@@ -3076,6 +3173,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
30763173
memcpy(storage->data, data, data_size);
30773174
}
30783175
heap->storage = storage;
3176+
heap->pid = getpid();
30793177
return heap;
30803178
#else
30813179
return NULL;

Zend/zend_alloc_sizes.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
/* num, size, count, pages */
3232
#define ZEND_MM_BINS_INFO(_, x, y) \
33-
_( 0, 8, 512, 1, x, y) \
33+
_( 0, 8, 256, 1, x, y) \
3434
_( 1, 16, 256, 1, x, y) \
3535
_( 2, 24, 170, 1, x, y) \
3636
_( 3, 32, 128, 1, x, y) \

ext/random/engine_xoshiro256starstar.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ static bool unserialize(void *state, HashTable *data)
154154
return true;
155155
}
156156

157-
const php_random_algo php_random_algo_xoshiro256starstar = {
157+
PHPAPI const php_random_algo php_random_algo_xoshiro256starstar = {
158158
sizeof(php_random_status_state_xoshiro256starstar),
159159
generate,
160160
range,

0 commit comments

Comments
 (0)