Loading...
tests/xzone_corruption.c /dev/null libmalloc-792.80.2
--- /dev/null
+++ libmalloc/libmalloc-792.80.2/tests/xzone_corruption.c
@@ -0,0 +1,119 @@
+#include <sys/queue.h>
+
+#include <darwintest.h>
+#include <../src/internal.h>
+
+#if CONFIG_XZONE_MALLOC
+
+T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true), T_META_TAG_VM_NOT_PREFERRED);
+
+// Ensure that all allocations get the same bucketing
+MALLOC_NOINLINE
+static void *
+malloc_wrapper(size_t n)
+{
+	return malloc_type_malloc(n, (malloc_type_id_t)42);
+}
+
+static bool
+same_chunk(void *a, void *b)
+{
+	uintptr_t chunk_a =
+			(((uintptr_t)a) & (XZM_LIMIT_ADDRESS - 1)) >> XZM_TINY_CHUNK_SHIFT;
+	uintptr_t chunk_b =
+			(((uintptr_t)b) & (XZM_LIMIT_ADDRESS - 1)) >> XZM_TINY_CHUNK_SHIFT;
+	return chunk_a == chunk_b;
+}
+
+static void
+test_freelist_corruption(bool linkage)
+{
+	pid_t child_pid = fork();
+	T_ASSERT_NE(child_pid, -1, "fork()");
+
+	if (!child_pid) {
+		// Exhaust the early allocator
+		for (int i = 0; i < 1000; i++) {
+			void *p = malloc_wrapper(1024);
+			free(p);
+		}
+
+		uint32_t seqno_cycles = arc4random_uniform(100000);
+		uint32_t bit_to_flip = arc4random_uniform(64);
+
+		// Get two allocations from the same chunk, so that we can free and
+		// modify one without having to worry about the underlying page being
+		// madvised
+		void *p1, *p2;
+		p1 = malloc_wrapper(1024);
+		while (true) {
+			p2 = malloc_wrapper(1024);
+
+			if (same_chunk(p1, p2)) {
+				// Cycle the sequence number for the chunk up
+				for (uint32_t i = 0; i < seqno_cycles; i++) {
+					free(p2);
+					p2 = malloc_wrapper(1024);
+				}
+
+				// Retry a limited number of times, on the outside chance that
+				// the signature happens to also be valid for the corrupted bits
+				for (int i = 0; i < 5; i++) {
+					free(p2);
+
+					xzm_block_t block = p2;
+					if (linkage) {
+						block->xzb_linkage.xzbl_next_value ^= (1ull << bit_to_flip);
+					} else {
+						block->xzb_cookie ^= (1ull << bit_to_flip);
+					}
+
+					p2 = malloc_wrapper(1024);
+				}
+				T_ASSERT_FAIL("Corruption not detected");
+			}
+
+			p1 = p2;
+			// this process is intended to crash, so we're not worried about
+			// leaks
+		}
+
+		// Should not get here
+		T_FAIL("Tiny linkage corruption test failed");
+	} else {
+		int status;
+		pid_t wait_pid = waitpid(child_pid, &status, 0);
+		T_ASSERT_EQ(wait_pid, child_pid, "Got child status");
+		T_ASSERT_TRUE(WIFSIGNALED(status), "Child terminated by signal");
+	}
+}
+
+T_DECL(tiny_freelist_cookie_corruption,
+		"Crash on corruption of tiny freelist cookie",
+		T_META_ENVVAR("MallocXzoneSlotConfig=0"),
+		T_META_IGNORECRASHES("xzone_corruption"),
+		T_META_TAG_XZONE_ONLY)
+{
+	test_freelist_corruption(false);
+}
+
+T_DECL(tiny_freelist_linkage_corruption,
+		"Crash on corruption of tiny freelist linkage",
+		T_META_ENVVAR("MallocXzoneSlotConfig=0"),
+		T_META_IGNORECRASHES("xzone_corruption"),
+		T_META_TAG_XZONE_ONLY,
+		T_META_ENABLED(__has_feature(ptrauth_calls)))
+{
+	test_freelist_corruption(true);
+}
+
+#else // CONFIG_XZONE_MALLOC
+
+T_DECL(tiny_freelist_corruption, "Crash on corruption of tiny freelist",
+		T_META_ENABLED(false), T_META_TAG_VM_PREFERRED,
+		T_META_TAG_NO_ALLOCATOR_OVERRIDE)
+{
+	T_SKIP("Nothing to test for !CONFIG_XZONE_MALLOC");
+}
+
+#endif // CONFIG_XZONE_MALLOC