Skip to content

Commit b2adee9

Browse files
committed
Review
* Swap pointer in shadow, as this extends the minimal expoitable overflow by at least 2 bytes * Comment / clarify * Use fallback seed if seeding with CSPRNG fails * Export all RNG algos
1 parent e342f5f commit b2adee9

File tree

1 file changed

+73
-23
lines changed

1 file changed

+73
-23
lines changed

Zend/zend_alloc.c

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,44 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */
197197

198198
#define ZEND_MM_BINS 30
199199

200+
#if defined(_MSC_VER)
201+
# if UINTPTR_MAX == UINT64_MAX
202+
# define BSWAPPTR(u) _byteswap_uint64(u)
203+
# else
204+
# define BSWAPPTR(u) _byteswap_ulong(u)
205+
# endif
206+
#else
207+
# if UINTPTR_MAX == UINT64_MAX
208+
# if __has_builtin(__builtin_bswap64)
209+
# define BSWAPPTR(u) __builtin_bswap64(u)
210+
# else
211+
zend_always_inline uintptr_t BSWAPPTR(uintptr_t u)
212+
{
213+
return (((u & 0xff00000000000000ULL) >> 56)
214+
| ((u & 0x00ff000000000000ULL) >> 40)
215+
| ((u & 0x0000ff0000000000ULL) >> 24)
216+
| ((u & 0x000000ff00000000ULL) >> 8)
217+
| ((u & 0x00000000ff000000ULL) << 8)
218+
| ((u & 0x0000000000ff0000ULL) << 24)
219+
| ((u & 0x000000000000ff00ULL) << 40)
220+
| ((u & 0x00000000000000ffULL) << 56));
221+
}
222+
# endif /* __has_builtin(__builtin_bswap64) */
223+
# else /* UINTPTR_MAX == UINT64_MAX */
224+
# if __has_builtin(__builtin_bswap32)
225+
# define BSWAPPTR(u) __builtin_bswap32(u)
226+
# else
227+
zend_always_inline uintptr_t BSWAPPTR(uintptr_t u)
228+
{
229+
return (((u & 0xff000000) >> 24)
230+
| ((u & 0x00ff0000) >> 8)
231+
| ((u & 0x0000ff00) << 8)
232+
| ((u & 0x000000ff) << 24));
233+
}
234+
# endif /* __has_builtin(__builtin_bswap32) */
235+
# endif /* UINTPTR_MAX == UINT64_MAX */
236+
#endif /* defined(_MSC_VER) */
237+
200238
typedef struct _zend_mm_page zend_mm_page;
201239
typedef struct _zend_mm_bin zend_mm_bin;
202240
typedef struct _zend_mm_free_slot zend_mm_free_slot;
@@ -248,7 +286,7 @@ struct _zend_mm_heap {
248286
size_t size; /* current memory usage */
249287
size_t peak; /* peak memory usage */
250288
#endif
251-
uintptr_t key; /* free slot shadow ptr key */
289+
uintptr_t shadow_key; /* free slot shadow ptr xor key */
252290
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
253291
#if ZEND_MM_STAT || ZEND_MM_LIMIT
254292
size_t real_size; /* current size of allocated pages */
@@ -349,14 +387,6 @@ static const uint32_t bin_pages[] = {
349387
ZEND_MM_BINS_INFO(_BIN_DATA_PAGES_C, x, y)
350388
};
351389

352-
#define _BIN_SHADOW_OFFSET(num, size, elements, pages, x, y) \
353-
(_BIN_DATA_SIZE(num, size, elements, pages, x, y) - sizeof(zend_mm_free_slot*))
354-
#define _BIN_SHADOW_OFFSET_C(num, size, elements, pages, x, y) \
355-
_BIN_SHADOW_OFFSET(num, size, elements, pages, x, y),
356-
static const uint32_t bin_shadow_offset[] = {
357-
ZEND_MM_BINS_INFO(_BIN_SHADOW_OFFSET_C, x, y)
358-
};
359-
360390
#if ZEND_DEBUG
361391
ZEND_COLD void zend_debug_alloc_output(char *format, ...)
362392
{
@@ -1272,40 +1302,58 @@ static zend_always_inline int zend_mm_small_size_to_bin(size_t size)
12721302

12731303
#define ZEND_MM_SMALL_SIZE_TO_BIN(size) zend_mm_small_size_to_bin(size)
12741304

1305+
/* We keep track of free slots by organizing them in a linked list, with the
1306+
* first word of every free slot being a pointer to the next one.
1307+
*
1308+
* In order to frustrate corruptions, we check the consistency of these pointers
1309+
* before dereference by comparing them with a shadow.
1310+
*
1311+
* The shadow is a copy of the pointer, stored at the end of the slot. It is
1312+
* XOR'ed with a random key, and converted to big-endian so that smaller
1313+
* corruptions affect the most significant bytes, which has a high chance of
1314+
* resulting in an invalid address instead of pointing to an adjacent slot.
1315+
*/
1316+
1317+
#define ZEND_MM_FREE_SLOT_PTR_SHADOW(free_slot, bin_num) \
1318+
*((zend_mm_free_slot**)((char*)(free_slot) + bin_data_size[(bin_num)] - sizeof(zend_mm_free_slot*)))
1319+
12751320
static zend_always_inline zend_mm_free_slot* zend_mm_encode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
12761321
{
1277-
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->key);
1322+
#if WORDS_BIGENDIAN
1323+
return (zend_mm_free_slot*)(((uintptr_t)slot) ^ heap->shadow_key);
1324+
#else
1325+
return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot) ^ heap->shadow_key);
1326+
#endif
12781327
}
12791328

12801329
static zend_always_inline zend_mm_free_slot* zend_mm_decode_free_slot(zend_mm_heap *heap, zend_mm_free_slot *slot)
12811330
{
1282-
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->key);
1331+
#if WORDS_BIGENDIAN
1332+
return (zend_mm_free_slot*)((uintptr_t)slot ^ heap->shadow_key));
1333+
#else
1334+
return (zend_mm_free_slot*)(BSWAPPTR((uintptr_t)slot ^ heap->shadow_key));
1335+
#endif
12831336
}
12841337

12851338
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)
12861339
{
12871340
slot->next_free_slot = next;
1288-
*(zend_mm_free_slot**)((char*)slot + bin_shadow_offset[bin_num]) = zend_mm_encode_free_slot(heap, next);
1341+
ZEND_MM_FREE_SLOT_PTR_SHADOW(slot, bin_num) = zend_mm_encode_free_slot(heap, next);
12891342
}
12901343

12911344
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)
12921345
{
12931346
dest->next_free_slot = from->next_free_slot;
1294-
*(zend_mm_free_slot**)((char*)dest + bin_shadow_offset[bin_num]) = *(zend_mm_free_slot**)((char*)from + bin_shadow_offset[bin_num]);
1295-
}
1296-
1297-
static ZEND_COLD ZEND_NORETURN void zend_mm_free_slot_corrupted(void)
1298-
{
1299-
zend_mm_panic("zend_mm_heap corrupted");
1347+
ZEND_MM_FREE_SLOT_PTR_SHADOW(dest, bin_num) = ZEND_MM_FREE_SLOT_PTR_SHADOW(from, bin_num);
13001348
}
13011349

13021350
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)
13031351
{
13041352
zend_mm_free_slot *next = slot->next_free_slot;
1305-
zend_mm_free_slot *shadow = *(zend_mm_free_slot**)((char*)slot + bin_shadow_offset[bin_num]);
1353+
zend_mm_free_slot *shadow = ZEND_MM_FREE_SLOT_PTR_SHADOW(slot, bin_num);
13061354
if (EXPECTED(next != NULL)) {
13071355
if (UNEXPECTED(next != zend_mm_decode_free_slot(heap, shadow))) {
1308-
zend_mm_free_slot_corrupted();
1356+
zend_mm_panic("zend_mm_heap corrupted");
13091357
}
13101358
}
13111359
return (zend_mm_free_slot*)next;
@@ -1971,15 +2019,17 @@ static zend_result zend_mm_refresh_key(zend_mm_heap *heap)
19712019
{
19722020
php_random_result result = php_random_algo_xoshiro256starstar.generate(&heap->random_state);
19732021
ZEND_ASSERT(result.size == sizeof(uint64_t));
1974-
heap->key = (uintptr_t) result.result;
2022+
heap->shadow_key = (uintptr_t) result.result;
19752023
return SUCCESS;
19762024
}
19772025

19782026
static zend_result zend_mm_init_key(zend_mm_heap *heap)
19792027
{
19802028
uint64_t seed[4];
1981-
if (php_random_bytes(&seed, sizeof(seed), false) != SUCCESS) {
1982-
return FAILURE;
2029+
if (php_random_bytes_silent(&seed, sizeof(seed)) != SUCCESS) {
2030+
for (int i = 0; i < sizeof(seed)/sizeof(seed[0]); i++) {
2031+
seed[i] = php_random_generate_fallback_seed();
2032+
}
19832033
}
19842034

19852035
php_random_xoshiro256starstar_seed256(&heap->random_state,

0 commit comments

Comments
 (0)