Skip to content

Commit 96cbe36

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 96cbe36

File tree

1 file changed

+25
-6
lines changed

1 file changed

+25
-6
lines changed

Zend/zend_alloc.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,20 @@ static bool zend_mm_use_huge_pages = false;
296296
*/
297297

298298
struct _zend_mm_heap {
299+
union { /* This union contains security-relevant properties, and is thus made read-only at run-time. */
300+
struct {
301+
uintptr_t shadow_key; /* free slot shadow ptr xor key */
302+
#if ZEND_MM_CUSTOM
303+
struct {
304+
void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
305+
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
306+
void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
307+
} custom_heap;
308+
#endif
309+
};
310+
char padding [ZEND_MM_PAGE_SIZE];
311+
};
312+
299313
#if ZEND_MM_CUSTOM
300314
int use_custom_heap;
301315
#endif
@@ -306,7 +320,6 @@ struct _zend_mm_heap {
306320
size_t size; /* current memory usage */
307321
size_t peak; /* peak memory usage */
308322
#endif
309-
uintptr_t shadow_key; /* free slot shadow ptr xor key */
310323
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
311324
#if ZEND_MM_STAT || ZEND_MM_LIMIT
312325
size_t real_size; /* current size of allocated pages */
@@ -330,11 +343,6 @@ struct _zend_mm_heap {
330343
int last_chunks_delete_boundary; /* number of chunks after last deletion */
331344
int last_chunks_delete_count; /* number of deletion over the last boundary */
332345
#if ZEND_MM_CUSTOM
333-
struct {
334-
void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
335-
void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
336-
void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
337-
} custom_heap;
338346
HashTable *tracked_allocs;
339347
#endif
340348
pid_t pid;
@@ -2103,6 +2111,9 @@ static zend_mm_heap *zend_mm_init(void)
21032111
#endif
21042112
heap->huge_list = NULL;
21052113
heap->pid = getpid();
2114+
2115+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_READ);
2116+
21062117
return heap;
21072118
}
21082119

@@ -2440,7 +2451,9 @@ void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent)
24402451
zend_hash_destroy(heap->tracked_allocs);
24412452
free(heap->tracked_allocs);
24422453
/* Make sure the heap free below does not use tracked_free(). */
2454+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_WRITE);
24432455
heap->custom_heap._free = __zend_free;
2456+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_READ);
24442457
}
24452458
heap->size = 0;
24462459
}
@@ -3063,6 +3076,7 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals)
30633076
mm_heap->limit = (size_t)Z_L(-1) >> 1;
30643077
mm_heap->overflow = 0;
30653078

3079+
mprotect(mm_heap, ZEND_MM_PAGE_SIZE, PROT_WRITE);
30663080
if (!tracked) {
30673081
/* Use system allocator. */
30683082
mm_heap->custom_heap._malloc = __zend_malloc;
@@ -3076,6 +3090,7 @@ static void alloc_globals_ctor(zend_alloc_globals *alloc_globals)
30763090
mm_heap->tracked_allocs = malloc(sizeof(HashTable));
30773091
zend_hash_init(mm_heap->tracked_allocs, 1024, NULL, NULL, 1);
30783092
}
3093+
mprotect(mm_heap, ZEND_MM_PAGE_SIZE, PROT_READ);
30793094
return;
30803095
}
30813096
#endif
@@ -3141,14 +3156,18 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
31413156
#if ZEND_MM_CUSTOM
31423157
zend_mm_heap *_heap = (zend_mm_heap*)heap;
31433158

3159+
mprotect(_heap, ZEND_MM_PAGE_SIZE, PROT_WRITE);
31443160
if (!_malloc && !_free && !_realloc) {
31453161
_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE;
31463162
} else {
31473163
_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_STD;
3164+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_WRITE);
31483165
_heap->custom_heap._malloc = _malloc;
31493166
_heap->custom_heap._free = _free;
31503167
_heap->custom_heap._realloc = _realloc;
3168+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_READ);
31513169
}
3170+
mprotect(_heap, ZEND_MM_PAGE_SIZE, PROT_READ);
31523171
#endif
31533172
}
31543173

0 commit comments

Comments
 (0)