Skip to content

Commit 120967f

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 120967f

File tree

1 file changed

+19
-6
lines changed

1 file changed

+19
-6
lines changed

Zend/zend_alloc.c

Lines changed: 19 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

@@ -3141,6 +3152,7 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
31413152
#if ZEND_MM_CUSTOM
31423153
zend_mm_heap *_heap = (zend_mm_heap*)heap;
31433154

3155+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_WRITE);
31443156
if (!_malloc && !_free && !_realloc) {
31453157
_heap->use_custom_heap = ZEND_MM_CUSTOM_HEAP_NONE;
31463158
} else {
@@ -3149,6 +3161,7 @@ ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
31493161
_heap->custom_heap._free = _free;
31503162
_heap->custom_heap._realloc = _realloc;
31513163
}
3164+
mprotect(heap, ZEND_MM_PAGE_SIZE, PROT_READ);
31523165
#endif
31533166
}
31543167

0 commit comments

Comments
 (0)