Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /