Loading...
--- libmalloc/libmalloc-166.251.2/tests/nano_tests.c
+++ libmalloc/libmalloc-646.0.13/tests/nano_tests.c
@@ -85,26 +85,44 @@
 
 static void *allocations[ALLOCATION_COUNT];
 
+T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true), T_META_TAG_VM_NOT_PREFERRED);
+
 T_DECL(nano_active_test, "Test that Nano is activated",
-	   T_META_ENVVAR("MallocNanoZone=1"))
-{
-#if CONFIG_NANOZONE
-	void *ptr = malloc(16);
-	T_LOG("Nano ptr is %p\n", ptr);
-	T_ASSERT_EQ(NANOZONE_SIGNATURE, (uint64_t)((uintptr_t)ptr) >> SHIFT_NANO_SIGNATURE,
-			"Nanozone is active");
-	T_ASSERT_NE(malloc_engaged_nano(), 0, "Nanozone engaged");
-	free(ptr);
+		T_META_ENVVAR("MallocNanoZone=1"), T_META_ENVVAR("MallocProbGuard=0"),
+		T_META_TAG_XZONE, T_META_TAG_NANO_ON_XZONE)
+{
+#if CONFIG_NANOZONE
+	bool nano_on_xzone = getenv("MallocNanoOnXzone");
+	if (nano_on_xzone) {
+		T_ASSERT_NE(malloc_engaged_secure_allocator(), 0,
+				"Secure allocator engaged");
+	}
+
+	T_ASSERT_NE(malloc_engaged_nano(), 0, "Nano mode engaged");
+
+	if (nano_on_xzone || !malloc_engaged_secure_allocator()) {
+		void *ptr = malloc(16);
+		T_LOG("Nano ptr is %p\n", ptr);
+		T_ASSERT_EQ(NANOZONE_SIGNATURE, (uint64_t)((uintptr_t)ptr) >> SHIFT_NANO_SIGNATURE,
+				"Nanozone is active");
+		free(ptr);
+	}
 #else // CONFIG_NANOZONE
 	T_SKIP("Nano allocator not configured");
 #endif // CONFIG_NANOZONE
 }
 
 T_DECL(nano_enumerator_test, "Test the Nanov2 enumerator",
-	   T_META_ENVVAR("MallocNanoZone=V2"))
-{
-#if CONFIG_NANOZONE
-	T_ASSERT_EQ(malloc_engaged_nano(), 2, "Nanozone V2 engaged");
+		T_META_ENVVAR("MallocNanoZone=V2"), T_META_ENVVAR("MallocProbGuard=0"),
+		T_META_TAG_XZONE)
+{
+#if CONFIG_NANOZONE
+	if (malloc_engaged_secure_allocator()) {
+		T_ASSERT_NE(malloc_engaged_nano(), 0,
+				"Secure allocator nano mode engaged");
+	} else {
+		T_ASSERT_EQ(malloc_engaged_nano(), 2, "Nanozone V2 engaged");
+	}
 
 	// This test is problematic because the allocator is used before the test
 	// starts, so we can't start everything from zero.
@@ -136,7 +154,7 @@
 			"Incorrect blocks_in_use");
 	T_ASSERT_EQ(stats.size_in_use, initial_size_in_use + total_requested_size,
 			"Incorrect size_in_use");
-	T_ASSERT_TRUE(stats.size_allocated - initial_size_allocated >= total_requested_size,
+	T_ASSERT_GE(stats.size_allocated, total_requested_size,
 			"Size allocated must be >= size requested");
 
 	T_ASSERT_EQ(ptr_count, initial_ptrs + ALLOCATION_COUNT,
@@ -159,7 +177,7 @@
 	T_ASSERT_EQ(stats.size_in_use,
 			initial_size_in_use + total_requested_size - size_freed,
 			"Incorrect size_in_use after half free");
-	T_ASSERT_TRUE(stats.size_allocated >= initial_size_allocated ,
+	T_ASSERT_GE(stats.size_allocated, initial_size_allocated,
 			"Size allocated must be >= size requested");
 
 	T_ASSERT_EQ(ptr_count, initial_ptrs + ALLOCATION_COUNT / 2,
@@ -181,7 +199,7 @@
 			"Incorrect blocks_in_use after full free");
 	T_ASSERT_EQ(stats.size_in_use, initial_size_in_use,
 			"Incorrect size_in_use after full free");
-	T_ASSERT_TRUE(stats.size_allocated >= initial_size_allocated ,
+	T_ASSERT_GE(stats.size_allocated, initial_size_allocated,
 			"Size allocated must be >= size requested");
 	
 	T_ASSERT_EQ(ptr_count, initial_ptrs, "Incorrect number of pointers after free");
@@ -245,7 +263,7 @@
 }
 
 T_DECL(realloc_nano_to_other, "realloc with allocator change (nano)",
-	   T_META_ENVVAR("MallocNanoZone=1"))
+	   T_META_ENVVAR("MallocNanoZone=1"), T_META_TAG_NANO_ON_XZONE)
 {
 #if CONFIG_NANOZONE
 	void *ptr = malloc(32);					// From Nano
@@ -308,6 +326,25 @@
 #endif // CONFIG_NANOZONE
 }
 
+T_DECL(nano_memalign_trivial, "Test that nano serves trivial memalign allocations",
+	   T_META_ENVVAR("MallocNanoZone=1"))
+{
+#if CONFIG_NANOZONE
+	size_t size = 16;
+	void *ptr8 = aligned_alloc(8, size);
+	void *ptr16 = aligned_alloc(16, size);
+	T_LOG("Nano ptrs are %p, %p\n", ptr8, ptr16);
+	T_ASSERT_EQ(NANOZONE_SIGNATURE, (uint64_t)((uintptr_t)ptr8) >> SHIFT_NANO_SIGNATURE,
+			"8-byte-aligned allocation served from nano");
+	T_ASSERT_EQ(NANOZONE_SIGNATURE, (uint64_t)((uintptr_t)ptr16) >> SHIFT_NANO_SIGNATURE,
+			"16-byte-aligned allocation served from nano");
+	free(ptr8);
+	free(ptr16);
+#else // CONFIG_NANOZONE
+	T_SKIP("Nano allocator not configured");
+#endif // CONFIG_NANOZONE
+}
+
 #pragma mark -
 #pragma mark Nanov2 tests
 
@@ -317,7 +354,8 @@
 #define ALLOCS_PER_ARENA ((NANOV2_ARENA_SIZE)/256)
 
 T_DECL(overspill_arena, "force overspill of an arena",
-	   T_META_ENVVAR("MallocNanoZone=V2"))
+	   T_META_ENVVAR("MallocNanoZone=V2"),
+	   T_META_ENVVAR("MallocGuardEdges=all"))
 {
 #if CONFIG_NANOZONE
 	void **ptrs = calloc(ALLOCS_PER_ARENA, sizeof(void *));
@@ -339,6 +377,10 @@
 		if (current_ptr.fields.nano_arena != first_ptr.fields.nano_arena) {
 			break;
 		}
+
+		// Write to the pointer to ensure the containing block is not
+		// a guard block.
+		*(int *)ptrs[index] = 0;
 	}
 
 	// Free everything, which is a check that the book-keeping works across
@@ -352,16 +394,14 @@
 #endif // CONFIG_NANOZONE
 }
 
-#if TARGET_OS_OSX
-
+#if CONFIG_NANOZONE
 // Guaranteed number of 256-byte allocations to be sure we fill a region.
 #define ALLOCS_PER_REGION ((NANOV2_REGION_SIZE)/256)
 
-// This test is required only on macOS because iOS only uses one region.
+#if NANOV2_MULTIPLE_REGIONS
 T_DECL(overspill_region, "force overspill of a region",
 	   T_META_ENVVAR("MallocNanoZone=V2"))
 {
-#if CONFIG_NANOZONE
 	void **ptrs = calloc(ALLOCS_PER_REGION, sizeof(void *));
 	T_QUIET; T_ASSERT_NOTNULL(ptrs, "Unable to allocate pointers");
 	int index;
@@ -389,9 +429,157 @@
 		free(ptrs[i]);
 	}
 	free(ptrs);
-#else // CONFIG_NANOZONE
-	T_SKIP("Nano allocator not configured");
-#endif // CONFIG_NANOZONE
-}
-#endif // TARGET_OS_OSX
-
+}
+#endif // NANOV2_MULTIPLE_REGIONS
+
+extern malloc_zone_t **malloc_zones;
+
+T_DECL(overspill_nanozone, "force overspill of nano zone",
+		T_META_ENVVAR("MallocNanoZone=V2"),
+		T_META_ENVVAR("MallocNanoMaxRegion=1"),
+		T_META_ENVVAR("MallocProbGuard=0"),
+		T_META_TAG_NANO_ON_XZONE)
+{
+	int index;
+	bool spilled_to_tiny = false;
+	void **ptrs;
+	malloc_zone_t *nano_zone = malloc_zones[0];
+	malloc_zone_t *helper_zone = malloc_zones[1];
+
+	// Max number of 256B allocations that will fit in the nano zone (+1 to overspill)
+	const unsigned int nano_max_allocations = 2 * ALLOCS_PER_REGION + 1;
+
+	T_LOG("Allocating %d pointers for allocations", nano_max_allocations);
+	ptrs = calloc(nano_max_allocations, sizeof(void *));
+	T_QUIET; T_ASSERT_NOTNULL(ptrs, "Unable to allocate pointers");
+
+	ptrs[0] = malloc(256);
+	T_QUIET; T_ASSERT_NOTNULL(ptrs[0], "Failed to make initial allocation");
+	T_QUIET; T_ASSERT_TRUE(malloc_zone_claimed_address(nano_zone, ptrs[0]), 
+			"Initial allocation did not come from nano zone");
+	T_QUIET; T_ASSERT_FALSE(malloc_zone_claimed_address(helper_zone, ptrs[0]), 
+			"Initial allocation came from helper zone");
+
+	for (index = 1; index < (nano_max_allocations); index++) {
+		ptrs[index] = malloc(256);
+		if (malloc_zone_claimed_address(helper_zone, ptrs[index])) {
+			T_LOG("Spilled to helper zone");
+			spilled_to_tiny = true;
+			break;
+		}
+	}
+	T_EXPECT_TRUE(spilled_to_tiny, "Allocation falls through to helper zone");
+
+	T_LOG("Freeing %d pointers", index);
+	for (int i = 0; i < MIN(index + 1, nano_max_allocations); i++) {
+		free(ptrs[i]);
+	}
+	free(ptrs);
+}
+
+#if NANOV2_MULTIPLE_REGIONS
+void *
+punch_holes_thread(void *arg)
+{
+	T_LOG("Starting holes thread");
+	bool *done = arg;
+
+	bool holes[1024] = { false };
+	while (!os_atomic_load(done, relaxed)) {
+		bool allocate = random() % 2;
+		int which = random() % 1024;
+		uintptr_t base = NANOZONE_BASE_REGION_ADDRESS;
+		size_t len = 128ull * 1024ull * 1024ull;
+		size_t stride = 512ull * 1024ull * 1024ull;
+		uint64_t offset = stride * which;
+		void *addr = (void *)(base + offset);
+
+		if (allocate && !holes[which]) {
+			void *hole = mmap(addr, len, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
+			if (hole == addr) {
+				holes[which] = true;
+				T_LOG("punched hole %d (%p, %p)", which, hole, addr);
+			} else if (hole != MAP_FAILED) {
+				T_LOG("failed to punch hole %d (wanted %p, got %p), unmapping", which,
+						addr, hole);
+				munmap(hole, len);
+			}
+		} else if (!allocate && holes[which]) {
+			munmap(addr, len);
+			holes[which] = false;
+			T_LOG("unmapped hole %d", which);
+		}
+
+		usleep(300);
+	}
+
+	return NULL;
+}
+
+void *
+do_allocations_thread(void *arg)
+{
+	bool *done = arg;
+
+	size_t n = (256ull << 20) / 128;
+	void **allocations = malloc(n * sizeof(void *));
+	T_ASSERT_NOTNULL(allocations, "allocations");
+
+	while (!os_atomic_load(done, relaxed)) {
+		for (int i = 0; i < n; i++) {
+			allocations[i] = malloc(128);
+		}
+
+		usleep(100);
+
+		for (int i = 0; i < n; i++) {
+			free(allocations[i]);
+		}
+
+		usleep(50000 * ((random() % 8) + 1));
+	}
+
+	free(allocations);
+
+	return NULL;
+}
+
+T_DECL(region_holes, "ensure correct handling of holes between regions",
+		T_META_ENVVAR("MallocNanoZone=V2"),
+		// Region reservation does not allow for holes between regions
+		T_META_ENABLED(!CONFIG_NANO_RESERVE_REGIONS))
+{
+	srandom(time(NULL));
+
+	bool done = false;
+
+	pthread_t holes_thread;
+	int rc = pthread_create(&holes_thread, NULL, punch_holes_thread, &done);
+	T_ASSERT_POSIX_ZERO(rc, "pthread_create");
+
+	int nthreads = 4;
+	pthread_t allocation_threads[nthreads];
+	for (int i = 0; i < nthreads; i++) {
+		rc = pthread_create(&allocation_threads[i], NULL, do_allocations_thread,
+				&done);
+		T_ASSERT_POSIX_ZERO(rc, "pthread_create");
+	}
+
+	sleep(4); // arbitrary time to try to hit the race
+
+	os_atomic_store(&done, true, relaxed);
+
+	rc = pthread_join(holes_thread, NULL);
+	T_ASSERT_POSIX_ZERO(rc, "pthread_join");
+
+	for (int i = 0; i < nthreads; i++) {
+		rc = pthread_join(allocation_threads[i], NULL);
+		T_ASSERT_POSIX_ZERO(rc, "pthread_join");
+	}
+
+	T_PASS("Didn't crash");
+	T_END;
+}
+#endif // NANOV2_MULTIPLE_REGIONS
+#endif // CONFIG_NANOZONE
+