]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
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( | |
c3c9b80d A |
123 | __unused mach_port_t task, |
124 | __unused mach_port_t thread, | |
f427ee49 A |
125 | exception_type_t exception, |
126 | mach_exception_data_t code) | |
127 | { | |
128 | T_EXPECT_EQ(exception, EXC_BAD_ACCESS, | |
129 | "Verified bad address exception"); | |
130 | T_EXPECT_EQ((int)code[0], KERN_MEMORY_FAILURE, "caught KERN_MEMORY_FAILURE"); | |
131 | T_PASS("received KERN_MEMORY_FAILURE from test thread"); | |
132 | // Skip the next instruction as well so that the faulting code can detect | |
133 | // the exception. | |
134 | return 8; | |
135 | } | |
136 | ||
137 | static void | |
138 | run_test_expect_fault() | |
139 | { | |
140 | mach_port_t exc_port = create_exception_port(EXC_MASK_BAD_ACCESS); | |
141 | vm_address_t buffer_length = 10 * 1024ULL * 1024ULL; | |
142 | vm_address_t buffer_start = create_corrupted_region(buffer_length); | |
143 | ||
144 | run_exception_handler(exc_port, kern_memory_failure_handler); | |
145 | run_test(buffer_start, buffer_length); | |
146 | free((void *)buffer_start); | |
147 | } | |
148 | ||
149 | ||
150 | ||
151 | T_DECL(decompression_failure, | |
152 | "Confirm that exception is raised on decompression failure", | |
153 | // Disable software checks in development builds, as these would result in | |
154 | // panics. | |
155 | T_META_BOOTARGS_SET("vm_compressor_validation=0")) | |
156 | { | |
157 | if (pid_hibernate(-2) != 0) { | |
158 | T_SKIP("compressor not active"); | |
159 | } | |
160 | ||
161 | int value; | |
162 | size_t size = sizeof(value); | |
163 | if (sysctlbyname("vm.compressor_inject_error", &value, &size, NULL, 0) | |
164 | != 0) { | |
165 | T_SKIP("vm.compressor_inject_error not present"); | |
166 | } | |
167 | ||
168 | T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.pagesize", &value, &size, NULL, 0), | |
169 | NULL); | |
170 | T_ASSERT_EQ_ULONG(size, sizeof(value), NULL); | |
171 | page_size = (vm_address_t)value; | |
172 | ||
173 | run_test_expect_fault(); | |
174 | } |