Loading...
--- /dev/null
+++ libmalloc/libmalloc-792.41.1/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