Skip to content

Commit 61c54a8

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

File tree

2 files changed

+137
-18
lines changed

2 files changed

+137
-18
lines changed

Zend/zend_alloc.c

Lines changed: 136 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
@@ -200,6 +203,7 @@ 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+
203207
static bool zend_mm_use_huge_pages = false;
204208

205209
/*
@@ -245,6 +249,7 @@ struct _zend_mm_heap {
245249
size_t size; /* current memory usage */
246250
size_t peak; /* peak memory usage */
247251
#endif
252+
uintptr_t key; /* free slot shadow ptr key */
248253
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
249254
#if ZEND_MM_STAT || ZEND_MM_LIMIT
250255
size_t real_size; /* current size of allocated pages */
@@ -275,6 +280,8 @@ struct _zend_mm_heap {
275280
} custom_heap;
276281
HashTable *tracked_allocs;
277282
#endif
283+
pid_t pid;
284+
php_random_status_state_xoshiro256starstar random_state;
278285
};
279286

280287
struct _zend_mm_chunk {
@@ -318,19 +325,37 @@ struct _zend_mm_huge_list {
318325
#define ZEND_MM_PAGE_ADDR(chunk, page_num) \
319326
((void*)(((zend_mm_page*)(chunk)) + (page_num)))
320327

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

326-
#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) elements,
337+
#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) \
338+
/* Adjusting size requires adjusting elements */ \
339+
(elements / (_BIN_DATA_SIZE(num, size, elements, pages, x, y) / size))
340+
#define _BIN_DATA_ELEMENTS_C(num, size, elements, pages, x, y) \
341+
_BIN_DATA_ELEMENTS(num, size, elements, pages, x, y),
327342
static const uint32_t bin_elements[] = {
328-
ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS, x, y)
343+
ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS_C, x, y)
329344
};
330345

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

336361
#if ZEND_DEBUG
@@ -1248,6 +1273,45 @@ static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
12481273

12491274
#define ZEND_MM_SMALL_SIZE_TO_BIN(size) zend_mm_small_size_to_bin(size)
12501275

1276+
static zend_always_inline zend_mm_free_slot* zend_mm_encode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
1277+
{
1278+
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->key);
1279+
}
1280+
1281+
static zend_always_inline zend_mm_free_slot* zend_mm_decode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
1282+
{
1283+
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->key);
1284+
}
1285+
1286+
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)
1287+
{
1288+
slot->next_free_slot = next;
1289+
*(zend_mm_free_slot**)((char*)slot + bin_shadow_offset[bin_num]) = zend_mm_encode_free_slot(heap, next);
1290+
}
1291+
1292+
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)
1293+
{
1294+
dest->next_free_slot = from->next_free_slot;
1295+
*(zend_mm_free_slot**)((char*)dest + bin_shadow_offset[bin_num]) = *(zend_mm_free_slot**)((char*)from + bin_shadow_offset[bin_num]);
1296+
}
1297+
1298+
static ZEND_COLD ZEND_NORETURN void zend_mm_free_slot_corrupted(void)
1299+
{
1300+
zend_mm_panic("zend_mm_heap corrupted");
1301+
}
1302+
1303+
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)
1304+
{
1305+
zend_mm_free_slot *next = slot->next_free_slot;
1306+
zend_mm_free_slot *shadow = *(zend_mm_free_slot**)((char*)slot + bin_shadow_offset[bin_num]);
1307+
if (EXPECTED(next != NULL)) {
1308+
if (UNEXPECTED(next != zend_mm_decode_free_slot(heap, shadow))) {
1309+
zend_mm_free_slot_corrupted();
1310+
}
1311+
}
1312+
return (zend_mm_free_slot*)next;
1313+
}
1314+
12511315
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)
12521316
{
12531317
zend_mm_chunk *chunk;
@@ -1281,7 +1345,7 @@ static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint
12811345
end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1)));
12821346
heap->free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]);
12831347
do {
1284-
p->next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);
1348+
zend_mm_set_next_free_slot(heap, bin_num, p, (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]));
12851349
#if ZEND_DEBUG
12861350
do {
12871351
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 +1381,7 @@ static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_
13171381

13181382
if (EXPECTED(heap->free_slot[bin_num] != NULL)) {
13191383
zend_mm_free_slot *p = heap->free_slot[bin_num];
1320-
heap->free_slot[bin_num] = p->next_free_slot;
1384+
heap->free_slot[bin_num] = zend_mm_check_next_free_slot(heap, bin_num, p);
13211385
return p;
13221386
} else {
13231387
return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
@@ -1340,7 +1404,7 @@ static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr,
13401404
#endif
13411405

13421406
p = (zend_mm_free_slot*)ptr;
1343-
p->next_free_slot = heap->free_slot[bin_num];
1407+
zend_mm_set_next_free_slot(heap, bin_num, p, heap->free_slot[bin_num]);
13441408
heap->free_slot[bin_num] = p;
13451409
}
13461410

@@ -1904,6 +1968,27 @@ static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZE
19041968
/* Initialization */
19051969
/******************/
19061970

1971+
static zend_result zend_mm_refresh_key(zend_mm_heap *heap)
1972+
{
1973+
php_random_result result = php_random_algo_xoshiro256starstar.generate(&heap->random_state);
1974+
ZEND_ASSERT(result.size == sizeof(uint64_t));
1975+
heap->key = (uintptr_t) result.result;
1976+
return SUCCESS;
1977+
}
1978+
1979+
static zend_result zend_mm_init_key(zend_mm_heap *heap)
1980+
{
1981+
uint64_t seed[4];
1982+
if (php_random_bytes(&seed, sizeof(seed), false) != SUCCESS) {
1983+
return FAILURE;
1984+
}
1985+
1986+
php_random_xoshiro256starstar_seed256(&heap->random_state,
1987+
seed[0], seed[1], seed[2], seed[3]);
1988+
1989+
return zend_mm_refresh_key(heap);
1990+
}
1991+
19071992
static zend_mm_heap *zend_mm_init(void)
19081993
{
19091994
zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE);
@@ -1940,6 +2025,12 @@ static zend_mm_heap *zend_mm_init(void)
19402025
heap->size = 0;
19412026
heap->peak = 0;
19422027
#endif
2028+
if (zend_mm_init_key(heap) != SUCCESS) {
2029+
#if ZEND_MM_ERROR
2030+
fprintf(stderr, "Can't initialize heap\n");
2031+
#endif
2032+
return NULL;
2033+
}
19432034
#if ZEND_MM_LIMIT
19442035
heap->limit = (size_t)Z_L(-1) >> 1;
19452036
heap->overflow = 0;
@@ -1951,12 +2042,13 @@ static zend_mm_heap *zend_mm_init(void)
19512042
heap->storage = NULL;
19522043
#endif
19532044
heap->huge_list = NULL;
2045+
heap->pid = getpid();
19542046
return heap;
19552047
}
19562048

19572049
ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
19582050
{
1959-
zend_mm_free_slot *p, **q;
2051+
zend_mm_free_slot *p, *q;
19602052
zend_mm_chunk *chunk;
19612053
size_t page_offset;
19622054
int page_num;
@@ -1994,15 +2086,15 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
19942086
has_free_pages = true;
19952087
}
19962088
chunk->map[page_num] = ZEND_MM_SRUN_EX(i, free_counter);
1997-
p = p->next_free_slot;
2089+
p = zend_mm_check_next_free_slot(heap, i, p);
19982090
}
19992091

20002092
if (!has_free_pages) {
20012093
continue;
20022094
}
20032095

2004-
q = &heap->free_slot[i];
2005-
p = *q;
2096+
q = (zend_mm_free_slot*)&heap->free_slot[i];
2097+
p = q->next_free_slot;
20062098
while (p != NULL) {
20072099
chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(p, ZEND_MM_CHUNK_SIZE);
20082100
ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted");
@@ -2020,11 +2112,19 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap)
20202112
ZEND_ASSERT(ZEND_MM_SRUN_BIN_NUM(info) == i);
20212113
if (ZEND_MM_SRUN_FREE_COUNTER(info) == bin_elements[i]) {
20222114
/* remove from cache */
2023-
p = p->next_free_slot;
2024-
*q = p;
2115+
if (q == (zend_mm_free_slot*)&heap->free_slot[i]) {
2116+
q->next_free_slot = zend_mm_check_next_free_slot(heap, i, p);
2117+
} else {
2118+
zend_mm_copy_next_free_slot((zend_mm_free_slot*)q, i, p);
2119+
}
2120+
p = zend_mm_check_next_free_slot(heap, i, p);
20252121
} else {
2026-
q = &p->next_free_slot;
2027-
p = *q;
2122+
q = p;
2123+
if (q == (zend_mm_free_slot*)&heap->free_slot[i]) {
2124+
p = q->next_free_slot;
2125+
} else {
2126+
p = zend_mm_check_next_free_slot(heap, i, q);
2127+
}
20282128
}
20292129
}
20302130
}
@@ -2376,6 +2476,18 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
23762476
memset(p->free_map, 0, sizeof(p->free_map) + sizeof(p->map));
23772477
p->free_map[0] = (1L << ZEND_MM_FIRST_PAGE) - 1;
23782478
p->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE);
2479+
2480+
pid_t pid = getpid();
2481+
if (heap->pid != pid) {
2482+
if (zend_mm_init_key(heap) != SUCCESS) {
2483+
zend_mm_panic("Can't initialize heap");
2484+
}
2485+
heap->pid = pid;
2486+
} else {
2487+
if (zend_mm_refresh_key(heap) != SUCCESS) {
2488+
zend_mm_panic("Can't initialize heap");
2489+
}
2490+
}
23792491
}
23802492
}
23812493

@@ -3052,6 +3164,12 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
30523164
heap->size = 0;
30533165
heap->peak = 0;
30543166
#endif
3167+
if (zend_mm_init_key(heap) != SUCCESS) {
3168+
#if ZEND_MM_ERROR
3169+
fprintf(stderr, "Can't initialize heap\n");
3170+
#endif
3171+
return NULL;
3172+
}
30553173
#if ZEND_MM_LIMIT
30563174
heap->limit = (size_t)Z_L(-1) >> 1;
30573175
heap->overflow = 0;
@@ -3076,6 +3194,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
30763194
memcpy(storage->data, data, data_size);
30773195
}
30783196
heap->storage = storage;
3197+
heap->pid = getpid();
30793198
return heap;
30803199
#else
30813200
return NULL;

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)