libmalloc源码分析之初始化

摘要

为了加深对OS X系统在应用层堆内存分配的了解,对libmalloc进行了阅读与理解。

  • 加强对堆上内存分布的理解
  • 遇到内存泄露问题需要处理时,对堆分配策略的了解,可以提高分析的速度与精确度
  • 遇到堆内存漏洞利用时,可以更加清楚的理解EXP的原理,做出更精准的分析

阅读本文之前可以先稍微了解一下OS X在堆上的内存处理的大致情况,点这里。了解大致情况之后,才能更好的理解源码的设计。

从malloc开始

初始化

所有的逻辑都是在应用层调用malloc函数时开始的,这里是malloc函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

void *
malloc(size_t size) {

void *retval;
retval = malloc_zone_malloc(inline_malloc_default_zone(), size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}

/*......*/

static inline malloc_zone_t *
inline_malloc_default_zone(void) {

if (!_malloc_is_initialized) _malloc_initialize();
// _malloc_printf(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone);
return malloc_zones[0];
}

很明显可以看出,libmalloc是在第一次调用malloc函数的的时候,通过inline_malloc_default_zone来进行堆上数据的初始化。而inline_malloc_default_zone函数最终会调用_malloc_initialize来完成最终的内存空间初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void
_malloc_initialize(void) {
MALLOC_LOCK();
if (!_malloc_is_initialized) {
unsigned n;
malloc_zone_t *zone;
_malloc_is_initialized = TRUE;
set_flags_from_environment(); // will only set flags up to two times
n = malloc_num_zones;

#if CONFIG_NANOZONE //可以通过宏控制是否使用nano_zone.

//创建一个会变化的zone
malloc_zone_t *helper_zone = create_scalable_zone(0, malloc_debug_flags);
//创建一个nano_zone,使用helper_zone作为nano_zone的
zone = create_nano_zone(0, helper_zone, malloc_debug_flags);

/*...*/
}
MALLOC_UNLOCK();
}

malloc_zone_t、szone_t、nanozone_t

这里需要先了解一下在堆空间初始化中,非常重要的两个数据结构。malloc_zone_tszone_t

malloc_zone_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct _malloc_zone_t {
/* Only zone implementors should depend on the layout of this structure;
Regular callers should use the access functions below */

void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */
void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */
size_t (*size)(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */
void *(*malloc)(struct _malloc_zone_t *zone, size_t size);
void *(*calloc)(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
void *(*valloc)(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */
void (*free)(struct _malloc_zone_t *zone, void *ptr);
void *(*realloc)(struct _malloc_zone_t *zone, void *ptr, size_t size);
void (*destroy)(struct _malloc_zone_t *zone); /* zone is destroyed and all memory reclaimed */
const char *zone_name;

/* Optional batch callbacks; these may be NULL */
unsigned (*batch_malloc)(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested) */
void (*batch_free)(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process */

struct malloc_introspection_t *introspect;
unsigned version;

/* aligned memory allocation. The callback may be NULL. Present in version >= 5. */
void *(*memalign)(struct _malloc_zone_t *zone, size_t alignment, size_t size);

/* free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.*/
void (*free_definite_size)(struct _malloc_zone_t *zone, void *ptr, size_t size);

/* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */
size_t (*pressure_relief)(struct _malloc_zone_t *zone, size_t goal);
} malloc_zone_t;

malloc_zone_t非常简单,就是一堆函数指针,用来存储一堆相关的处理函数的具体实现的地址,例如mallocfreerealloc等函数的具体实现。

szone_t、nanozone_t

szone_t其实与malloc_zone_t所描述的是同一块zone,但是szone_t的结构中存有大量的libmalloc内部逻辑会使用到的数据结构。他的结构体重第一个组成部分就是malloc_zone_t。

可以理解为,szone_t结构用来描述了这一块堆上初始化出来的zone的所有内部属性和状态,而malloc_zone_t是对外提供的用来处理该zone内部数据的所有接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
typedef struct szone_s {				// vm_allocate()'d, so page-aligned to begin with.
malloc_zone_t basic_zone; // first page will be given read-only protection
uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];

unsigned long cpu_id_key; // unused
// remainder of structure is R/W (contains no function pointers)
unsigned debug_flags;
void *log_address;

/* Regions for tiny objects */
_malloc_lock_s tiny_regions_lock CACHE_ALIGN;
size_t num_tiny_regions;
size_t num_tiny_regions_dealloc;
region_hash_generation_t *tiny_region_generation;
region_hash_generation_t trg[2];

int num_tiny_magazines;
unsigned num_tiny_magazines_mask;
int num_tiny_magazines_mask_shift;
magazine_t *tiny_magazines; // array of per-processor magazines

uintptr_t last_tiny_advise;

/* Regions for small objects */
_malloc_lock_s small_regions_lock CACHE_ALIGN;
size_t num_small_regions;
size_t num_small_regions_dealloc;
region_hash_generation_t *small_region_generation;
region_hash_generation_t srg[2];

unsigned num_small_slots; // determined by physmem size

int num_small_magazines;
unsigned num_small_magazines_mask;
int num_small_magazines_mask_shift;
magazine_t *small_magazines; // array of per-processor magazines

uintptr_t last_small_advise;

/* large objects: all the rest */
_malloc_lock_s large_szone_lock CACHE_ALIGN; // One customer at a time for large
unsigned num_large_objects_in_use;
unsigned num_large_entries;
large_entry_t *large_entries; // hashed by location; null entries don't count
size_t num_bytes_in_large_objects;

#if LARGE_CACHE
int large_entry_cache_oldest;
int large_entry_cache_newest;
large_entry_t large_entry_cache[LARGE_ENTRY_CACHE_SIZE]; // "death row" for large malloc/free
boolean_t large_legacy_reset_mprotect;
size_t large_entry_cache_reserve_bytes;
size_t large_entry_cache_reserve_limit;
size_t large_entry_cache_bytes; // total size of death row, bytes
#endif

/* flag and limits pertaining to altered malloc behavior for systems with
large amounts of physical memory */

unsigned is_largemem;
unsigned large_threshold;
unsigned vm_copy_threshold;

/* security cookie */
uintptr_t cookie;

/* Initial region list */
region_t initial_tiny_regions[INITIAL_NUM_REGIONS];
region_t initial_small_regions[INITIAL_NUM_REGIONS];

/* The purgeable zone constructed by create_purgeable_zone() would like to hand off tiny and small
* allocations to the default scalable zone. Record the latter as the "helper" zone here. */

struct szone_s *helper_zone;

boolean_t flotsam_enabled;
} szone_t;

nano_zone_tmalloc_zone_t的关系相同,因为nano_zone_t的结构相对简单,可以先从nano_zone_t入手,理解其使用方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

typedef struct nanozone_s { // vm_allocate()'d, so page-aligned to begin with.
malloc_zone_t basic_zone; // first page will be given read-only protection
uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];

// remainder of structure is R/W (contains no function pointers)
// page-aligned
struct nano_meta_s meta_data[NANO_MAG_SIZE][NANO_SLOT_SIZE]; // max: NANO_MAG_SIZE cores x NANO_SLOT_SIZE slots for nano blocks {16 .. 256}
_malloc_lock_s band_resupply_lock[NANO_MAG_SIZE];
uintptr_t band_max_mapped_baseaddr[NANO_MAG_SIZE];
size_t core_mapped_size[NANO_MAG_SIZE];

unsigned debug_flags;
unsigned our_signature;
unsigned phys_ncpus;
unsigned logical_ncpus;
unsigned hyper_shift;

/* security cookie */
uintptr_t cookie;

/*
* The nano zone constructed by create_nano_zone() would like to hand off tiny, small, and large
* allocations to the default scalable zone. Record the latter as the "helper" zone here.
*/

malloc_zone_t *helper_zone;
} nanozone_t;

在实际的使用中,代码是这样写的。详细的nano_zone创建,会在下文出现。

1
2
3
4
5
6
7
8
#define SZONE_PAGED_SIZE	((sizeof(nanozone_t) + vm_page_size - 1) & ~ (vm_page_size - 1))
malloc_zone_t *
foo(){
nanozone_t *nanozone;
nanozone = allocate_pages(NULL, SZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC);
/*对nano_zone进行处理,设置内部数据*/
return (malloc_zone_t *)nanozone; //强制转换指针类型,外部结构只能通过malloc_zone_t的殊绝结构来调用malloc_zone_t中的一些函数,来接进行数据处理。
}

create_scalable_zone

该函数实现了helper_zone的创建以及相关数据的初始化。

comm pages

1
2
3
4
5
// * The shared kernel/user "comm page(s)":
//*
//* The last several pages of every address space are reserved for the kernel/user
//* "comm area". During system initialization, the kernel populates the comm pages with
//* code customized for the particular processor and platform

comm pages是系统初始化时创建的一块共享的内存区域,保存了一些系统的数据。在libmalloc中有不少地方都用到了comm pages来获取系统的属性。

源码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
malloc_zone_t *
create_scalable_zone(size_t initial_size, unsigned debug_flags)
{
szone_t *szone;
uint64_t hw_memsize = 0;

//判断comm page的版本是否符合要求
#if defined(__i386__) || defined(__x86_64__)
if (_COMM_PAGE_VERSION_REQD > (*((uint16_t *)_COMM_PAGE_VERSION))) {
malloc_printf("*** ERROR - comm page version mismatch.\n");
exit(-1);
}
#endif

/* get memory for the zone. */
//从内核中获取了内存空间,对应szone的大小,因为一些调试参数的不同,内存的布局可能会不同。
// #define SZONE_PAGED_SIZE round_page_quanta((sizeof(szone_t)))
// SZONE_PAGED_SIZE 是szone_t的大小根据page大小对齐之后的大小
szone = allocate_pages(NULL, SZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC);
if (!szone)
return NULL;

/* set up the szone structure */
// 调试信息的设置以及调试日志的设置
#if 0
#warning CHECK_REGIONS enabled
debug_flags |= CHECK_REGIONS;
#endif
#if 0
#warning LOG enabled
szone->log_address = ~0;
#endif
//zone中与tiny相关的数据初始化
/*
typedef struct region_hash_generation {
size_t num_regions_allocated;
size_t num_regions_allocated_shift; // log2(num_regions_allocated)
region_t *hashed_regions; // hashed by location
struct region_hash_generation *nextgen;
} region_hash_generation_t;
*/


/*
...
_malloc_lock_s tiny_regions_lock CACHE_ALIGN;
size_t num_tiny_regions;
size_t num_tiny_regions_dealloc;
region_hash_generation_t *tiny_region_generation;
region_hash_generation_t trg[2];

int num_tiny_magazines;
unsigned num_tiny_magazines_mask;
int num_tiny_magazines_mask_shift;
magazine_t *tiny_magazines; // array of per-processor magazines

uintptr_t last_tiny_advise;
...
*/

szone->trg[0].nextgen = &(szone->trg[1]);
szone->trg[1].nextgen = &(szone->trg[0]);
szone->tiny_region_generation = &(szone->trg[0]);

szone->tiny_region_generation->hashed_regions = szone->initial_tiny_regions;
szone->tiny_region_generation->num_regions_allocated = INITIAL_NUM_REGIONS;
szone->tiny_region_generation->num_regions_allocated_shift = INITIAL_NUM_REGIONS_SHIFT;

/*
初始化之后数相关数据结构如下
----------------------------------
|num_tiny_regions |
----------------------------------
|num_tiny_regions_dealloc |
----------------------------------
|tiny_region_generation = &trg[0]|
---------------------------------- ---------------------------------------------------------
|trg[0] |------|num_regions_allocated = INITIAL_NUM_REGIONS |<-----
---------------------------------- --------------------------------------------------------- |
|trg[1] |--- |num_regions_allocated_shift = INITIAL_NUM_REGIONS_SHIFT| |
---------------------------------| | --------------------------------------------------------- |
|num_tiny_magazines | | |hashed_regionsr = initial_tiny_regions |=====|=====
---------------------------------- | --------------------------------------------------------- | ||
|num_tiny_magazines_mask | | |nextgen = &trg[1] |--- | ||
---------------------------------- | --------------------------------------------------------- | | ||
|num_tiny_magazines_mask_shift | | --------------------------------------------------------- | | ||
---------------------------------- ----|num_regions_allocated = 0 |<-- | ||
|tiny_magazines | --------------------------------------------------------- | ||
---------------------------------- |num_regions_allocated_shift = 0 | | ||
|last_tiny_advise | --------------------------------------------------------- | ||
---------------------------------- |hashed_regions = NULL | | ||
|initial_tiny_regions |<=== --------------------------------------------------------- | ||
---------------------------------- || |nextgen = &trg[0] |------ ||
|| --------------------------------------------------------- ||
=======================================================================

*/

//zone中与small相关的数据初始化
szone->srg[0].nextgen = &(szone->srg[1]);
szone->srg[1].nextgen = &(szone->srg[0]);
szone->small_region_generation = &(szone->srg[0]);

szone->small_region_generation->hashed_regions = szone->initial_small_regions;
szone->small_region_generation->num_regions_allocated = INITIAL_NUM_REGIONS;
szone->small_region_generation->num_regions_allocated_shift = INITIAL_NUM_REGIONS_SHIFT;

//初始化之后完全与tiny相同,只是变量不同。

/*
* Initialize variables that size the free list for SMALL allocations based
* upon the amount of memory in the system. Switch to a larger number of
* free list entries at 1GB.
*/

// 初始化一些变量,根据系统中的内存的用量来计算SMALL分配列表的大小时将会用到这些变量。

// * The shared kernel/user "comm page(s)":
//*
//* The last several pages of every address space are reserved for the kernel/user
//* "comm area". During system initialization, the kernel populates the comm pages with
//* code customized for the particular processor and platform.
//*1073741824
//* Because Mach VM cannot map the last page of an address space, we don't use it
//

//----------根据是否需要使用large,做出不同的初始化---------------start-------------
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__arm64__)
if ((hw_memsize = *(uint64_t *)(uintptr_t)_COMM_PAGE_MEMORY_SIZE) >= (1ULL << 30))
#else
size_t uint64_t_size = sizeof(hw_memsize);

if (0 == sysctlbyname("hw.memsize", &hw_memsize, &uint64_t_size, 0, 0) &&
hw_memsize >= (1ULL << 30))
#endif
{
szone->is_largemem = 1;
szone->num_small_slots = NUM_SMALL_SLOTS_LARGEMEM;
szone->large_threshold = LARGE_THRESHOLD_LARGEMEM;
szone->vm_copy_threshold = VM_COPY_THRESHOLD_LARGEMEM;
} else {
szone->is_largemem = 0;
szone->num_small_slots = NUM_SMALL_SLOTS;
szone->large_threshold = LARGE_THRESHOLD;
szone->vm_copy_threshold = VM_COPY_THRESHOLD;
}
#if LARGE_CACHE
szone->large_entry_cache_reserve_limit =
hw_memsize >> 10; // madvise(..., MADV_REUSABLE) death-row arrivals above this threshold [~0.1%]

/* <rdar://problem/6610904> Reset protection when returning a previous large allocation? */
int32_t libSystemVersion = NSVersionOfLinkTimeLibrary("System");
if ((-1 != libSystemVersion) && ((libSystemVersion >> 16) < 112) /* CFSystemVersionSnowLeopard */)
szone->large_legacy_reset_mprotect = TRUE;
else
szone->large_legacy_reset_mprotect = FALSE;
#endif
//----------根据是否需要使用large,做出不同的初始化---------------end----------------

// Prepare ASLR
// ---------处理ASLR相关的内容---------------------------------start---------------
#if __i386__ || __x86_64__ || __arm64__ || TARGET_OS_EMBEDDED
#if __i386__
uintptr_t stackbase = 0x8fe00000;
int entropic_bits = 3;
#elif __x86_64__
uintptr_t stackbase = USRSTACK64;
int entropic_bits = 16;
#elif __arm64__
uintptr_t stackbase = USRSTACK64;
int entropic_bits = 7;
#else
uintptr_t stackbase = USRSTACK;
int entropic_bits = 3;
#endif

// assert(((1 << entropic_bits) - 1) << SMALL_BLOCKS_ALIGN < (stackbase - MAXSSIZ - ENTROPIC_KABILLION));

if (0 != _dyld_get_image_slide((const struct mach_header*)_NSGetMachExecuteHeader())) {
if (0 == entropic_address) {
uintptr_t t = stackbase - MAXSSIZ - ((uintptr_t) (malloc_entropy[1] & ((1 << entropic_bits) - 1)) << SMALL_BLOCKS_ALIGN);
(void)__sync_bool_compare_and_swap(&entropic_limit, 0, t); // Just one initialization please
(void)__sync_bool_compare_and_swap(&entropic_address, 0, t - ENTROPIC_KABILLION); // Just one initialization please
}
debug_flags &= ~DISABLE_ASLR;
} else {
// zero slide when ASLR has been disabled by boot-arg. Eliminate cloaking.
malloc_entropy[0] = 0;
malloc_entropy[1] = 0;
debug_flags |= DISABLE_ASLR;
}

#else
malloc_entropy[0] = 0;
malloc_entropy[1] = 0;
debug_flags |= DISABLE_ASLR;
#endif
// ---------处理ASLR相关的内容---------------------------------end---------------


// Initialize the security token.
// ---------初始化外部调用函数---------------------------------start-------------
szone->cookie = (uintptr_t)malloc_entropy[0];

szone->basic_zone.version = 8;
szone->basic_zone.size = (void *)szone_size;
szone->basic_zone.malloc = (void *)szone_malloc;
szone->basic_zone.calloc = (void *)szone_calloc;
szone->basic_zone.valloc = (void *)szone_valloc;
szone->basic_zone.free = (void *)szone_free;
szone->basic_zone.realloc = (void *)szone_realloc;
szone->basic_zone.destroy = (void *)szone_destroy;
szone->basic_zone.batch_malloc = (void *)szone_batch_malloc;
szone->basic_zone.batch_free = (void *)szone_batch_free;
szone->basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect;
szone->basic_zone.memalign = (void *)szone_memalign;
szone->basic_zone.free_definite_size = (void *)szone_free_definite_size;
szone->basic_zone.pressure_relief = (void *)szone_pressure_relief;

szone->basic_zone.reserved1 = 0; /* Set to zero once and for all as required by CFAllocator. */
szone->basic_zone.reserved2 = 0; /* Set to zero once and for all as required by CFAllocator. */
mprotect(szone, sizeof(szone->basic_zone), PROT_READ); /* Prevent overwriting the function pointers in basic_zone. */

szone->debug_flags = debug_flags;
_malloc_lock_init(&szone->large_szone_lock);
// ---------初始化外部调用函数---------------------------------end-------------

#if defined(__ppc__) || defined(__ppc64__)
/*
* In the interest of compatibility for PPC applications executing via Rosetta,
* arrange to zero-fill allocations as occurred by side effect in Leopard and earlier.
*/

zeroify_scalable_zone((malloc_zone_t *)szone);
#endif

szone->cpu_id_key = -1UL; // Unused.

// Query the number of configured processors.
// Uniprocessor case gets just one tiny and one small magazine (whose index is zero). This gives
// the same behavior as the original scalable malloc. MP gets per-CPU magazines
// that scale (way) better.
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__arm64__)
int nproc = *(uint8_t *)(uintptr_t)_COMM_PAGE_NCPUS;
#else
int nproc = sysconf(_SC_NPROCESSORS_CONF);
#endif

// ------为每个一个cpu都申请一个tiny的内存空间,并初始化相关数据--------start
//根据cpu的根数,计算获得需要多少个tiny的内存空间
szone->num_tiny_magazines = (nproc > 1) ? MIN(nproc, TINY_MAX_MAGAZINES) : 1;

// FIXME vm_allocate() based on number of configured CPUs
//申请需要的内存空间
magazine_t *tiny_magazines = allocate_pages(NULL, TINY_MAGAZINE_PAGED_SIZE, 0,
SCALABLE_MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC);
if (NULL == tiny_magazines)
return NULL;
//将申请的内存空间赋值到szone中相关的字段上
szone->tiny_magazines = &(tiny_magazines[1]); // szone->tiny_magazines[-1] is the Depot

// The magazines are indexed in [0 .. (num_tiny_magazines - 1)]
// Find the smallest power of 2 that exceeds (num_tiny_magazines - 1)
// 通过计算获取num_tiny_magazines_mask_shift的值
// num_tiny_magazines_mask_shift = log2(szone->num_tiny_magazines-1)
szone->num_tiny_magazines_mask_shift = 0;
int i = 1;
while( i <= (szone->num_tiny_magazines - 1) ) {
szone->num_tiny_magazines_mask_shift++;
i <<= 1;
}

// Now if i <= TINY_MAX_MAGAZINES we'll never access tiny_magazines[] out of bounds.
if (i > TINY_MAX_MAGAZINES) {
malloc_printf("*** FATAL ERROR - magazine mask exceeds allocated magazines.\n");
exit(-1);
}

// Reduce i by 1 to obtain a mask covering [0 .. (num_tiny_magazines - 1)]
szone->num_tiny_magazines_mask = i - 1; // A mask used for hashing to a magazine index (and a safety aid)
szone->last_tiny_advise = 0;

// Init the tiny_magazine locks
_malloc_lock_init(&szone->tiny_regions_lock);
_malloc_lock_init(&szone->tiny_magazines[DEPOT_MAGAZINE_INDEX].magazine_lock);
for (i = 0; i < szone->num_tiny_magazines; ++i) {
_malloc_lock_init(&szone->tiny_magazines[i].magazine_lock);
}
// ------为每个一个cpu都申请一个tiny的内存空间,并初始化相关数据--------end

// ------为每个一个cpu都申请一个small的内存空间,并初始化相关数据--------start
szone->num_small_magazines = (nproc > 1) ? MIN(nproc, SMALL_MAX_MAGAZINES) : 1;

// FIXME vm_allocate() based on number of configured CPUs
magazine_t *small_magazines = allocate_pages(NULL, SMALL_MAGAZINE_PAGED_SIZE, 0,
SCALABLE_MALLOC_ADD_GUARD_PAGES, VM_MEMORY_MALLOC);
if (NULL == small_magazines)
return NULL;

szone->small_magazines = &(small_magazines[1]); // szone->small_magazines[-1] is the Depot

// The magazines are indexed in [0 .. (num_small_magazines - 1)]
// Find the smallest power of 2 that exceeds (num_small_magazines - 1)
szone->num_small_magazines_mask_shift = 0;
while( i <= (szone->num_small_magazines - 1) ) {
szone->num_small_magazines_mask_shift++;
i <<= 1;
}

// Now if i <= SMALL_MAX_MAGAZINES we'll never access small_magazines[] out of bounds.
if (i > SMALL_MAX_MAGAZINES) {
malloc_printf("*** FATAL ERROR - magazine mask exceeds allocated magazines.\n");
exit(-1);
}

// Reduce i by 1 to obtain a mask covering [0 .. (num_small_magazines - 1)]
szone->num_small_magazines_mask = i - 1; // A mask used for hashing to a magazine index (and a safety aid)
szone->last_small_advise = 0;

// Init the small_magazine locks
_malloc_lock_init(&szone->small_regions_lock);
_malloc_lock_init(&szone->small_magazines[DEPOT_MAGAZINE_INDEX].magazine_lock);
for (i = 0; i < szone->num_small_magazines; ++i) {
_malloc_lock_init(&szone->small_magazines[i].magazine_lock);
}

CHECK(szone, __PRETTY_FUNCTION__);
// ------为每个一个cpu都申请一个small的内存空间,并初始化相关数据--------end
//逻辑与tiny相同,只是变量不同
return (malloc_zone_t *)szone;
}

create_nano_zone

nano_zone的创建就相对简单很多,这里不多做分析,直接看注释的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
__attribute__((visibility("hidden")))
malloc_zone_t *
create_nano_zone(size_t initial_size, malloc_zone_t *helper_zone, unsigned debug_flags)
{
nanozone_t *nanozone;
int i, j;

if (!_malloc_engaged_nano) return NULL;

//检测comm page的版本
#if defined(__x86_64__)
if (_COMM_PAGE_VERSION_REQD > (*((uint16_t *)_COMM_PAGE_VERSION))) {
malloc_printf("*** FATAL ERROR - comm page version mismatch.\n");
exit(-1);
}
#endif

/* get memory for the zone. */
//申请nanozone数据结构大小的内存
nanozone = allocate_pages(NULL, SZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC);
if (!nanozone)
return NULL;

/* set up the basic_zone portion of the nanozone structure */
//设置外部调用函数
nanozone->basic_zone.version = 8;
nanozone->basic_zone.size = (void *)nano_size;
nanozone->basic_zone.malloc = (debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) ? (void *)nano_malloc_scribble : (void *)nano_malloc;
nanozone->basic_zone.calloc = (void *)nano_calloc;
nanozone->basic_zone.valloc = (void *)nano_valloc;
nanozone->basic_zone.free = (debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) ? (void *)nano_free_scribble : (void *)nano_free;
nanozone->basic_zone.realloc = (void *)nano_realloc;
nanozone->basic_zone.destroy = (void *)nano_destroy;
nanozone->basic_zone.batch_malloc = (void *)nano_batch_malloc;
nanozone->basic_zone.batch_free = (void *)nano_batch_free;
nanozone->basic_zone.introspect = (struct malloc_introspection_t *)&nano_introspect;
nanozone->basic_zone.memalign = (void *)nano_memalign;
nanozone->basic_zone.free_definite_size = (debug_flags & SCALABLE_MALLOC_DO_SCRIBBLE) ?
(void *)nano_free_definite_size_scribble : (void *)nano_free_definite_size;

nanozone->basic_zone.pressure_relief = (void *)nano_pressure_relief;

nanozone->basic_zone.reserved1 = 0; /* Set to zero once and for all as required by CFAllocator. */
nanozone->basic_zone.reserved2 = 0; /* Set to zero once and for all as required by CFAllocator. */

mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ); /* Prevent overwriting the function pointers in basic_zone. */

/* set up the remainder of the nanozone structure */
nanozone->debug_flags = debug_flags;
nanozone->our_signature = NANOZONE_SIGNATURE;

/* Query the number of configured processors. */
#if defined(__x86_64__)
nanozone->phys_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_PHYSICAL_CPUS;
nanozone->logical_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_LOGICAL_CPUS;
#else
#error Unknown architecture
#endif

//根据cpu的不同,对meta_data进行初始化
if (nanozone->phys_ncpus > sizeof(nanozone->core_mapped_size)/sizeof(nanozone->core_mapped_size[0])) {
_malloc_printf(ASL_LEVEL_NOTICE, "nano zone abandoned because NCPUS mismatch.\n");
return NULL;
}

if (0 != (nanozone->logical_ncpus % nanozone->phys_ncpus)) {
malloc_printf("*** FATAL ERROR - logical_ncpus % phys_ncpus != 0.\n");
exit(-1);
}

switch (nanozone->logical_ncpus/nanozone->phys_ncpus) {
case 1:
nanozone->hyper_shift = 0;
break;
case 2:
nanozone->hyper_shift = 1;
break;
case 4:
nanozone->hyper_shift = 2;
break;
default:
malloc_printf("*** FATAL ERROR - logical_ncpus / phys_ncpus not 1, 2, or 4.\n");
exit(-1);
}

/* Initialize slot queue heads and resupply locks. */
//#define OS_ATOMIC_QUEUE_INIT { NULL, 0 }
OSQueueHead q0 = OS_ATOMIC_QUEUE_INIT; //{NULL,0}
for (i = 0; i < nanozone->phys_ncpus; ++i) {
_malloc_lock_init(&nanozone->band_resupply_lock[i]);

for (j = 0; j < NANO_SLOT_SIZE; ++j) {
nanozone->meta_data[i][j].slot_LIFO = q0;
}
}

//对地址随机化做处理。
/* Initialize the security token. */
if (0 == _dyld_get_image_slide((const struct mach_header*)_NSGetMachExecuteHeader())) {
// zero slide when ASLR has been disabled by boot-arg. Eliminate cloaking.
malloc_entropy[0] = 0;
malloc_entropy[1] = 0;
}
nanozone->cookie = (uintptr_t)malloc_entropy[0] & 0x0000ffffffff0000ULL; // scramble central 32bits with this cookie

/* Nano zone does not support SCALABLE_MALLOC_ADD_GUARD_PAGES. */
if (nanozone->debug_flags & SCALABLE_MALLOC_ADD_GUARD_PAGES) {
_malloc_printf(ASL_LEVEL_INFO, "nano zone does not support guard pages\n");
nanozone->debug_flags &= ~SCALABLE_MALLOC_ADD_GUARD_PAGES;
}

nanozone->helper_zone = helper_zone;

return (malloc_zone_t *)nanozone;
}

小结

至此,nano_zonescalable_zone的数据结构组成,以及其数据初始化之后的分布,已经大致的分析完成了,下一篇文章,将通过对nano_malloc以及nano_free的简单分析,来理解nano_zone的使用情况。

reference

1.iOS内存管理和malloc源码解读

https://yq.aliyun.com/articles/3065

2.OS X heap exploitation techniques

http://phrack.org/issues/63/5.html#article

文章目录
  1. 1. 摘要
  2. 2. 从malloc开始
    1. 2.1. 初始化
      1. 2.1.1. malloc_zone_t、szone_t、nanozone_t
        1. 2.1.1.1. malloc_zone_t
        2. 2.1.1.2. szone_t、nanozone_t
      2. 2.1.2. create_scalable_zone
        1. 2.1.2.1. comm pages
        2. 2.1.2.2. 源码注释
      3. 2.1.3. create_nano_zone
  3. 3. 小结
  4. 4. reference
,