Loading...
--- libmalloc/libmalloc-425.100.7/src/pgm_malloc.c
+++ libmalloc/libmalloc-521.120.7/src/pgm_malloc.c
@@ -113,8 +113,8 @@
 	uint64_t last_log_time;
 } pgm_zone_t;
 
-MALLOC_STATIC_ASSERT(__offsetof(pgm_zone_t, malloc_zone) == 0,
-		"pgm_zone_t instances must be usable as regular zones");
+ASSERT_WRAPPER_ZONE(pgm_zone_t);
+
 MALLOC_STATIC_ASSERT(__offsetof(pgm_zone_t, padding) < PAGE_MAX_SIZE,
 		"First page is mapped read-only");
 MALLOC_STATIC_ASSERT(__offsetof(pgm_zone_t, lock) >= PAGE_MAX_SIZE,
@@ -631,57 +631,6 @@
 	my_vm_deallocate((vm_address_t)zone, sizeof(pgm_zone_t));
 }
 
-static unsigned
-pgm_batch_malloc(pgm_zone_t *zone, size_t size, void **results, unsigned count)
-{
-	if (os_unlikely(count == 0)) {
-		return 0;
-	}
-	DELEGATE_UNSAMPLED(size, batch_malloc, size, results, count);
-
-	uint32_t sample_count = 1; // Sample at least one allocation.
-	for (uint32_t i = 1; i < count; i++) {
-		if (should_sample_counter(zone->sample_counter_range)) {
-			sample_count++;
-		}
-	}
-	// TODO(yln): Express the above with only one call to rand_uniform(). "n choose k"?
-
-	for (uint32_t i = 0; i < sample_count; i++) {
-		lock(zone);
-		void *ptr = (void *)allocate(zone, size, zone->min_alignment);
-		unlock(zone);
-		if (!ptr) {
-			sample_count = i;
-			break; // Zone full.
-		}
-		results[i] = ptr;
-	}
-
-	void **remaining_results = results + sample_count;
-	uint32_t remaining_count = count - sample_count;
-	remaining_count = DELEGATE(batch_malloc, size, remaining_results, remaining_count) ;
-
-	// TODO(yln): sampled allocations will always be in the beginning of the results
-	// array.  We could shuffle it: https://en.wikipedia.org/wiki/Fisher–Yates_shuffle
-	return sample_count + remaining_count;
-}
-
-static void
-pgm_batch_free(pgm_zone_t *zone, void **to_be_freed, unsigned count)
-{
-	for (uint32_t i = 0; i < count; i++) {
-		vm_address_t addr = (vm_address_t)to_be_freed[i];
-		if (os_unlikely(is_guarded(zone, addr))) {
-			lock(zone);
-			deallocate(zone, addr);
-			unlock(zone);
-			to_be_freed[i] = NULL;
-		}
-	}
-	return DELEGATE(batch_free, to_be_freed, count);
-}
-
 static void *
 pgm_memalign(pgm_zone_t *zone, size_t alignment, size_t size)
 {
@@ -706,6 +655,38 @@
 	return is_guarded(zone, (vm_address_t)ptr);
 }
 
+static void *
+pgm_malloc_with_options(pgm_zone_t *zone, size_t align, size_t size,
+		uint64_t options)
+{
+	if (os_unlikely(should_sample(zone, size))) {
+		size_t adj_alignment = MAX(align, zone->min_alignment);
+		lock(zone);
+		void *ptr = (void *)allocate(zone, size, adj_alignment);
+		unlock(zone);
+		if (ptr) {
+			if (options & MALLOC_NP_OPTION_CLEAR) {
+				bzero(ptr, size);
+			}
+			return ptr;
+		}
+		// If the allocaiton fails, fall back to the wrapped zone
+	}
+
+	if (zone->wrapped_zone->version >= 15) {
+		return DELEGATE(malloc_with_options, align, size, options);
+	} else if (align) {
+		void *ptr = DELEGATE(memalign, align, size);
+		if (ptr && (options & MALLOC_NP_OPTION_CLEAR)) {
+			bzero(ptr, size);
+		}
+		return ptr;
+	} else if (options & MALLOC_NP_OPTION_CLEAR) {
+		return DELEGATE(calloc, 1, size);
+	} else {
+		return DELEGATE(malloc, size);
+	}
+}
 
 #pragma mark -
 #pragma mark Integrity Checks
@@ -730,6 +711,8 @@
 	return check_configuration(zone) &&
 			// Quarantine
 			(zone->size == quarantine_size(zone->num_slots)) &&
+			(zone->begin % PAGE_SIZE == 0) &&
+			(zone->size % PAGE_SIZE == 0) &&
 			(zone->begin + zone->size == zone->end) &&
 			(zone->begin < zone->end) &&
 			(zone->region_size == 2 * k_zone_spacer + zone->size) &&
@@ -807,30 +790,32 @@
 #pragma mark -
 #pragma mark Introspection Functions
 
-typedef enum { rt_zone_only, rt_slots, rt_slots_and_metadata } read_type_t;
-
-#define READ(remote_address, size, local_memory, checker, zone) \
+typedef enum {
+	rt_zone_only = 1 << 0,
+	rt_slots     = 1 << 1,
+	rt_metadata  = 1 << 2,
+} read_type_t;
+
+#define READ(remote_address, size, local_memory, checker, check_data) \
 { \
 	kern_return_t kr = reader(task, (vm_address_t)remote_address, size, (void **)local_memory); \
 	if (kr != KERN_SUCCESS) return kr; \
-	if (!checker(zone)) return KERN_FAILURE; \
+	if (!checker(check_data)) return KERN_FAILURE; \
 }
 
 static kern_return_t
 read_zone(task_t task, vm_address_t zone_address, memory_reader_t reader, pgm_zone_t *zone, read_type_t read_type)
 {
-	if (!reader && task == mach_task_self()) {
-		reader = _malloc_default_reader;
-	}
+	reader = reader_or_in_memory_fallback(reader, task);
 
 	pgm_zone_t *zone_ptr;
 	READ(zone_address, sizeof(pgm_zone_t), &zone_ptr, check_zone, zone_ptr);
 	*zone = *zone_ptr;  // Copy to writable memory
 
-	if (read_type >= rt_slots) {
+	if (read_type & rt_slots) {
 		READ(zone->slots, zone->num_slots * sizeof(slot_t), &zone->slots, check_slots, zone);
 	}
-	if (read_type >= rt_slots_and_metadata) {
+	if (read_type & rt_metadata) {
 		READ(zone->metadata, zone->max_metadata * sizeof(metadata_t), &zone->metadata, check_metadata, zone);
 	}
 	return KERN_SUCCESS;
@@ -842,7 +827,7 @@
 		kern_return_t kr = read_zone(task, zone_address, reader, &zone_copy, read_type); \
 		if (kr != KERN_SUCCESS) return kr; \
 	} \
-	pgm_zone_t *zone = &zone_copy;
+	const pgm_zone_t *zone = &zone_copy;
 
 #define RECORD(remote_address, size_, type) \
 { \
@@ -885,7 +870,7 @@
 }
 
 static void
-pgm_statistics(pgm_zone_t *zone, malloc_statistics_t *stats)
+pgm_statistics(const pgm_zone_t *zone, malloc_statistics_t *stats)
 {
 	*stats = (malloc_statistics_t){
 		.blocks_in_use = zone->num_allocations,
@@ -996,6 +981,8 @@
 #pragma mark -
 #pragma mark Zone Templates
 
+#define PGM_ZONE_VERSION 15
+
 // Suppress warning: incompatible function pointer types
 #define FN_PTR(fn) (void *)(&fn)
 
@@ -1031,6 +1018,9 @@
 #else
 	.enumerate_unavailable_without_blocks = NULL,
 #endif
+
+	// Zone type
+	.zone_type = MALLOC_ZONE_TYPE_PGM,
 };
 
 static const malloc_zone_t malloc_zone_template = {
@@ -1048,19 +1038,21 @@
 	.destroy = FN_PTR(pgm_destroy),
 
 	// Batch operations
-	.batch_malloc = FN_PTR(pgm_batch_malloc),
-	.batch_free = FN_PTR(pgm_batch_free),
+	.batch_malloc = malloc_zone_batch_malloc_fallback,
+	.batch_free = malloc_zone_batch_free_fallback,
 
 	// Introspection
 	.zone_name = "ProbGuardMallocZone",
-	.version = 12,
+	.version = PGM_ZONE_VERSION,
 	.introspect = (malloc_introspection_t *)&introspection_template, // Effectively const.
 
 	// Specialized operations
 	.memalign = FN_PTR(pgm_memalign),
 	.free_definite_size = FN_PTR(pgm_free_definite_size),
 	.pressure_relief = NULL,
-	.claimed_address = FN_PTR(pgm_claimed_address)
+	.claimed_address = FN_PTR(pgm_claimed_address),
+	.try_free_default = NULL,
+	.malloc_with_options = FN_PTR(pgm_malloc_with_options),
 };
 
 
@@ -1070,8 +1062,7 @@
 static const char *
 env_var(const char *name)
 {
-	const char **env = (const char **)*_NSGetEnviron();
-	return _simple_getenv(env, name);
+	return getenv(name);
 }
 
 static uint32_t
@@ -1098,6 +1089,29 @@
 #pragma mark -
 #pragma mark Zone Configuration
 
+static struct {
+	bool internal_build;
+	bool MallocProbGuard_is_set;
+	bool MallocProbGuard;
+	bool MallocProbGuardViaLaunchd;
+} g_env;
+
+void
+pgm_init_config(bool internal_build)
+{
+	// Avoid dirty memory; do not write in the common case
+	if (internal_build) {
+		g_env.internal_build = internal_build;
+	}
+	if (env_var("MallocProbGuard")) {
+		g_env.MallocProbGuard_is_set = true;
+		g_env.MallocProbGuard = env_bool("MallocProbGuard");
+	}
+	if (env_bool("MallocProbGuardViaLaunchd")) {
+		g_env.MallocProbGuardViaLaunchd = true;
+	}
+}
+
 static bool
 is_platform_binary(void)
 {
@@ -1122,7 +1136,7 @@
 		return false;
 	}
 #else
-	if (!env_bool("MallocProbGuardViaLaunchd")) {
+	if (!g_env.MallocProbGuardViaLaunchd) {
 		return false;
 	}
 #endif
@@ -1145,11 +1159,12 @@
 
 
 bool
-pgm_should_enable(bool internal_build)
-{
-	if (env_var("MallocProbGuard")) {
-		return env_bool("MallocProbGuard");
-	}
+pgm_should_enable(void)
+{
+	if (g_env.MallocProbGuard_is_set) {
+		return g_env.MallocProbGuard;
+	}
+	bool internal_build = g_env.internal_build;
 	if (FEATURE_FLAG(ProbGuard, true) && should_activate(internal_build)) {
 #if TARGET_OS_OSX || TARGET_OS_IOS
 		return true;
@@ -1245,11 +1260,20 @@
 static void my_vm_protect(vm_address_t addr, size_t size, vm_prot_t protection);
 
 static void
+disable_unsupported_apis(malloc_zone_t *pgm_zone, const malloc_zone_t *wrapped_zone)
+{
+	#define DISABLE_UNSUPPORTED(api) if (!wrapped_zone->api) pgm_zone->api = NULL;
+	DISABLE_UNSUPPORTED(memalign)
+	DISABLE_UNSUPPORTED(free_definite_size)
+}
+
+static void
 setup_zone(pgm_zone_t *zone, malloc_zone_t *wrapped_zone)
 {
 	// Malloc zone
 	zone->malloc_zone = malloc_zone_template;
 	zone->wrapped_zone = wrapped_zone;
+	disable_unsupported_apis(&zone->malloc_zone, wrapped_zone);
 
 	// Configuration
 	configure_zone(zone);
@@ -1274,10 +1298,12 @@
 malloc_zone_t *
 pgm_create_zone(malloc_zone_t *wrapped_zone)
 {
-	// rdar://74948496 ([PGM] Drop all requirements for wrapped_zone)
-	MALLOC_ASSERT(wrapped_zone->version >= 6);
-	MALLOC_ASSERT(wrapped_zone->batch_malloc && wrapped_zone->batch_free &&
-			wrapped_zone->memalign && wrapped_zone->free_definite_size);
+	// The PGM zone includes the `malloc_introspection_t::zone_type` field
+	// (version 14) so we need to support the "malloc()/calloc() set errno to
+	// ENOMEM on failure" behavior (version 13).  Require wrapped zone to be at
+	// least version 13.
+	MALLOC_STATIC_ASSERT(PGM_ZONE_VERSION == 15, "PGM zone version");
+	MALLOC_ASSERT(wrapped_zone->version >= 13);
 
 	pgm_zone_t *zone = (pgm_zone_t *)my_vm_map(sizeof(pgm_zone_t), VM_PROT_READ_WRITE, VM_MEMORY_MALLOC);
 	setup_zone(zone, wrapped_zone);
@@ -1318,8 +1344,10 @@
 		return;
 	}
 	if (should_log(zone)) {
-		malloc_report(ASL_LEVEL_INFO, "ProbGuard: %9s 0x%lx, fill state: %3u/%u\n",
-				label, addr,	zone->num_allocations, zone->max_allocations);
+		malloc_report(ASL_LEVEL_INFO,
+				"ProbGuard: %9s 0x%llx, fill state: %3u/%u\n", label,
+				(unsigned long long)addr, zone->num_allocations,
+				zone->max_allocations);
 	}
 	if (!pgm_check(zone)) {
 		MALLOC_REPORT_FATAL_ERROR(addr, "ProbGuard: zone integrity check failed");
@@ -1361,16 +1389,29 @@
 	}
 }
 
-static void
+#define KR_NO_PGM (KERN_RETURN_MAX + 1)
+static kern_return_t
 diagnose_page_fault(const pgm_zone_t *zone, vm_address_t fault_address, pgm_report_t *report)
 {
+	if (!is_guarded(zone, fault_address)) {
+		return KR_NO_PGM;
+	}
+
 	slot_lookup_t res = lookup_slot(zone, fault_address);
+	// Guaranteed by lookup_slot()
 	MALLOC_ASSERT(res.slot < zone->num_slots);
+	// Checked by read_zone()
 	MALLOC_ASSERT(zone->slots[res.slot].metadata < zone->max_metadata);
 	slot_state_t ss = zone->slots[res.slot].state;
 
-	// We got here because of a page fault.
-	MALLOC_ASSERT(ss != ss_allocated || res.bounds == b_oob_guard_page);
+	// We should have gotten here because of a page fault.
+	if (ss == ss_allocated && res.bounds != b_oob_guard_page) {
+		malloc_report(ASL_LEVEL_ERR | MALLOC_REPORT_LOG_ONLY,
+				"Failed to generate PGM report for fault address %p: "
+				"slot is unexpectedly allocated with bounds %d\n",
+				(void *)fault_address, (int)res.bounds);
+		return KERN_FAILURE;
+	}
 
 	// Note that all of the following error conditions may also be caused by:
 	//  *) Randomly corrupted pointer
@@ -1405,28 +1446,26 @@
 			}
 			break;
 		default:
+			// Checked by read_zone()
 			__builtin_unreachable();
 	}
 
 	report->fault_address = fault_address;
 	fill_in_report(zone, res.slot, report);
-}
-
-
-#pragma mark -
-#pragma mark Crash Reporter API
-
-static kern_return_t
-diagnose_fault_from_external_process(vm_address_t fault_address, pgm_report_t *report,
-		task_t task, vm_address_t zone_address, memory_reader_t reader)
-{
-	READ_ZONE(zone, rt_slots_and_metadata);
-	diagnose_page_fault(zone, fault_address, report);
 	return KERN_SUCCESS;
 }
 
+
+#pragma mark -
+#pragma mark Crash Reporter SPI
+
+// KERN_FAILURE - memory read error
+// KERN_SUCCESS - memory read ok, PGM report filled
+// KR_NO_PGM    - memory read ok, but no PGM result, try next zone
+MALLOC_STATIC_ASSERT(KR_NO_PGM > KERN_RETURN_MAX, "KR_NO_PGM");
+
 static crash_reporter_memory_reader_t g_crm_reader;
-static const uint32_t k_max_read_memory = 3;
+static const uint32_t k_max_read_memory = 3;  // See read_zone() and read_type_t
 static void *read_memory[k_max_read_memory];
 static uint32_t num_read_memory;
 static kern_return_t
@@ -1435,13 +1474,15 @@
 	MALLOC_ASSERT(num_read_memory < k_max_read_memory);
 	void *ptr = g_crm_reader(task, address, size);
 	*local_memory = ptr;
+	if (!ptr) return KERN_FAILURE;
 	read_memory[num_read_memory++] = ptr;
-	return ptr ? KERN_SUCCESS : KERN_FAILURE;
+	return KERN_SUCCESS;
 }
 
 static memory_reader_t *
 setup_memory_reader(crash_reporter_memory_reader_t crm_reader)
 {
+	MALLOC_ASSERT(crm_reader);
 	g_crm_reader = crm_reader;
 	num_read_memory = 0;
 	return memory_reader_adapter;
@@ -1451,20 +1492,58 @@
 free_read_memory()
 {
 	for (uint32_t i = 0; i < num_read_memory; i++) {
-		free(read_memory[i]);
-	}
+		_free(read_memory[i]);
+	}
+	num_read_memory = 0;
+}
+
+static kern_return_t
+is_pgm_zone(vm_address_t zone_address, task_t task, memory_reader_t reader)
+{
+	unsigned zone_type;
+	kern_return_t kr = get_zone_type(task, reader, zone_address, &zone_type);
+	if (kr != KERN_SUCCESS) {
+		return kr;
+	}
+
+	return (zone_type == MALLOC_ZONE_TYPE_PGM) ? KERN_SUCCESS : KR_NO_PGM;
+}
+
+static kern_return_t
+diagnose_fault_from_external_process(vm_address_t fault_address, pgm_report_t *report,
+		task_t task, vm_address_t zone_address, memory_reader_t reader)
+{
+	READ_ZONE(zone, rt_slots | rt_metadata);
+	return diagnose_page_fault(zone, fault_address, report);
+}
+
+static kern_return_t
+extract_report_select_zone(vm_address_t fault_address, pgm_report_t *report,
+		task_t task, vm_address_t *zone_addresses, uint32_t zone_count, memory_reader_t reader)
+{
+	for (uint32_t i = 0; i < zone_count; i++) {
+		kern_return_t kr = is_pgm_zone(zone_addresses[i], task, reader);
+		free_read_memory();
+		if (kr == KR_NO_PGM) continue;
+		if (kr != KERN_SUCCESS) return kr;
+
+		kr = diagnose_fault_from_external_process(fault_address, report, task, zone_addresses[i], reader);
+		free_read_memory();
+		if (kr == KR_NO_PGM) continue;
+		return kr;
+	}
+	return KERN_FAILURE;
 }
 
 static _malloc_lock_s crash_reporter_lock = _MALLOC_LOCK_INIT;
 kern_return_t
-pgm_diagnose_fault_from_crash_reporter(vm_address_t fault_address, pgm_report_t *report,
-		task_t task, vm_address_t zone_address, crash_reporter_memory_reader_t crm_reader)
+pgm_extract_report_from_corpse(vm_address_t fault_address, pgm_report_t *report, task_t task,
+		vm_address_t *zone_addresses, uint32_t zone_count, crash_reporter_memory_reader_t crm_reader)
 {
 	_malloc_lock_lock(&crash_reporter_lock);
 
 	memory_reader_t *reader = setup_memory_reader(crm_reader);
-	kern_return_t kr = diagnose_fault_from_external_process(fault_address, report, task, zone_address, reader);
-	free_read_memory();
+	kern_return_t kr = extract_report_select_zone(fault_address, report, task, zone_addresses, zone_count, reader);
 
 	_malloc_lock_unlock(&crash_reporter_lock);
 	return kr;
@@ -1531,7 +1610,7 @@
 	kern_return_t kr = mach_vm_map(target, &address, size_rounded, mask, flags,
 		object, offset, copy, cur_protection, max_protection, inheritance);
 	MALLOC_ASSERT(kr == KERN_SUCCESS);
-	return address;
+	return (vm_address_t)address;
 }
 
 static vm_address_t