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