]> git.saurik.com Git - apple/xnu.git/blob - tests/decompression_failure.c
1e753f7f1d72b2e468cbff5226b01c5c0b587352
[apple/xnu.git] / tests / decompression_failure.c
1 #include <darwintest.h>
2 #include <mach/mach.h>
3 #include <sys/sysctl.h>
4 #include <stdio.h>
5 #include <stdbool.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <inttypes.h>
9 #include <pthread.h>
10 #include "excserver.h"
11 #include "exc_helpers.h"
12
13 extern int pid_hibernate(int pid);
14
15 static vm_address_t page_size;
16
17 T_GLOBAL_META(
18 T_META_REQUIRES_SYSCTL_EQ("hw.optional.wkdm_popcount", 1)
19 );
20
21 static void
22 page_out(void)
23 {
24 T_ASSERT_POSIX_SUCCESS(pid_hibernate(-2), NULL);
25 T_ASSERT_POSIX_SUCCESS(pid_hibernate(-2), NULL);
26 }
27
28 static void
29 dirty_page(const vm_address_t address)
30 {
31 assert((address & (page_size - 1)) == 0UL);
32 uint32_t *const page_as_u32 = (uint32_t *)address;
33 for (uint32_t i = 0; i < page_size / sizeof(uint32_t); i += 2) {
34 page_as_u32[i + 0] = i % 4;
35 page_as_u32[i + 1] = 0xcdcdcdcd;
36 }
37 }
38
39 static bool
40 try_to_corrupt_page(vm_address_t page_va)
41 {
42 int val;
43 size_t size = sizeof(val);
44 int result = sysctlbyname("vm.compressor_inject_error", &val, &size,
45 &page_va, sizeof(page_va));
46 return result == 0;
47 }
48
49 static vm_address_t
50 create_corrupted_region(const vm_address_t buffer_length)
51 {
52 void *const bufferp = malloc(buffer_length);
53 T_ASSERT_NOTNULL(bufferp, "allocated test buffer");
54 const vm_address_t buffer = (vm_address_t)bufferp;
55
56 T_LOG("buffer address: %lx\n", (unsigned long)buffer);
57
58 for (size_t buffer_offset = 0; buffer_offset < buffer_length;
59 buffer_offset += page_size) {
60 dirty_page(buffer + buffer_offset);
61 }
62
63 page_out();
64
65 uint32_t corrupt = 0;
66 for (size_t buffer_offset = 0; buffer_offset < buffer_length;
67 buffer_offset += page_size) {
68 if (try_to_corrupt_page(buffer + buffer_offset)) {
69 corrupt++;
70 }
71 }
72
73 T_LOG("corrupted %u/%lu pages. accessing...\n", corrupt,
74 (unsigned long)(buffer_length / page_size));
75 if (corrupt == 0) {
76 T_SKIP("no pages corrupted");
77 }
78
79 return buffer;
80 }
81
82 static bool
83 try_write(volatile uint32_t *word __unused)
84 {
85 #ifdef __arm64__
86 uint64_t val = 1;
87 __asm__ volatile (
88 "str %w0, %1\n"
89 "mov %0, 0\n"
90 : "+r"(val) : "m"(*word));
91 // The exception handler skips over the instruction that zeroes val when a
92 // decompression failure is detected.
93 return val == 0;
94 #else
95 return false;
96 #endif
97 }
98
99 static void *
100 run_test(vm_address_t buffer_start, vm_address_t buffer_length)
101 {
102 bool fault = false;
103 for (size_t buffer_offset = 0; buffer_offset < buffer_length;
104 buffer_offset += page_size) {
105 // Access pages until the fault is detected.
106 if (!try_write((volatile uint32_t *)(buffer_start +
107 buffer_offset))) {
108 T_LOG("test_thread breaking");
109 fault = true;
110 break;
111 }
112 }
113
114 if (!fault) {
115 T_SKIP("no faults");
116 }
117 T_LOG("test thread completing");
118 return NULL;
119 }
120
121 static size_t
122 kern_memory_failure_handler(
123 exception_type_t exception,
124 mach_exception_data_t code)
125 {
126 T_EXPECT_EQ(exception, EXC_BAD_ACCESS,
127 "Verified bad address exception");
128 T_EXPECT_EQ((int)code[0], KERN_MEMORY_FAILURE, "caught KERN_MEMORY_FAILURE");
129 T_PASS("received KERN_MEMORY_FAILURE from test thread");
130 // Skip the next instruction as well so that the faulting code can detect
131 // the exception.
132 return 8;
133 }
134
135 static void
136 run_test_expect_fault()
137 {
138 mach_port_t exc_port = create_exception_port(EXC_MASK_BAD_ACCESS);
139 vm_address_t buffer_length = 10 * 1024ULL * 1024ULL;
140 vm_address_t buffer_start = create_corrupted_region(buffer_length);
141
142 run_exception_handler(exc_port, kern_memory_failure_handler);
143 run_test(buffer_start, buffer_length);
144 free((void *)buffer_start);
145 }
146
147
148
149 T_DECL(decompression_failure,
150 "Confirm that exception is raised on decompression failure",
151 // Disable software checks in development builds, as these would result in
152 // panics.
153 T_META_BOOTARGS_SET("vm_compressor_validation=0"))
154 {
155 if (pid_hibernate(-2) != 0) {
156 T_SKIP("compressor not active");
157 }
158
159 int value;
160 size_t size = sizeof(value);
161 if (sysctlbyname("vm.compressor_inject_error", &value, &size, NULL, 0)
162 != 0) {
163 T_SKIP("vm.compressor_inject_error not present");
164 }
165
166 T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.pagesize", &value, &size, NULL, 0),
167 NULL);
168 T_ASSERT_EQ_ULONG(size, sizeof(value), NULL);
169 page_size = (vm_address_t)value;
170
171 run_test_expect_fault();
172 }