Skip to content

Commit d25588c

Browse files
committed
Make some parts of _zend_mm_heap read-only at runtime.
As [presented at OffensiveCon 2024](https://youtu.be/dqKFHjcK9hM?t=1622), having trivially callable writeable function pointers at the top of the heap makes it straightforward to turn a limited write into an arbitrary code execution. Disabling ZEND_MM_HEAP by default isn't doable, as it's used by a couple of profilers, so we're making some parts of `_zend_mm_heap` read-only at runtime instead: this will prevent the custom heap functions pointers from being hijacked, and we're also throwing the `shadow_key` there as it doesn't hurt to make it read-only as well.
1 parent 37488d6 commit d25588c

File tree

2 files changed

+74
-39
lines changed

2 files changed

+74
-39
lines changed

Zend/zend_alloc.c

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */
217217

218218
#define ZEND_MM_BINS 30
219219

220+
#define ZEND_MM_RO_HEAP(heap) ((zend_mm_ro_heap*)((char*)(heap) + REAL_PAGE_SIZE))
221+
220222
#if defined(_MSC_VER)
221223
# if UINTPTR_MAX == UINT64_MAX
222224
# define BSWAPPTR(u) _byteswap_uint64(u)
@@ -263,6 +265,23 @@ typedef struct _zend_mm_huge_list zend_mm_huge_list;
263265

264266
static bool zend_mm_use_huge_pages = false;
265267

268+
#define ZEND_MM_DEFAULT_PAGE_SIZE 4096
269+
270+
static size_t zend_mm_get_page_size(void)
271+
{
272+
static size_t page_size = 0;
273+
274+
if (!page_size) {
275+
page_size = zend_get_page_size();
276+
if (!page_size || (page_size & (page_size - 1))) {
277+
/* anyway, we have to return a valid result */
278+
page_size = ZEND_MM_DEFAULT_PAGE_SIZE;
279+
}
280+
}
281+
282+
return page_size;
283+
}
284+
266285
/*
267286
* Memory is retrieved from OS by chunks of fixed size 2MB.
268287
* Inside chunk it's managed by pages of fixed size 4096B.
@@ -306,7 +325,6 @@ struct _zend_mm_heap {
306325
size_t size; /* current memory usage */
307326
size_t peak; /* peak memory usage */
308327
#endif
309-
uintptr_t shadow_key; /* free slot shadow ptr xor key */
310328
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
311329
#if ZEND_MM_STAT || ZEND_MM_LIMIT
312330
size_t real_size; /* current size of allocated pages */
@@ -329,16 +347,25 @@ struct _zend_mm_heap {
329347
double avg_chunks_count; /* average number of chunks allocated per request */
330348
int last_chunks_delete_boundary; /* number of chunks after last deletion */
331349
int last_chunks_delete_count; /* number of deletion over the last boundary */
350+
#if ZEND_MM_CUSTOM
351+
HashTable *tracked_allocs;
352+
#endif
353+
pid_t pid;
354+
zend_random_bytes_insecure_state rand_state;
355+
};
356+
357+
/* This contains security-sensitive data, and is thus mapped as read-only at run-time right after the _zend_mm_heap struct
358+
* and accessed via the ZEND_MM_RO_HEAP macro.*/
359+
struct _zend_mm_ro_heap {
360+
uintptr_t shadow_key; /* free slot shadow ptr xor key */
361+
332362
#if ZEND_MM_CUSTOM
333363
struct {
334364
void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
335365
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
336366
void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
337367
} custom_heap;
338-
HashTable *tracked_allocs;
339368
#endif
340-
pid_t pid;
341-
zend_random_bytes_insecure_state rand_state;
342369
};
343370

344371
struct _zend_mm_chunk {
@@ -349,7 +376,6 @@ struct _zend_mm_chunk {
349376
uint32_t free_tail; /* number of free pages at the end of chunk */
350377
uint32_t num;
351378
char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)];
352-
zend_mm_heap heap_slot; /* used only in main chunk */
353379
zend_mm_page_map free_map; /* 512 bits or 64 bytes */
354380
zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */
355381
};
@@ -1331,18 +1357,18 @@ static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
13311357
static zend_always_inline zend_mm_free_slot* zend_mm_encode_free_slot(const zend_mm_heap *heap, const zend_mm_free_slot *slot)
13321358
{
13331359
#ifdef WORDS_BIGENDIAN
1334-
return (zend_mm_free_slot*)(((uintptr_t)slot) ^ heap->shadow_key);
1360+
return (zend_mm_free_slot*)(((uintptr_t)slot) ^ ZEND_MM_RO_HEAP(heap)->shadow_key);
13351361
#else
1336-
return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot) ^ heap->shadow_key);
1362+
return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot) ^ ZEND_MM_RO_HEAP(heap)->shadow_key);
13371363
#endif
13381364
}
13391365

13401366
static zend_always_inline zend_mm_free_slot* zend_mm_decode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
13411367
{
13421368
#ifdef WORDS_BIGENDIAN
1343-
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->shadow_key);
1369+
return (zend_mm_free_slot*)((uintptr_t)slot ^ ZEND_MM_RO_HEAP(heap)->shadow_key);
13441370
#else
1345-
return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot ^ heap->shadow_key));
1371+
return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot ^ ZEND_MM_RO_HEAP(heap)->shadow_key));
13461372
#endif
13471373
}
13481374

@@ -2045,7 +2071,7 @@ static void zend_mm_free_huge(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZE
20452071

20462072
static void zend_mm_refresh_key(zend_mm_heap *heap)
20472073
{
2048-
zend_random_bytes_insecure(&heap->rand_state, &heap->shadow_key, sizeof(heap->shadow_key));
2074+
zend_random_bytes_insecure(&heap->rand_state, &(ZEND_MM_RO_HEAP(heap)->shadow_key), sizeof(ZEND_MM_RO_HEAP(heap)->shadow_key));
20492075
}
20502076

20512077
static void zend_mm_init_key(zend_mm_heap *heap)
@@ -2056,16 +2082,15 @@ static void zend_mm_init_key(zend_mm_heap *heap)
20562082

20572083
static zend_mm_heap *zend_mm_init(void)
20582084
{
2085+
zend_mm_heap *heap = (zend_mm_heap*)zend_mm_chunk_alloc_int(zend_mm_get_page_size() * 2, zend_mm_get_page_size());
20592086
zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE);
2060-
zend_mm_heap *heap;
20612087

20622088
if (UNEXPECTED(chunk == NULL)) {
20632089
#if ZEND_MM_ERROR
20642090
fprintf(stderr, "Can't initialize heap\n");
20652091
#endif
20662092
return NULL;
20672093
}
2068-
heap = &chunk->heap_slot;
20692094
chunk->heap = heap;
20702095
chunk->next = chunk;
20712096
chunk->prev = chunk;
@@ -2103,6 +2128,9 @@ static zend_mm_heap *zend_mm_init(void)
21032128
#endif
21042129
heap->huge_list = NULL;
21052130
heap->pid = getpid();
2131+
2132+
mprotect(ZEND_MM_RO_HEAP(heap), zend_mm_get_page_size(), PROT_READ);
2133+
21062134
return heap;
21072135
}
21082136

@@ -2431,7 +2459,7 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
24312459

24322460
#if ZEND_MM_CUSTOM
24332461
if (heap->use_custom_heap) {
2434-
if (heap->custom_heap._malloc == tracked_malloc) {
2462+
if (ZEND_MM_RO_HEAP(heap)->custom_heap._malloc == tracked_malloc) {
24352463
if (silent) {
24362464
tracked_free_all();
24372465
}
@@ -2440,13 +2468,15 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
24402468
zend_hash_destroy(heap->tracked_allocs);
24412469
free(heap->tracked_allocs);
24422470
/* Make sure the heap free below does not use tracked_free(). */
2443-
heap->custom_heap._free = __zend_free;
2471+
mprotect(ZEND_MM_RO_HEAP(heap), zend_mm_get_page_size(), PROT_WRITE);
2472+
ZEND_MM_RO_HEAP(heap)->custom_heap._free = __zend_free;
2473+
mprotect(ZEND_MM_RO_HEAP(heap), zend_mm_get_page_size(), PROT_READ);
24442474
}
24452475
heap->size = 0;
24462476
}
24472477

24482478
if (full) {
2449-
heap->custom_heap._free(heap ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC);
2479+
ZEND_MM_RO_HEAP(heap)->custom_heap._free(heap ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC);
24502480
}
24512481
return;
24522482
}
@@ -2511,7 +2541,7 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
25112541

25122542
/* reinitialize the first chunk and heap */
25132543
p = heap->main_chunk;
2514-
p->heap = &p->heap_slot;
2544+
//p->heap = &p->heap_slot;
25152545
p->next = p;
25162546
p->prev = p;
25172547
p->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE;
@@ -2575,7 +2605,7 @@ ZEND_API size_t ZEND_FASTCALL _zend_mm_block_size(zend_mm_heap *heap, void *ptr
25752605
{
25762606
#if ZEND_MM_CUSTOM
25772607
if (UNEXPECTED(heap->use_custom_heap)) {
2578-
if (heap->custom_heap._malloc == tracked_malloc) {
2608+
if (ZEND_MM_RO_HEAP(heap)->custom_heap._malloc == tracked_malloc) {
25792609
zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2;
25802610
zval *size_zv = zend_hash_index_find(heap->tracked_allocs, h);
25812611
if (size_zv) {
@@ -2618,7 +2648,7 @@ ZEND_API bool is_zend_ptr(const void *ptr)
26182648
{
26192649
#if ZEND_MM_CUSTOM
26202650
if (AG(mm_heap)->use_custom_heap) {
2621-
if (AG(mm_heap)->custom_heap._malloc == tracked_malloc) {
2651+
if (ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._malloc == tracked_malloc) {
26222652
zend_ulong h = ((uintptr_t) ptr) >> ZEND_MM_ALIGNMENT_LOG2;
26232653
zval *size_zv = zend_hash_index_find(AG(mm_heap)->tracked_allocs, h);
26242654
if (size_zv) {
@@ -2661,12 +2691,12 @@ ZEND_API bool is_zend_ptr(const void *ptr)
26612691
#if ZEND_MM_CUSTOM
26622692
# define ZEND_MM_CUSTOM_ALLOCATOR(size) do { \
26632693
if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { \
2664-
return AG(mm_heap)->custom_heap._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \
2694+
return ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \
26652695
} \
26662696
} while (0)
26672697
# define ZEND_MM_CUSTOM_DEALLOCATOR(ptr) do { \
26682698
if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) { \
2669-
AG(mm_heap)->custom_heap._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \
2699+
ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \
26702700
return; \
26712701
} \
26722702
} while (0)
@@ -2762,7 +2792,7 @@ ZEND_API void* ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LI
27622792
{
27632793
#if ZEND_MM_CUSTOM
27642794
if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) {
2765-
return AG(mm_heap)->custom_heap._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \
2795+
return ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._malloc(size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); \
27662796
}
27672797
#endif
27682798
return zend_mm_alloc_heap(AG(mm_heap), size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
@@ -2772,7 +2802,7 @@ ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_OR
27722802
{
27732803
#if ZEND_MM_CUSTOM
27742804
if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) {
2775-
AG(mm_heap)->custom_heap._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
2805+
ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._free(ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
27762806
return;
27772807
}
27782808
#endif
@@ -2783,7 +2813,7 @@ ZEND_API void* ZEND_FASTCALL _erealloc(void *ptr, size_t size ZEND_FILE_LINE_DC
27832813
{
27842814
#if ZEND_MM_CUSTOM
27852815
if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) {
2786-
return AG(mm_heap)->custom_heap._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
2816+
return ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
27872817
}
27882818
#endif
27892819
return zend_mm_realloc_heap(AG(mm_heap), ptr, size, 0, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
@@ -2793,7 +2823,7 @@ ZEND_API void* ZEND_FASTCALL _erealloc2(void *ptr, size_t size, size_t copy_size
27932823
{
27942824
#if ZEND_MM_CUSTOM
27952825
if (UNEXPECTED(AG(mm_heap)->use_custom_heap)) {
2796-
return AG(mm_heap)->custom_heap._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
2826+
return ZEND_MM_RO_HEAP(AG(mm_heap))->custom_heap._realloc(ptr, size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
27972827
}
27982828
#endif
27992829
return zend_mm_realloc_heap(AG(mm_heap), ptr, size, 1, copy_size ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
@@ -3057,25 +3087,28 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals)
30573087
tmp = getenv("USE_ZEND_ALLOC");
30583088
if (tmp && !ZEND_ATOL(tmp)) {
30593089
bool tracked = (tmp = getenv("USE_TRACKED_ALLOC")) && ZEND_ATOL(tmp);
3060-
zend_mm_heap *mm_heap = alloc_globals->mm_heap = malloc(sizeof(zend_mm_heap));
3090+
//TODO(jvoisin) fix this
3091+
zend_mm_heap *mm_heap = alloc_globals->mm_heap = malloc(zend_mm_get_page_size() * 2);
30613092
memset(mm_heap, 0, sizeof(zend_mm_heap));
30623093
mm_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD;
30633094
mm_heap->limit = (size_t)Z_L(-1) >> 1;
30643095
mm_heap->overflow = 0;
30653096

3097+
mprotect(ZEND_MM_RO_HEAP(mm_heap), zend_mm_get_page_size(), PROT_WRITE);
30663098
if (!tracked) {
30673099
/* Use system allocator. */
3068-
mm_heap->custom_heap._malloc = __zend_malloc;
3069-
mm_heap->custom_heap._free = __zend_free;
3070-
mm_heap->custom_heap._realloc = __zend_realloc;
3100+
ZEND_MM_RO_HEAP(mm_heap)->custom_heap._malloc = __zend_malloc;
3101+
ZEND_MM_RO_HEAP(mm_heap)->custom_heap._free = __zend_free;
3102+
ZEND_MM_RO_HEAP(mm_heap)->custom_heap._realloc = __zend_realloc;
30713103
} else {
30723104
/* Use system allocator and track allocations for auto-free. */
3073-
mm_heap->custom_heap._malloc = tracked_malloc;
3074-
mm_heap->custom_heap._free = tracked_free;
3075-
mm_heap->custom_heap._realloc = tracked_realloc;
3105+
ZEND_MM_RO_HEAP(mm_heap)->custom_heap._malloc = tracked_malloc;
3106+
ZEND_MM_RO_HEAP(mm_heap)->custom_heap._free = tracked_free;
3107+
ZEND_MM_RO_HEAP(mm_heap)->custom_heap._realloc = tracked_realloc;
30763108
mm_heap->tracked_allocs = malloc(sizeof(HashTable));
30773109
zend_hash_init(mm_heap->tracked_allocs, 1024, NULL, NULL, 1);
30783110
}
3111+
mprotect(ZEND_MM_RO_HEAP(mm_heap), zend_mm_get_page_size(), PROT_READ);
30793112
return;
30803113
}
30813114
#endif
@@ -3145,9 +3178,11 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
31453178
_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE;
31463179
} else {
31473180
_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD;
3148-
_heap->custom_heap._malloc = _malloc;
3149-
_heap->custom_heap._free = _free;
3150-
_heap->custom_heap._realloc = _realloc;
3181+
mprotect(ZEND_MM_RO_HEAP(_heap), zend_mm_get_page_size(), PROT_WRITE);
3182+
ZEND_MM_RO_HEAP(_heap)->custom_heap._malloc = _malloc;
3183+
ZEND_MM_RO_HEAP(_heap)->custom_heap._free = _free;
3184+
ZEND_MM_RO_HEAP(_heap)->custom_heap._realloc = _realloc;
3185+
mprotect(ZEND_MM_RO_HEAP(_heap), zend_mm_get_page_size(), PROT_READ);
31513186
}
31523187
#endif
31533188
}
@@ -3161,9 +3196,9 @@ ZEND_API void zend_mm_get_custom_handlers(zend_mm_heap *heap,
31613196
zend_mm_heap *_heap = (zend_mm_heap*)heap;
31623197

31633198
if (heap->use_custom_heap) {
3164-
*_malloc = _heap->custom_heap._malloc;
3165-
*_free = _heap->custom_heap._free;
3166-
*_realloc = _heap->custom_heap._realloc;
3199+
*_malloc = ZEND_MM_RO_HEAP(_heap)->custom_heap._malloc;
3200+
*_free = ZEND_MM_RO_HEAP(_heap)->custom_heap._free;
3201+
*_realloc = ZEND_MM_RO_HEAP(_heap)->custom_heap._realloc;
31673202
} else {
31683203
*_malloc = NULL;
31693204
*_free = NULL;
@@ -3195,7 +3230,7 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
31953230
#if ZEND_MM_STORAGE
31963231
zend_mm_storage tmp_storage, *storage;
31973232
zend_mm_chunk *chunk;
3198-
zend_mm_heap *heap;
3233+
zend_mm_heap *heap = (zend_mm_heap*)zend_mm_chunk_alloc_int(REAL_PAGE_SIZE * 2, REAL_PAGE_SIZE);
31993234

32003235
memcpy((zend_mm_handlers*)&tmp_storage.handlers, handlers, sizeof(zend_mm_handlers));
32013236
tmp_storage.data = data;
@@ -3206,7 +3241,6 @@ ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void
32063241
#endif
32073242
return NULL;
32083243
}
3209-
heap = &chunk->heap_slot;
32103244
chunk->heap = heap;
32113245
chunk->next = chunk;
32123246
chunk->prev = chunk;

Zend/zend_alloc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ ZEND_API void zend_memory_reset_peak_usage(void);
242242

243243
/* Heap functions */
244244
typedef struct _zend_mm_heap zend_mm_heap;
245+
typedef struct _zend_mm_ro_heap zend_mm_ro_heap;
245246

246247
ZEND_API zend_mm_heap *zend_mm_startup(void);
247248
ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full_shutdown, bool silent);

0 commit comments

Comments
 (0)