Skip to content

Commit e342f5f

Browse files
committed
Detect heap freelist corruption
1 parent 378b015 commit e342f5f

File tree

1 file changed

+135
-17
lines changed

1 file changed

+135
-17
lines changed

Zend/zend_alloc.c

Lines changed: 135 additions & 17 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
@@ -245,6 +248,7 @@ struct _zend_mm_heap {
245248
size_t size; /* current memory usage */
246249
size_t peak; /* peak memory usage */
247250
#endif
251+
uintptr_t key; /* free slot shadow ptr key */
248252
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
249253
#if ZEND_MM_STAT || ZEND_MM_LIMIT
250254
size_t real_size; /* current size of allocated pages */
@@ -275,6 +279,8 @@ struct _zend_mm_heap {
275279
} custom_heap;
276280
HashTable *tracked_allocs;
277281
#endif
282+
pid_t pid;
283+
php_random_status_state_xoshiro256starstar random_state;
278284
};
279285

280286
struct _zend_mm_chunk {
@@ -318,19 +324,37 @@ struct _zend_mm_huge_list {
318324
#define ZEND_MM_PAGE_ADDR(chunk, page_num) \
319325
((void*)(((zend_mm_page*)(chunk)) + (page_num)))
320326

321-
#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,
327+
#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) \
328+
/* Need two words for free slot pointer and shadow */ \
329+
MAX(size, sizeof(zend_mm_free_slot*)*2)
330+
#define _BIN_DATA_SIZE_C(num, size, elements, pages, x, y) \
331+
_BIN_DATA_SIZE(num, size, elements, pages, x, y),
322332
static const uint32_t bin_data_size[] = {
323-
ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)
333+
ZEND_MM_BINS_INFO(_BIN_DATA_SIZE_C, x, y)
324334
};
325335

326-
#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) elements,
336+
#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) \
337+
/* Adjusting size requires adjusting elements */ \
338+
(elements / (_BIN_DATA_SIZE(num, size, elements, pages, x, y) / size))
339+
#define _BIN_DATA_ELEMENTS_C(num, size, elements, pages, x, y) \
340+
_BIN_DATA_ELEMENTS(num, size, elements, pages, x, y),
327341
static const uint32_t bin_elements[] = {
328-
ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS, x, y)
342+
ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS_C, x, y)
329343
};
330344

331-
#define _BIN_DATA_PAGES(num, size, elements, pages, x, y) pages,
345+
#define _BIN_DATA_PAGES(num, size, elements, pages, x, y) pages
346+
#define _BIN_DATA_PAGES_C(num, size, elements, pages, x, y) \
347+
_BIN_DATA_PAGES(num, size, elements, pages, x, y),
332348
static const uint32_t bin_pages[] = {
333-
ZEND_MM_BINS_INFO(_BIN_DATA_PAGES, x, y)
349+
ZEND_MM_BINS_INFO(_BIN_DATA_PAGES_C, x, y)
350+
};
351+
352+
#define _BIN_SHADOW_OFFSET(num, size, elements, pages, x, y) \
353+
(_BIN_DATA_SIZE(num, size, elements, pages, x, y) - sizeof(zend_mm_free_slot*))
354+
#define _BIN_SHADOW_OFFSET_C(num, size, elements, pages, x, y) \
355+
_BIN_SHADOW_OFFSET(num, size, elements, pages, x, y),
356+
static const uint32_t bin_shadow_offset[] = {
357+
ZEND_MM_BINS_INFO(_BIN_SHADOW_OFFSET_C, x, y)
334358
};
335359

336360
#if ZEND_DEBUG
@@ -1248,6 +1272,45 @@ static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
12481272

12491273
#define ZEND_MM_SMALL_SIZE_TO_BIN(size) zend_mm_small_size_to_bin(size)
12501274

1275+
static zend_always_inline zend_mm_free_slot* zend_mm_encode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
1276+
{
1277+
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->key);
1278+
}
1279+
1280+
static zend_always_inline zend_mm_free_slot* zend_mm_decode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
1281+
{
1282+
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->key);
1283+
}
1284+
1285+
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)
1286+
{
1287+
slot->next_free_slot = next;
1288+
*(zend_mm_free_slot**)((char*)slot + bin_shadow_offset[bin_num]) = zend_mm_encode_free_slot(heap, next);
1289+
}
1290+
1291+
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)
1292+
{
1293+
dest->next_free_slot = from->next_free_slot;
1294+
*(zend_mm_free_slot**)((char*)dest + bin_shadow_offset[bin_num]) = *(zend_mm_free_slot**)((char*)from + bin_shadow_offset[bin_num]);
1295+
}
1296+
1297+
static ZEND_COLD ZEND_NORETURN void zend_mm_free_slot_corrupted(void)
1298+
{
1299+
zend_mm_panic("zend_mm_heap corrupted");
1300+
}
1301+
1302+
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)
1303+
{
1304+
zend_mm_free_slot *next = slot->next_free_slot;
1305+
zend_mm_free_slot *shadow = *(zend_mm_free_slot**)((char*)slot + bin_shadow_offset[bin_num]);
1306+
if (EXPECTED(next != NULL)) {
1307+
if (UNEXPECTED(next != zend_mm_decode_free_slot(heap, shadow))) {
1308+
zend_mm_free_slot_corrupted();
1309+
}
1310+
}
1311+
return (zend_mm_free_slot*)next;
1312+
}
1313+
12511314
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)
12521315
{
12531316
zend_mm_chunk *chunk;
@@ -1281,7 +1344,7 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint
12811344
end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1)));
12821345
heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]);
12831346
do {
1284-
p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);
1347+
zend_mm_set_next_free_slot(heap, bin_num, p, (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]));
12851348
#if ZEND_DEBUG
12861349
do {
12871350
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 +1380,7 @@ static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_
13171380

13181381
if (EXPECTED(heap->free_slot[bin_num] != NULL)) {
13191382
zend_mm_free_slot *p = heap->free_slot[bin_num];
1320-
heap->free_slot[bin_num] = p->next_free_slot;
1383+
heap->free_slot[bin_num] = zend_mm_check_next_free_slot(heap, bin_num, p);
13211384
return p;
13221385
} else {
13231386
return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
@@ -1340,7 +1403,7 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr,
13401403
#endif
13411404

13421405
p = (zend_mm_free_slot*)ptr;
1343-
p->next_free_slot = heap->free_slot[bin_num];
1406+
zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]);
13441407
heap->free_slot[bin_num] = p;
13451408
}
13461409

@@ -1904,6 +1967,27 @@ static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZE
19041967
/* Initialization */
19051968
/******************/
19061969

1970+
static zend_result zend_mm_refresh_key(zend_mm_heap *heap)
1971+
{
1972+
php_random_result result = php_random_algo_xoshiro256starstar.generate(&heap->random_state);
1973+
ZEND_ASSERT(result.size == sizeof(uint64_t));
1974+
heap->key = (uintptr_t) result.result;
1975+
return SUCCESS;
1976+
}
1977+
1978+
static zend_result zend_mm_init_key(zend_mm_heap *heap)
1979+
{
1980+
uint64_t seed[4];
1981+
if (php_random_bytes(&seed, sizeof(seed), false) != SUCCESS) {
1982+
return FAILURE;
1983+
}
1984+
1985+
php_random_xoshiro256starstar_seed256(&heap->random_state,
1986+
seed[0], seed[1], seed[2], seed[3]);
1987+
1988+
return zend_mm_refresh_key(heap);
1989+
}
1990+
19071991
static zend_mm_heap *zend_mm_init(void)
19081992
{
19091993
zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE);
@@ -1940,6 +2024,12 @@ static zend_mm_heap *zend_mm_init(void)
19402024
heap->size = 0;
19412025
heap->peak = 0;
19422026
#endif
2027+
if (zend_mm_init_key(heap) != SUCCESS) {
2028+
#if ZEND_MM_ERROR
2029+
fprintf(stderr, "Can't initialize heap\n");
2030+
#endif
2031+
return NULL;
2032+
}
19432033
#if ZEND_MM_LIMIT
19442034
heap->limit = (size_t)Z_L(-1) >> 1;
19452035
heap->overflow = 0;
@@ -1951,12 +2041,13 @@ static zend_mm_heap *zend_mm_init(void)
19512041
heap->storage = NULL;
19522042
#endif
19532043
heap->huge_list = NULL;
2044+
heap->pid = getpid();
19542045
return heap;
19552046
}
19562047

19572048
ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
19582049
{
1959-
zend_mm_free_slot *p, **q;
2050+
zend_mm_free_slot *p, *q;
19602051
zend_mm_chunk *chunk;
19612052
size_t page_offset;
19622053
int page_num;
@@ -1994,15 +2085,15 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
19942085
has_free_pages = true;
19952086
}
19962087
chunk->map[page_num] = ZEND_MM_SRUN_EX(i, free_counter);
1997-
p = p->next_free_slot;
2088+
p = zend_mm_check_next_free_slot(heap, i, p);
19982089
}
19992090

20002091
if (!has_free_pages) {
20012092
continue;
20022093
}
20032094

2004-
q = &heap->free_slot[i];
2005-
p = *q;
2095+
q = (zend_mm_free_slot*)&heap->free_slot[i];
2096+
p = q->next_free_slot;
20062097
while (p != NULL) {
20072098
chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE);
20082099
ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted");
@@ -2020,11 +2111,19 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
20202111
ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i);
20212112
if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[i]) {
20222113
/* remove from cache */
2023-
p = p->next_free_slot;
2024-
*q = p;
2114+
if (q == (zend_mm_free_slot*)&heap->free_slot[i]) {
2115+
q->next_free_slot = zend_mm_check_next_free_slot(heap, i, p);
2116+
} else {
2117+
zend_mm_copy_next_free_slot((zend_mm_free_slot*)q, i, p);
2118+
}
2119+
p = zend_mm_check_next_free_slot(heap, i, p);
20252120
} else {
2026-
q = &p->next_free_slot;
2027-
p = *q;
2121+
q = p;
2122+
if (q == (zend_mm_free_slot*)&heap->free_slot[i]) {
2123+
p = q->next_free_slot;
2124+
} else {
2125+
p = zend_mm_check_next_free_slot(heap, i, q);
2126+
}
20282127
}
20292128
}
20302129
}
@@ -2376,6 +2475,18 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
23762475
memset(p->free_map, 0, sizeof(p->free_map) + sizeof(p->map));
23772476
p->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1;
23782477
p->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE);
2478+
2479+
pid_t pid = getpid();
2480+
if (heap->pid != pid) {
2481+
if (zend_mm_init_key(heap) != SUCCESS) {
2482+
zend_mm_panic("Can't initialize heap");
2483+
}
2484+
heap->pid = pid;
2485+
} else {
2486+
if (zend_mm_refresh_key(heap) != SUCCESS) {
2487+
zend_mm_panic("Can't initialize heap");
2488+
}
2489+
}
23792490
}
23802491
}
23812492

@@ -3052,6 +3163,12 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
30523163
heap->size = 0;
30533164
heap->peak = 0;
30543165
#endif
3166+
if (zend_mm_init_key(heap) != SUCCESS) {
3167+
#if ZEND_MM_ERROR
3168+
fprintf(stderr, "Can't initialize heap\n");
3169+
#endif
3170+
return NULL;
3171+
}
30553172
#if ZEND_MM_LIMIT
30563173
heap->limit = (size_t)Z_L(-1) >> 1;
30573174
heap->overflow = 0;
@@ -3076,6 +3193,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
30763193
memcpy(storage->data, data, data_size);
30773194
}
30783195
heap->storage = storage;
3196+
heap->pid = getpid();
30793197
return heap;
30803198
#else
30813199
return NULL;

0 commit comments

Comments
 (0)