xref: /xnu-8792.41.9/tests/decompression_failure.c (revision 5c2921b07a2480ab43ec66f5b9e41cb872bc554f)
1*5c2921b0SApple OSS Distributions #include <darwintest.h>
2*5c2921b0SApple OSS Distributions #include <mach/mach.h>
3*5c2921b0SApple OSS Distributions #include <sys/sysctl.h>
4*5c2921b0SApple OSS Distributions #include <stdio.h>
5*5c2921b0SApple OSS Distributions #include <stdbool.h>
6*5c2921b0SApple OSS Distributions #include <stdlib.h>
7*5c2921b0SApple OSS Distributions #include <unistd.h>
8*5c2921b0SApple OSS Distributions #include <inttypes.h>
9*5c2921b0SApple OSS Distributions #include <pthread.h>
10*5c2921b0SApple OSS Distributions #include <TargetConditionals.h>
11*5c2921b0SApple OSS Distributions #include "excserver.h"
12*5c2921b0SApple OSS Distributions #include "exc_helpers.h"
13*5c2921b0SApple OSS Distributions 
14*5c2921b0SApple OSS Distributions extern int pid_hibernate(int pid);
15*5c2921b0SApple OSS Distributions 
16*5c2921b0SApple OSS Distributions static vm_address_t page_size;
17*5c2921b0SApple OSS Distributions 
18*5c2921b0SApple OSS Distributions T_GLOBAL_META(
19*5c2921b0SApple OSS Distributions 	T_META_RADAR_COMPONENT_NAME("xnu"),
20*5c2921b0SApple OSS Distributions 	T_META_RADAR_COMPONENT_VERSION("arm"),
21*5c2921b0SApple OSS Distributions 	T_META_OWNER("peter_newman"),
22*5c2921b0SApple OSS Distributions 	T_META_REQUIRES_SYSCTL_EQ("hw.optional.wkdm_popcount", 1)
23*5c2921b0SApple OSS Distributions 	);
24*5c2921b0SApple OSS Distributions 
25*5c2921b0SApple OSS Distributions static vm_address_t *blocks;
26*5c2921b0SApple OSS Distributions static uint64_t block_count;
27*5c2921b0SApple OSS Distributions static const uint64_t block_length = 0x800000;
28*5c2921b0SApple OSS Distributions 
29*5c2921b0SApple OSS Distributions static uint32_t vm_pagesize;
30*5c2921b0SApple OSS Distributions 
31*5c2921b0SApple OSS Distributions static void
dirty_page(const vm_address_t address)32*5c2921b0SApple OSS Distributions dirty_page(const vm_address_t address)
33*5c2921b0SApple OSS Distributions {
34*5c2921b0SApple OSS Distributions 	assert((address & (page_size - 1)) == 0UL);
35*5c2921b0SApple OSS Distributions 	uint32_t *const page_as_u32 = (uint32_t *)address;
36*5c2921b0SApple OSS Distributions 	for (uint32_t i = 0; i < page_size / sizeof(uint32_t); i += 2) {
37*5c2921b0SApple OSS Distributions 		page_as_u32[i + 0] = i % 4;
38*5c2921b0SApple OSS Distributions 		page_as_u32[i + 1] = 0xcdcdcdcd;
39*5c2921b0SApple OSS Distributions 	}
40*5c2921b0SApple OSS Distributions }
41*5c2921b0SApple OSS Distributions 
42*5c2921b0SApple OSS Distributions static bool
try_to_corrupt_page(vm_address_t page_va)43*5c2921b0SApple OSS Distributions try_to_corrupt_page(vm_address_t page_va)
44*5c2921b0SApple OSS Distributions {
45*5c2921b0SApple OSS Distributions 	int val;
46*5c2921b0SApple OSS Distributions 	size_t size = sizeof(val);
47*5c2921b0SApple OSS Distributions 	int result = sysctlbyname("vm.compressor_inject_error", &val, &size,
48*5c2921b0SApple OSS Distributions 	    &page_va, sizeof(page_va));
49*5c2921b0SApple OSS Distributions 	return result == 0;
50*5c2921b0SApple OSS Distributions }
51*5c2921b0SApple OSS Distributions 
52*5c2921b0SApple OSS Distributions static void
create_corrupted_regions(void)53*5c2921b0SApple OSS Distributions create_corrupted_regions(void)
54*5c2921b0SApple OSS Distributions {
55*5c2921b0SApple OSS Distributions 	uint64_t hw_memsize;
56*5c2921b0SApple OSS Distributions 
57*5c2921b0SApple OSS Distributions 	size_t size = sizeof(unsigned int);
58*5c2921b0SApple OSS Distributions 	T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.pagesize", &vm_pagesize, &size,
59*5c2921b0SApple OSS Distributions 	    NULL, 0), "read vm.pagesize");
60*5c2921b0SApple OSS Distributions 	size = sizeof(uint64_t);
61*5c2921b0SApple OSS Distributions 	T_ASSERT_POSIX_SUCCESS(sysctlbyname("hw.memsize", &hw_memsize, &size,
62*5c2921b0SApple OSS Distributions 	    NULL, 0), "read hw.memsize");
63*5c2921b0SApple OSS Distributions 
64*5c2921b0SApple OSS Distributions #if TARGET_OS_OSX
65*5c2921b0SApple OSS Distributions 	const uint64_t max_memsize = 32ULL * 0x40000000ULL; // 32 GB
66*5c2921b0SApple OSS Distributions #else
67*5c2921b0SApple OSS Distributions 	const uint64_t max_memsize = 8ULL * 0x100000ULL; // 8 MB
68*5c2921b0SApple OSS Distributions #endif
69*5c2921b0SApple OSS Distributions 	const uint64_t effective_memsize = (hw_memsize > max_memsize) ?
70*5c2921b0SApple OSS Distributions 	    max_memsize : hw_memsize;
71*5c2921b0SApple OSS Distributions 
72*5c2921b0SApple OSS Distributions 	const uint64_t total_pages = effective_memsize / vm_pagesize;
73*5c2921b0SApple OSS Distributions 	const uint64_t pages_per_block = block_length / vm_pagesize;
74*5c2921b0SApple OSS Distributions 
75*5c2921b0SApple OSS Distributions 	// Map a as much memory as we have physical memory to back. Dirtying all
76*5c2921b0SApple OSS Distributions 	// of these pages will force a compressor sweep. The mapping is done using
77*5c2921b0SApple OSS Distributions 	// the smallest number of malloc() calls to allocate the necessary VAs.
78*5c2921b0SApple OSS Distributions 	block_count = total_pages / pages_per_block;
79*5c2921b0SApple OSS Distributions 
80*5c2921b0SApple OSS Distributions 	blocks = (vm_address_t *)malloc(sizeof(*blocks) * block_count);
81*5c2921b0SApple OSS Distributions 	for (uint64_t i = 0; i < block_count; i++) {
82*5c2921b0SApple OSS Distributions 		void *bufferp = malloc(block_length);
83*5c2921b0SApple OSS Distributions 		blocks[i] = (vm_address_t)bufferp;
84*5c2921b0SApple OSS Distributions 	}
85*5c2921b0SApple OSS Distributions 
86*5c2921b0SApple OSS Distributions 	for (uint32_t i = 0; i < block_count; i++) {
87*5c2921b0SApple OSS Distributions 		for (size_t buffer_offset = 0; buffer_offset < block_length;
88*5c2921b0SApple OSS Distributions 		    buffer_offset += vm_pagesize) {
89*5c2921b0SApple OSS Distributions 			dirty_page(blocks[i] + buffer_offset);
90*5c2921b0SApple OSS Distributions 		}
91*5c2921b0SApple OSS Distributions 	}
92*5c2921b0SApple OSS Distributions 
93*5c2921b0SApple OSS Distributions #if !TARGET_OS_OSX
94*5c2921b0SApple OSS Distributions 	// We can't use a substantial amount of memory on embedded platforms, so
95*5c2921b0SApple OSS Distributions 	// freeze the current process instead to cause everything to be compressed.
96*5c2921b0SApple OSS Distributions 	T_ASSERT_POSIX_SUCCESS(pid_hibernate(-2), NULL);
97*5c2921b0SApple OSS Distributions 	T_ASSERT_POSIX_SUCCESS(pid_hibernate(-2), NULL);
98*5c2921b0SApple OSS Distributions #endif
99*5c2921b0SApple OSS Distributions 
100*5c2921b0SApple OSS Distributions 	uint32_t corrupt = 0;
101*5c2921b0SApple OSS Distributions 	for (uint32_t i = 0; i < block_count; i++) {
102*5c2921b0SApple OSS Distributions 		for (size_t buffer_offset = 0; buffer_offset < block_length;
103*5c2921b0SApple OSS Distributions 		    buffer_offset += vm_pagesize) {
104*5c2921b0SApple OSS Distributions 			if (try_to_corrupt_page(blocks[i] + buffer_offset)) {
105*5c2921b0SApple OSS Distributions 				corrupt++;
106*5c2921b0SApple OSS Distributions 			}
107*5c2921b0SApple OSS Distributions 		}
108*5c2921b0SApple OSS Distributions 	}
109*5c2921b0SApple OSS Distributions 
110*5c2921b0SApple OSS Distributions 	T_LOG("corrupted %u/%llu pages. accessing...\n", corrupt, total_pages);
111*5c2921b0SApple OSS Distributions 	if (corrupt == 0) {
112*5c2921b0SApple OSS Distributions 		T_SKIP("no pages corrupted");
113*5c2921b0SApple OSS Distributions 	}
114*5c2921b0SApple OSS Distributions }
115*5c2921b0SApple OSS Distributions 
116*5c2921b0SApple OSS Distributions static bool
try_write(volatile uint32_t * word __unused)117*5c2921b0SApple OSS Distributions try_write(volatile uint32_t *word __unused)
118*5c2921b0SApple OSS Distributions {
119*5c2921b0SApple OSS Distributions #ifdef __arm64__
120*5c2921b0SApple OSS Distributions 	uint64_t val = 1;
121*5c2921b0SApple OSS Distributions 	__asm__ volatile (
122*5c2921b0SApple OSS Distributions              "str		%w0, %1\n"
123*5c2921b0SApple OSS Distributions              "mov		%0, 0\n"
124*5c2921b0SApple OSS Distributions              : "+r"(val) : "m"(*word));
125*5c2921b0SApple OSS Distributions 	// The exception handler skips over the instruction that zeroes val when a
126*5c2921b0SApple OSS Distributions 	// decompression failure is detected.
127*5c2921b0SApple OSS Distributions 	return val == 0;
128*5c2921b0SApple OSS Distributions #else
129*5c2921b0SApple OSS Distributions 	return false;
130*5c2921b0SApple OSS Distributions #endif
131*5c2921b0SApple OSS Distributions }
132*5c2921b0SApple OSS Distributions 
133*5c2921b0SApple OSS Distributions static bool
read_blocks(void)134*5c2921b0SApple OSS Distributions read_blocks(void)
135*5c2921b0SApple OSS Distributions {
136*5c2921b0SApple OSS Distributions 	for (uint32_t i = 0; i < block_count; i++) {
137*5c2921b0SApple OSS Distributions 		for (size_t buffer_offset = 0; buffer_offset < block_length;
138*5c2921b0SApple OSS Distributions 		    buffer_offset += vm_pagesize) {
139*5c2921b0SApple OSS Distributions 			// Access pages until the fault is detected.
140*5c2921b0SApple OSS Distributions 			if (!try_write((volatile uint32_t *)(blocks[i] + buffer_offset))) {
141*5c2921b0SApple OSS Distributions 				T_LOG("test_thread breaking");
142*5c2921b0SApple OSS Distributions 				return true;
143*5c2921b0SApple OSS Distributions 			}
144*5c2921b0SApple OSS Distributions 		}
145*5c2921b0SApple OSS Distributions 	}
146*5c2921b0SApple OSS Distributions 	return false;
147*5c2921b0SApple OSS Distributions }
148*5c2921b0SApple OSS Distributions 
149*5c2921b0SApple OSS Distributions static size_t
kern_memory_failure_handler(__unused mach_port_t task,__unused mach_port_t thread,exception_type_t exception,mach_exception_data_t code)150*5c2921b0SApple OSS Distributions kern_memory_failure_handler(
151*5c2921b0SApple OSS Distributions 	__unused mach_port_t task,
152*5c2921b0SApple OSS Distributions 	__unused mach_port_t thread,
153*5c2921b0SApple OSS Distributions 	exception_type_t exception,
154*5c2921b0SApple OSS Distributions 	mach_exception_data_t code)
155*5c2921b0SApple OSS Distributions {
156*5c2921b0SApple OSS Distributions 	T_EXPECT_EQ(exception, EXC_BAD_ACCESS,
157*5c2921b0SApple OSS Distributions 	    "Verified bad address exception");
158*5c2921b0SApple OSS Distributions 	T_EXPECT_EQ((int)code[0], KERN_MEMORY_FAILURE, "caught KERN_MEMORY_FAILURE");
159*5c2921b0SApple OSS Distributions 	T_PASS("received KERN_MEMORY_FAILURE from test thread");
160*5c2921b0SApple OSS Distributions 	// Skip the next instruction as well so that the faulting code can detect
161*5c2921b0SApple OSS Distributions 	// the exception.
162*5c2921b0SApple OSS Distributions 	return 8;
163*5c2921b0SApple OSS Distributions }
164*5c2921b0SApple OSS Distributions 
165*5c2921b0SApple OSS Distributions T_DECL(decompression_failure,
166*5c2921b0SApple OSS Distributions     "Confirm that exception is raised on decompression failure",
167*5c2921b0SApple OSS Distributions     // Disable software checks in development builds, as these would result in
168*5c2921b0SApple OSS Distributions     // panics.
169*5c2921b0SApple OSS Distributions     T_META_BOOTARGS_SET("vm_compressor_validation=0"),
170*5c2921b0SApple OSS Distributions     T_META_ASROOT(true),
171*5c2921b0SApple OSS Distributions     // This test intentionally corrupts pages backing heap memory, so it's
172*5c2921b0SApple OSS Distributions     // not practical for it to release all the buffers properly.
173*5c2921b0SApple OSS Distributions     T_META_CHECK_LEAKS(false))
174*5c2921b0SApple OSS Distributions {
175*5c2921b0SApple OSS Distributions 	T_SETUPBEGIN;
176*5c2921b0SApple OSS Distributions 
177*5c2921b0SApple OSS Distributions #if !TARGET_OS_OSX
178*5c2921b0SApple OSS Distributions 	if (pid_hibernate(-2) != 0) {
179*5c2921b0SApple OSS Distributions 		T_SKIP("compressor not active");
180*5c2921b0SApple OSS Distributions 	}
181*5c2921b0SApple OSS Distributions #endif
182*5c2921b0SApple OSS Distributions 
183*5c2921b0SApple OSS Distributions 	int value;
184*5c2921b0SApple OSS Distributions 	size_t size = sizeof(value);
185*5c2921b0SApple OSS Distributions 	if (sysctlbyname("vm.compressor_inject_error", &value, &size, NULL, 0)
186*5c2921b0SApple OSS Distributions 	    != 0) {
187*5c2921b0SApple OSS Distributions 		T_SKIP("vm.compressor_inject_error not present");
188*5c2921b0SApple OSS Distributions 	}
189*5c2921b0SApple OSS Distributions 
190*5c2921b0SApple OSS Distributions 	T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.pagesize", &value, &size, NULL, 0),
191*5c2921b0SApple OSS Distributions 	    NULL);
192*5c2921b0SApple OSS Distributions 	T_ASSERT_EQ_ULONG(size, sizeof(value), NULL);
193*5c2921b0SApple OSS Distributions 	page_size = (vm_address_t)value;
194*5c2921b0SApple OSS Distributions 
195*5c2921b0SApple OSS Distributions 	mach_port_t exc_port = create_exception_port(EXC_MASK_BAD_ACCESS);
196*5c2921b0SApple OSS Distributions 	create_corrupted_regions();
197*5c2921b0SApple OSS Distributions 	T_SETUPEND;
198*5c2921b0SApple OSS Distributions 
199*5c2921b0SApple OSS Distributions 	run_exception_handler(exc_port, kern_memory_failure_handler);
200*5c2921b0SApple OSS Distributions 
201*5c2921b0SApple OSS Distributions 	if (!read_blocks()) {
202*5c2921b0SApple OSS Distributions 		T_SKIP("no faults");
203*5c2921b0SApple OSS Distributions 	}
204*5c2921b0SApple OSS Distributions }
205