Loading...
tests/xctests/magazine_tiny_tests.m libmalloc-792.80.2 libmalloc-409.81.2
--- libmalloc/libmalloc-792.80.2/tests/xctests/magazine_tiny_tests.m
+++ libmalloc/libmalloc-409.81.2/tests/xctests/magazine_tiny_tests.m
@@ -309,3 +309,205 @@
 }
 
 @end
+
+@interface magazine_tiny_corruption_tests : magazine_tiny_tests
+@end
+
+extern jmp_buf *zone_error_expected_jmp;
+
+// The structure of these tests is:
+// - setjmp() at the start
+// - Set up an interesting state in tiny
+// - Corrupt a free block at some offset to make sure we detect it
+// - Perform an allocation that should get that free block
+//
+// That should result in an internal call to malloc_zone_error(), which is
+// replaced by a mock that longjmp()s to the zone_error_expected_jmp global that
+// we set to the jmp_buf from the beginning of the test.
+//
+// This allows us to test that a call to malloc_zone_error() occurs where we
+// expect.
+//
+// To avoid potential issues mixing setjmp()/longjmp() and ObjC, the relevant
+// test logic is in C functions.
+
+typedef void (*corruption_test_fn_t)(magazine_tiny_corruption_tests *self,
+		int offset, jmp_buf *env);
+
+static void
+run_corruption_test_at_offset(magazine_tiny_corruption_tests *self,
+		corruption_test_fn_t testfn, int offset)
+{
+	jmp_buf env;
+
+	int val = setjmp(env);
+	if (val != 0) {
+		// success!
+		return;
+	}
+
+	testfn(self, offset, &env);
+
+	XCTFail("Expected testfn to induce a call to malloc_zone_error");
+}
+
+static void
+corrupt_at_offset(void *ptr, size_t size, int offset, jmp_buf *env)
+{
+	XCTAssertEqual(offset % 4, 0);
+
+	if (offset < 0) {
+		offset = (int)size + offset;
+	}
+
+	*(uint32_t *)((char *)ptr + offset) = 0xabcddcbau;
+
+	// expect the next interaction to detect corruption
+	zone_error_expected_jmp = env;
+}
+
+static void
+test_corruption_in_cache(magazine_tiny_corruption_tests *self, int offset,
+		jmp_buf *env)
+{
+	const size_t size = 64;
+
+	void *ptr = [self tiny_malloc:size];
+	memset(ptr, 'a', size);
+
+	[self tiny_free:ptr];
+
+	corrupt_at_offset(ptr, size, offset, env);
+	(void)tiny_malloc_should_clear(&self->tiny_rack, TINY_MSIZE_FOR_BYTES(size),
+			false);
+}
+
+static void
+test_corruption_in_freelist(magazine_tiny_corruption_tests *self, int offset,
+		jmp_buf *env)
+{
+	const size_t size = 272; // skip the tiny cache
+
+	void *ptr = [self tiny_malloc:size];
+	memset(ptr, 'a', size);
+
+	[self tiny_free:ptr];
+
+	corrupt_at_offset(ptr, size, offset, env);
+	(void)tiny_malloc_should_clear(&self->tiny_rack, TINY_MSIZE_FOR_BYTES(size),
+			false);
+}
+
+static void
+test_realloc_cache_corruption(magazine_tiny_corruption_tests *self, int offset,
+		jmp_buf *env)
+{
+	const size_t size = 64;
+
+	void *ptr1 = [self tiny_malloc:size];
+	memset(ptr1, 'a', size);
+
+	void *ptr2 = [self tiny_malloc:size];
+	memset(ptr2, 'a', size);
+
+	XCTAssertEqual((uintptr_t)ptr1 + size, (uintptr_t)ptr2);
+
+	[self tiny_free:ptr2]; // put in the cache
+
+	corrupt_at_offset(ptr2, size, offset, env);
+
+	(void)tiny_try_realloc_in_place(&self->tiny_rack, ptr1, size,
+			size * 2);
+}
+
+static void
+test_realloc_freelist_corruption(magazine_tiny_corruption_tests *self,
+		int offset, jmp_buf *env)
+{
+	const size_t size = 272; // skip the cache
+	const size_t small_size = 64; // go through the cache
+
+	void *ptr1 = [self tiny_malloc:size];
+	memset(ptr1, 'a', size);
+
+	void *ptr2 = [self tiny_malloc:small_size];
+	memset(ptr2, 'b', small_size);
+
+	void *ptr3 = [self tiny_malloc:small_size];
+	memset(ptr3, 'c', small_size);
+
+	XCTAssertEqual((uintptr_t)ptr1 + size, (uintptr_t)ptr2);
+	XCTAssertEqual((uintptr_t)ptr2 + small_size, (uintptr_t)ptr3);
+
+	[self tiny_free:ptr2];
+	// Push block 2 out of the cache
+	[self tiny_free:ptr3];
+
+	corrupt_at_offset(ptr2, small_size, offset, env);
+
+	(void)tiny_try_realloc_in_place(&self->tiny_rack, ptr1, size,
+			size + small_size);
+}
+
+static void
+test_realloc_end_corruption(magazine_tiny_corruption_tests *self,
+		int offset, jmp_buf *env)
+{
+	const size_t size = 64;
+	const size_t end_size = 16;
+
+	void *ptr1 = [self tiny_malloc:size];
+	memset(ptr1, 'a', size);
+
+	void *end = (char *)ptr1 + size;
+	corrupt_at_offset(end, end_size, offset, env);
+
+	(void)tiny_try_realloc_in_place(&self->tiny_rack, ptr1, size,
+			size + end_size);
+}
+
+@implementation magazine_tiny_corruption_tests
+
+- (void)setUp {
+	zone_error_expected_jmp = NULL;
+	malloc_zero_on_free_sample_period = 1; // zero-assert on every allocation
+	[super setUp];
+}
+
+// Need to generate a separate test case method for each offset we want to test
+// corruption at to reset tiny_rack to something sane
+
+#define CORRUPTION_TEST_CASE(name, fn, off, offname) \
+	- (void)name##Offset##offname { \
+		run_corruption_test_at_offset(self, fn, off); \
+	}
+
+#define SHORT_CORRUPTION_TEST_CASES(name, fn) \
+	CORRUPTION_TEST_CASE(name, fn, 0, 0) \
+	CORRUPTION_TEST_CASE(name, fn, 4, 4) \
+	CORRUPTION_TEST_CASE(name, fn, 8, 8) \
+	CORRUPTION_TEST_CASE(name, fn, 12, 12)
+
+#define CORRUPTION_TEST_CASES(name, fn) \
+	SHORT_CORRUPTION_TEST_CASES(name, fn) \
+	CORRUPTION_TEST_CASE(name, fn, 16, 16) \
+	CORRUPTION_TEST_CASE(name, fn, 20, 20) \
+	CORRUPTION_TEST_CASE(name, fn, 24, 24) \
+	CORRUPTION_TEST_CASE(name, fn, -4, Negative4) \
+	CORRUPTION_TEST_CASE(name, fn, -8, Negative8)
+
+#define LONG_CORRUPTION_TEST_CASES(name, fn) \
+	CORRUPTION_TEST_CASES(name, fn) \
+	CORRUPTION_TEST_CASE(name, fn, 128, 128)
+
+CORRUPTION_TEST_CASES(testCorruptionInCache, test_corruption_in_cache);
+LONG_CORRUPTION_TEST_CASES(testCorruptionInFreeList,
+		test_corruption_in_freelist);
+CORRUPTION_TEST_CASES(testCorruptionOnReallocFromCache,
+		test_realloc_cache_corruption);
+CORRUPTION_TEST_CASES(testCorruptionOnReallocFromFreeList,
+		test_realloc_freelist_corruption);
+SHORT_CORRUPTION_TEST_CASES(testCorruptionOnReallocFromEnd,
+		test_realloc_end_corruption);
+
+@end